From 548d958f18e92c77f9654f1b4fe4188cf64bf009 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Fri, 29 May 2026 09:26:51 -0400 Subject: [PATCH] fix(skills): pull upstream skill runtime resolution to stop event-loop starvation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fork's listRuntimeSkillEntries rematerialized every skill's files from the DB on every heartbeat run dispatch — fs.rm + fs.mkdir + per-file readFile/writeFile, sequentially per skill. With 24 configured skills and 5 concurrent agents, this saturated the Node event loop badly enough that executeRun continuations couldn't reach activeRunExecutions.add() within the orphan-reaper's 5-min threshold, causing reaper to false-positive runs as "process_lost". Upstream's listRuntimeSkillEntries calls resolveRuntimeSkillSource, which checks if the materialized directory already exists on disk and short- circuits when it does. Fixes the symptom at the root. Replaces these files with upstream/master content: - server/src/services/company-skills.ts - server/src/services/heartbeat.ts - server/src/services/workspace-runtime.ts - server/src/services/company-portability.ts - server/src/routes/company-skills.ts - server/src/routes/agents.ts - packages/adapter-utils/src/server-utils.ts Pulls in supporting upstream files: - server/src/services/catalog-provenance.ts - server/src/services/skills-catalog.ts - server/src/services/github-fetch.ts - server/src/services/portable-path.ts - packages/skills-catalog/ (new package) - packages/db document_annotation_* schema + migration 0091 - packages/shared document-annotation types/validators Drops fork features (to be re-evaluated later): - Gitea/Forgejo git skill sources (server/src/services/git-source.ts deleted) - PAT support for private skill repos - Fork-specific secret-export portability extensions Adds agentId: null to acquireRunLease test-probe call in routes/agents.ts to satisfy the fork's environment-runtime agentId requirement (kept). Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/adapter-utils/src/server-utils.ts | 177 + packages/db/src/migrations/0091_old_swarm.sql | 189 + .../db/src/migrations/meta/0091_snapshot.json | 18843 ++++++++++++++++ packages/db/src/migrations/meta/_journal.json | 7 + .../document_annotation_anchor_snapshots.ts | 42 + .../schema/document_annotation_comments.ts | 44 + .../src/schema/document_annotation_threads.ts | 70 + packages/db/src/schema/index.ts | 3 + packages/shared/src/constants.ts | 16 + packages/shared/src/document-anchors.ts | 464 + packages/shared/src/index.ts | 76 +- packages/shared/src/types/company-skill.ts | 108 + .../shared/src/types/document-annotation.ts | 134 + packages/shared/src/types/index.ts | 32 +- .../shared/src/validators/company-skill.ts | 111 +- .../src/validators/document-annotation.ts | 65 + packages/shared/src/validators/index.ts | 32 +- .../bundled/docs/doc-maintenance/SKILL.md | 75 + .../issue-triage/SKILL.md | 74 + .../task-planning/SKILL.md | 84 + .../bundled/quality/qa-acceptance/SKILL.md | 93 + .../github-pr-workflow/SKILL.md | 93 + .../optional/browser/agent-browser/SKILL.md | 93 + .../content/release-announcement/SKILL.md | 128 + .../optional/product/design-critique/SKILL.md | 121 + .../skills-catalog/generated/catalog.json | 285 + packages/skills-catalog/package.json | 49 + .../scripts/build-catalog-manifest.ts | 15 + .../scripts/validate-catalog.ts | 15 + .../src/catalog-builder.test.ts | 165 + .../skills-catalog/src/catalog-builder.ts | 443 + packages/skills-catalog/src/frontmatter.ts | 154 + packages/skills-catalog/src/index.ts | 37 + .../src/shipped-catalog.test.ts | 90 + packages/skills-catalog/src/types.ts | 48 + packages/skills-catalog/tsconfig.json | 8 + packages/skills-catalog/vitest.config.ts | 8 + pnpm-lock.yaml | 2 + .../src/__tests__/company-portability.test.ts | 506 +- .../src/__tests__/environment-runtime.test.ts | 295 - server/src/__tests__/git-source.test.ts | 410 - server/src/routes/agents.ts | 7 +- server/src/routes/company-skills.ts | 205 +- server/src/services/catalog-provenance.ts | 65 + server/src/services/company-portability.ts | 521 +- server/src/services/company-skills.ts | 1349 +- server/src/services/git-source.ts | 282 - server/src/services/github-fetch.ts | 25 + server/src/services/heartbeat.ts | 105 +- server/src/services/portable-path.ts | 12 + server/src/services/skills-catalog.ts | 201 + server/src/services/workspace-runtime.ts | 173 +- 52 files changed, 24613 insertions(+), 2036 deletions(-) create mode 100644 packages/db/src/migrations/0091_old_swarm.sql create mode 100644 packages/db/src/migrations/meta/0091_snapshot.json create mode 100644 packages/db/src/schema/document_annotation_anchor_snapshots.ts create mode 100644 packages/db/src/schema/document_annotation_comments.ts create mode 100644 packages/db/src/schema/document_annotation_threads.ts create mode 100644 packages/shared/src/document-anchors.ts create mode 100644 packages/shared/src/types/document-annotation.ts create mode 100644 packages/shared/src/validators/document-annotation.ts create mode 100644 packages/skills-catalog/catalog/bundled/docs/doc-maintenance/SKILL.md create mode 100644 packages/skills-catalog/catalog/bundled/paperclip-operations/issue-triage/SKILL.md create mode 100644 packages/skills-catalog/catalog/bundled/paperclip-operations/task-planning/SKILL.md create mode 100644 packages/skills-catalog/catalog/bundled/quality/qa-acceptance/SKILL.md create mode 100644 packages/skills-catalog/catalog/bundled/software-development/github-pr-workflow/SKILL.md create mode 100644 packages/skills-catalog/catalog/optional/browser/agent-browser/SKILL.md create mode 100644 packages/skills-catalog/catalog/optional/content/release-announcement/SKILL.md create mode 100644 packages/skills-catalog/catalog/optional/product/design-critique/SKILL.md create mode 100644 packages/skills-catalog/generated/catalog.json create mode 100644 packages/skills-catalog/package.json create mode 100644 packages/skills-catalog/scripts/build-catalog-manifest.ts create mode 100644 packages/skills-catalog/scripts/validate-catalog.ts create mode 100644 packages/skills-catalog/src/catalog-builder.test.ts create mode 100644 packages/skills-catalog/src/catalog-builder.ts create mode 100644 packages/skills-catalog/src/frontmatter.ts create mode 100644 packages/skills-catalog/src/index.ts create mode 100644 packages/skills-catalog/src/shipped-catalog.test.ts create mode 100644 packages/skills-catalog/src/types.ts create mode 100644 packages/skills-catalog/tsconfig.json create mode 100644 packages/skills-catalog/vitest.config.ts delete mode 100644 server/src/__tests__/git-source.test.ts create mode 100644 server/src/services/catalog-provenance.ts delete mode 100644 server/src/services/git-source.ts create mode 100644 server/src/services/github-fetch.ts create mode 100644 server/src/services/portable-path.ts create mode 100644 server/src/services/skills-catalog.ts diff --git a/packages/adapter-utils/src/server-utils.ts b/packages/adapter-utils/src/server-utils.ts index 4624f637..44eb5bbd 100644 --- a/packages/adapter-utils/src/server-utils.ts +++ b/packages/adapter-utils/src/server-utils.ts @@ -133,6 +133,8 @@ export interface PaperclipSkillEntry { key: string; runtimeName: string; source: string; + sourceStatus?: "available" | "missing"; + missingDetail?: string | null; required?: boolean; requiredReason?: string | null; } @@ -161,6 +163,22 @@ interface PersistentSkillSnapshotOptions { warnings?: string[]; } +interface RuntimeMountedSkillSnapshotOptions { + adapterType: string; + availableEntries: PaperclipSkillEntry[]; + desiredSkills: string[]; + configuredDetail: string | ((entry: PaperclipSkillEntry) => string | null); + missingDetail?: string; + mode?: "ephemeral" | "unsupported"; + supported?: boolean; + unsupportedDetail?: string | ((entry: PaperclipSkillEntry) => string | null); + warnings?: string[]; + externalInstalled?: Map; + externalLocationLabel?: string | null; + externalDetail?: string; + skillsHome?: string; +} + function normalizePathSlashes(value: string): string { return value.replaceAll("\\", "/"); } @@ -193,6 +211,26 @@ function buildManagedSkillOrigin(entry: { required?: boolean }): Pick< }; } +function isPaperclipSkillSourceMissing(entry: PaperclipSkillEntry) { + return entry.sourceStatus === "missing"; +} + +function resolvePaperclipSkillMissingDetail( + entry: PaperclipSkillEntry, + fallback: string, +) { + return entry.missingDetail?.trim() || fallback; +} + +function resolveSkillDetail( + detail: string | ((entry: PaperclipSkillEntry) => string | null) | null | undefined, + entry: PaperclipSkillEntry, +): string | null { + if (typeof detail === "function") return detail(entry); + if (typeof detail === "string") return detail; + return null; +} + function resolveInstalledEntryTarget( skillsHome: string, entryName: string, @@ -1381,6 +1419,120 @@ export async function readInstalledSkillTargets(skillsHome: string): Promise [entry.key, entry])); + const desiredSet = new Set(desiredSkills); + const entries: AdapterSkillEntry[] = []; + const warnings = [...(options.warnings ?? [])]; + + for (const available of availableEntries) { + const desired = desiredSet.has(available.key); + if (isPaperclipSkillSourceMissing(available)) { + entries.push({ + key: available.key, + runtimeName: available.runtimeName, + desired, + managed: true, + state: "missing", + sourcePath: null, + targetPath: null, + detail: resolvePaperclipSkillMissingDetail(available, missingDetail), + required: Boolean(available.required), + requiredReason: available.requiredReason ?? null, + ...buildManagedSkillOrigin(available), + }); + continue; + } + + const configured = supported && mode === "ephemeral" && desired; + entries.push({ + key: available.key, + runtimeName: available.runtimeName, + desired, + managed: true, + state: configured ? "configured" : "available", + sourcePath: available.source, + targetPath: null, + detail: desired + ? configured + ? resolveSkillDetail(configuredDetail, available) + : resolveSkillDetail( + options.unsupportedDetail + ?? "Desired state is stored in Paperclip only; this adapter cannot apply skills at runtime.", + available, + ) + : null, + required: Boolean(available.required), + requiredReason: available.requiredReason ?? null, + ...buildManagedSkillOrigin(available), + }); + } + + for (const desiredSkill of desiredSkills) { + if (availableByKey.has(desiredSkill)) continue; + warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`); + entries.push({ + key: desiredSkill, + runtimeName: null, + desired: true, + managed: true, + state: "missing", + sourcePath: null, + targetPath: null, + detail: missingDetail, + origin: "external_unknown", + originLabel: "External or unavailable", + readOnly: false, + }); + } + + if (externalInstalled) { + for (const [name, installedEntry] of externalInstalled.entries()) { + if (availableEntries.some((entry) => entry.runtimeName === name)) continue; + entries.push({ + key: name, + runtimeName: name, + desired: false, + managed: false, + state: "external", + origin: "user_installed", + originLabel: "User-installed", + locationLabel: skillLocationLabel(externalLocationLabel), + readOnly: true, + sourcePath: null, + targetPath: installedEntry.targetPath ?? (skillsHome ? path.join(skillsHome, name) : null), + detail: externalDetail, + }); + } + } + + entries.sort((left, right) => left.key.localeCompare(right.key)); + + return { + adapterType, + supported, + mode, + desiredSkills, + entries, + warnings, + }; +} + export function buildPersistentSkillSnapshot( options: PersistentSkillSnapshotOptions, ): AdapterSkillSnapshot { @@ -1404,6 +1556,26 @@ export function buildPersistentSkillSnapshot( for (const available of availableEntries) { const installedEntry = installed.get(available.runtimeName) ?? null; const desired = desiredSet.has(available.key); + if (isPaperclipSkillSourceMissing(available)) { + entries.push({ + key: available.key, + runtimeName: available.runtimeName, + desired, + managed: true, + state: "missing", + sourcePath: null, + targetPath: path.join(skillsHome, available.runtimeName), + detail: resolvePaperclipSkillMissingDetail( + available, + missingDetail, + ), + required: Boolean(available.required), + requiredReason: available.requiredReason ?? null, + ...buildManagedSkillOrigin(available), + }); + continue; + } + let state: AdapterSkillEntry["state"] = "available"; let managed = false; let detail: string | null = null; @@ -1496,6 +1668,11 @@ function normalizeConfiguredPaperclipRuntimeSkills(value: unknown): PaperclipSki key, runtimeName, source, + sourceStatus: entry.sourceStatus === "missing" ? "missing" : "available", + missingDetail: + typeof entry.missingDetail === "string" && entry.missingDetail.trim().length > 0 + ? entry.missingDetail.trim() + : null, required: asBoolean(entry.required, false), requiredReason: typeof entry.requiredReason === "string" && entry.requiredReason.trim().length > 0 diff --git a/packages/db/src/migrations/0091_old_swarm.sql b/packages/db/src/migrations/0091_old_swarm.sql new file mode 100644 index 00000000..6efb26bd --- /dev/null +++ b/packages/db/src/migrations/0091_old_swarm.sql @@ -0,0 +1,189 @@ +CREATE TABLE IF NOT EXISTS "document_annotation_threads" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "issue_id" uuid NOT NULL, + "document_id" uuid NOT NULL, + "document_key" text NOT NULL, + "status" text DEFAULT 'open' NOT NULL, + "anchor_state" text DEFAULT 'active' NOT NULL, + "original_revision_id" uuid, + "original_revision_number" integer NOT NULL, + "current_revision_id" uuid, + "current_revision_number" integer NOT NULL, + "selected_text" text NOT NULL, + "prefix_text" text DEFAULT '' NOT NULL, + "suffix_text" text DEFAULT '' NOT NULL, + "normalized_start" integer NOT NULL, + "normalized_end" integer NOT NULL, + "markdown_start" integer NOT NULL, + "markdown_end" integer NOT NULL, + "anchor_confidence" text DEFAULT 'exact' NOT NULL, + "anchor_selector" jsonb NOT NULL, + "created_by_agent_id" uuid, + "created_by_user_id" text, + "resolved_by_agent_id" uuid, + "resolved_by_user_id" text, + "resolved_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 +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "document_annotation_comments" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "thread_id" uuid NOT NULL, + "issue_id" uuid NOT NULL, + "document_id" uuid NOT NULL, + "body" text NOT NULL, + "author_type" text NOT NULL, + "author_agent_id" uuid, + "author_user_id" text, + "created_by_run_id" uuid, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "document_annotation_anchor_snapshots" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "thread_id" uuid NOT NULL, + "document_id" uuid NOT NULL, + "from_revision_id" uuid, + "from_revision_number" integer, + "to_revision_id" uuid, + "to_revision_number" integer NOT NULL, + "previous_anchor" jsonb NOT NULL, + "next_anchor" jsonb, + "anchor_state" text NOT NULL, + "anchor_confidence" text NOT NULL, + "failure_reason" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_threads_company_id_companies_id_fk') THEN + ALTER TABLE "document_annotation_threads" ADD CONSTRAINT "document_annotation_threads_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_threads_issue_id_issues_id_fk') THEN + ALTER TABLE "document_annotation_threads" ADD CONSTRAINT "document_annotation_threads_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_threads_document_id_documents_id_fk') THEN + ALTER TABLE "document_annotation_threads" ADD CONSTRAINT "document_annotation_threads_document_id_documents_id_fk" FOREIGN KEY ("document_id") REFERENCES "public"."documents"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_threads_original_revision_id_document_revisions_id_fk') THEN + ALTER TABLE "document_annotation_threads" ADD CONSTRAINT "document_annotation_threads_original_revision_id_document_revisions_id_fk" FOREIGN KEY ("original_revision_id") REFERENCES "public"."document_revisions"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_threads_current_revision_id_document_revisions_id_fk') THEN + ALTER TABLE "document_annotation_threads" ADD CONSTRAINT "document_annotation_threads_current_revision_id_document_revisions_id_fk" FOREIGN KEY ("current_revision_id") REFERENCES "public"."document_revisions"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_threads_created_by_agent_id_agents_id_fk') THEN + ALTER TABLE "document_annotation_threads" ADD CONSTRAINT "document_annotation_threads_created_by_agent_id_agents_id_fk" FOREIGN KEY ("created_by_agent_id") REFERENCES "public"."agents"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_threads_resolved_by_agent_id_agents_id_fk') THEN + ALTER TABLE "document_annotation_threads" ADD CONSTRAINT "document_annotation_threads_resolved_by_agent_id_agents_id_fk" FOREIGN KEY ("resolved_by_agent_id") REFERENCES "public"."agents"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_comments_company_id_companies_id_fk') THEN + ALTER TABLE "document_annotation_comments" ADD CONSTRAINT "document_annotation_comments_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_comments_thread_id_document_annotation_threads_id_fk') THEN + ALTER TABLE "document_annotation_comments" ADD CONSTRAINT "document_annotation_comments_thread_id_document_annotation_threads_id_fk" FOREIGN KEY ("thread_id") REFERENCES "public"."document_annotation_threads"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_comments_issue_id_issues_id_fk') THEN + ALTER TABLE "document_annotation_comments" ADD CONSTRAINT "document_annotation_comments_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_comments_document_id_documents_id_fk') THEN + ALTER TABLE "document_annotation_comments" ADD CONSTRAINT "document_annotation_comments_document_id_documents_id_fk" FOREIGN KEY ("document_id") REFERENCES "public"."documents"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_comments_author_agent_id_agents_id_fk') THEN + ALTER TABLE "document_annotation_comments" ADD CONSTRAINT "document_annotation_comments_author_agent_id_agents_id_fk" FOREIGN KEY ("author_agent_id") REFERENCES "public"."agents"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk') THEN + ALTER TABLE "document_annotation_comments" ADD CONSTRAINT "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("created_by_run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_anchor_snapshots_company_id_companies_id_fk') THEN + ALTER TABLE "document_annotation_anchor_snapshots" ADD CONSTRAINT "document_annotation_anchor_snapshots_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk') THEN + ALTER TABLE "document_annotation_anchor_snapshots" ADD CONSTRAINT "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk" FOREIGN KEY ("thread_id") REFERENCES "public"."document_annotation_threads"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_anchor_snapshots_document_id_documents_id_fk') THEN + ALTER TABLE "document_annotation_anchor_snapshots" ADD CONSTRAINT "document_annotation_anchor_snapshots_document_id_documents_id_fk" FOREIGN KEY ("document_id") REFERENCES "public"."documents"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk') THEN + ALTER TABLE "document_annotation_anchor_snapshots" ADD CONSTRAINT "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk" FOREIGN KEY ("from_revision_id") REFERENCES "public"."document_revisions"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk') THEN + ALTER TABLE "document_annotation_anchor_snapshots" ADD CONSTRAINT "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk" FOREIGN KEY ("to_revision_id") REFERENCES "public"."document_revisions"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_annotation_threads_company_document_status_idx" ON "document_annotation_threads" USING btree ("company_id","document_id","status"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_annotation_threads_company_issue_status_idx" ON "document_annotation_threads" USING btree ("company_id","issue_id","status"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_annotation_threads_company_current_revision_open_idx" ON "document_annotation_threads" USING btree ("company_id","document_id","current_revision_id","status"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_annotation_threads_company_anchor_state_idx" ON "document_annotation_threads" USING btree ("company_id","anchor_state"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_annotation_comments_company_thread_created_at_idx" ON "document_annotation_comments" USING btree ("company_id","thread_id","created_at"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_annotation_comments_company_issue_created_at_idx" ON "document_annotation_comments" USING btree ("company_id","issue_id","created_at"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_annotation_comments_company_document_created_at_idx" ON "document_annotation_comments" USING btree ("company_id","document_id","created_at"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_annotation_comments_body_search_idx" ON "document_annotation_comments" USING gin ("body" gin_trgm_ops); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_annotation_anchor_snapshots_company_thread_created_at_idx" ON "document_annotation_anchor_snapshots" USING btree ("company_id","thread_id","created_at"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_annotation_anchor_snapshots_company_document_revision_idx" ON "document_annotation_anchor_snapshots" USING btree ("company_id","document_id","to_revision_number"); diff --git a/packages/db/src/migrations/meta/0091_snapshot.json b/packages/db/src/migrations/meta/0091_snapshot.json new file mode 100644 index 00000000..ba11ae35 --- /dev/null +++ b/packages/db/src/migrations/meta/0091_snapshot.json @@ -0,0 +1,18843 @@ +{ + "id": "5ef9dd10-9627-4e9d-8d68-b1b9dc538224", + "prevId": "11e30d07-51bf-4073-badc-f65fd3de13ad", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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_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.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 + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_memberships": { + "name": "agent_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time 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_agent_idx": { + "name": "agent_memberships_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_agent_uq": { + "name": "agent_memberships_company_user_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memberships_company_id_companies_id_fk": { + "name": "agent_memberships_company_id_companies_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memberships_agent_id_agents_id_fk": { + "name": "agent_memberships_agent_id_agents_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time 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": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_default_environment_idx": { + "name": "agents_company_default_environment_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "default_environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_default_environment_id_environments_id_fk": { + "name": "agents_default_environment_id_environments_id_fk", + "tableFrom": "agents", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "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": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attachment_max_bytes": { + "name": "attachment_max_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10485760 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "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 + }, + "brand_color": { + "name": "brand_color", + "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_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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time 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_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 + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_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, + "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, + "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_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": {}, + "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 + }, + "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" + }, + "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": {} + } + }, + "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" + } + }, + "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.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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": "no action", + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time 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": "no action", + "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.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": true + }, + "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": "cascade", + "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()" + }, + "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 + }, + "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" + }, + "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_company_status_idx": { + "name": "environments_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": {} + }, + "environments_company_driver_idx": { + "name": "environments_company_driver_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'local'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_company_name_idx": { + "name": "environments_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": {} + } + }, + "foreignKeys": { + "environments_company_id_companies_id_fk": { + "name": "environments_company_id_companies_id_fk", + "tableFrom": "environments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "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.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": "integer", + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_watchdog_decisions": { + "name": "heartbeat_run_watchdog_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evaluation_issue_id": { + "name": "evaluation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_watchdog_decisions_company_run_created_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_watchdog_decisions_company_run_snooze_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_snooze_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snoozed_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_watchdog_decisions_company_id_companies_id_fk": { + "name": "heartbeat_run_watchdog_decisions_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk": { + "name": "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "issues", + "columnsFrom": [ + "evaluation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "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 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "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_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_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": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inbox_dismissals": { + "name": "inbox_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_key": { + "name": "item_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_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": { + "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.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'" + }, + "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": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time 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_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" + } + }, + "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 + }, + "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 + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time 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": "no action", + "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_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" + } + }, + "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_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": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "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_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": "no action", + "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'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_comment_id": { + "name": "source_comment_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 + }, + "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 + }, + "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": {} + } + }, + "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": "no action", + "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_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" + } + }, + "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_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 + }, + "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 + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "work_mode": { + "name": "work_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "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 + }, + "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_fingerprint": { + "name": "origin_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_policy": { + "name": "execution_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_state": { + "name": "execution_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_next_check_at": { + "name": "monitor_next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_wake_requested_at": { + "name": "monitor_wake_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_last_triggered_at": { + "name": "monitor_last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_attempt_count": { + "name": "monitor_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "monitor_notes": { + "name": "monitor_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "monitor_scheduled_by": { + "name": "monitor_scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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_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_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_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_monitor_due_idx": { + "name": "issues_company_monitor_due_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "monitor_next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_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_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": {} + } + }, + "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_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "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.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 + }, + "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_id_idx": { + "name": "plugin_config_plugin_id_idx", + "columns": [ + { + "expression": "plugin_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" + } + }, + "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 + }, + "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_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": {} + }, + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "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" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 + }, + "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_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" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "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_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" + } + }, + "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 + }, + "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_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" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_memberships": { + "name": "project_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time 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_project_idx": { + "name": "project_memberships_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_project_uq": { + "name": "project_memberships_company_user_project_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_memberships_company_id_companies_id_fk": { + "name": "project_memberships_company_id_companies_id_fk", + "tableFrom": "project_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_memberships_project_id_projects_id_fk": { + "name": "project_memberships_project_id_projects_id_fk", + "tableFrom": "project_memberships", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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 + }, + "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": {} + } + }, + "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 + }, + "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_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_dispatch_fingerprint_idx": { + "name": "routine_runs_dispatch_fingerprint_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatch_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_routine_revision_id_routine_revisions_id_fk": { + "name": "routine_runs_routine_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_revisions", + "columnsFrom": [ + "routine_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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'" + }, + "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 + }, + "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": {} + } + }, + "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_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.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": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "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": 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_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_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.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.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 + }, + "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": {} + } + }, + "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": "no action", + "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" + } + }, + "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 + }, + "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.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": true + }, + "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_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_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": {}, + "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": true + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time 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_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_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_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" + } + }, + "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 + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index a4509f32..0ff7d2df 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -638,6 +638,13 @@ "when": 1779573019125, "tag": "0090_resource_memberships", "breakpoints": true + }, + { + "idx": 91, + "version": "7", + "when": 1778810394522, + "tag": "0091_old_swarm", + "breakpoints": true } ] } diff --git a/packages/db/src/schema/document_annotation_anchor_snapshots.ts b/packages/db/src/schema/document_annotation_anchor_snapshots.ts new file mode 100644 index 00000000..9aef198a --- /dev/null +++ b/packages/db/src/schema/document_annotation_anchor_snapshots.ts @@ -0,0 +1,42 @@ +import type { + DocumentAnnotationAnchorConfidence, + DocumentAnnotationAnchorSnapshot, + DocumentAnnotationAnchorState, +} from "@paperclipai/shared"; +import { index, integer, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; +import { documentAnnotationThreads } from "./document_annotation_threads.js"; +import { documentRevisions } from "./document_revisions.js"; +import { documents } from "./documents.js"; + +export const documentAnnotationAnchorSnapshots = pgTable( + "document_annotation_anchor_snapshots", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id), + threadId: uuid("thread_id").notNull().references(() => documentAnnotationThreads.id, { onDelete: "cascade" }), + documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }), + fromRevisionId: uuid("from_revision_id").references(() => documentRevisions.id, { onDelete: "set null" }), + fromRevisionNumber: integer("from_revision_number"), + toRevisionId: uuid("to_revision_id").references(() => documentRevisions.id, { onDelete: "set null" }), + toRevisionNumber: integer("to_revision_number").notNull(), + previousAnchor: jsonb("previous_anchor").$type().notNull(), + nextAnchor: jsonb("next_anchor").$type(), + anchorState: text("anchor_state").$type().notNull(), + anchorConfidence: text("anchor_confidence").$type().notNull(), + failureReason: text("failure_reason"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyThreadCreatedAtIdx: index("document_annotation_anchor_snapshots_company_thread_created_at_idx").on( + table.companyId, + table.threadId, + table.createdAt, + ), + companyDocumentRevisionIdx: index("document_annotation_anchor_snapshots_company_document_revision_idx").on( + table.companyId, + table.documentId, + table.toRevisionNumber, + ), + }), +); diff --git a/packages/db/src/schema/document_annotation_comments.ts b/packages/db/src/schema/document_annotation_comments.ts new file mode 100644 index 00000000..764c5818 --- /dev/null +++ b/packages/db/src/schema/document_annotation_comments.ts @@ -0,0 +1,44 @@ +import type { IssueCommentAuthorType } from "@paperclipai/shared"; +import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { agents } from "./agents.js"; +import { companies } from "./companies.js"; +import { documentAnnotationThreads } from "./document_annotation_threads.js"; +import { documents } from "./documents.js"; +import { heartbeatRuns } from "./heartbeat_runs.js"; +import { issues } from "./issues.js"; + +export const documentAnnotationComments = pgTable( + "document_annotation_comments", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id), + threadId: uuid("thread_id").notNull().references(() => documentAnnotationThreads.id, { onDelete: "cascade" }), + issueId: uuid("issue_id").notNull().references(() => issues.id, { onDelete: "cascade" }), + documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }), + body: text("body").notNull(), + authorType: text("author_type").$type().notNull(), + authorAgentId: uuid("author_agent_id").references(() => agents.id, { onDelete: "set null" }), + authorUserId: text("author_user_id"), + createdByRunId: uuid("created_by_run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyThreadCreatedAtIdx: index("document_annotation_comments_company_thread_created_at_idx").on( + table.companyId, + table.threadId, + table.createdAt, + ), + companyIssueCreatedAtIdx: index("document_annotation_comments_company_issue_created_at_idx").on( + table.companyId, + table.issueId, + table.createdAt, + ), + companyDocumentCreatedAtIdx: index("document_annotation_comments_company_document_created_at_idx").on( + table.companyId, + table.documentId, + table.createdAt, + ), + bodySearchIdx: index("document_annotation_comments_body_search_idx").using("gin", table.body.op("gin_trgm_ops")), + }), +); diff --git a/packages/db/src/schema/document_annotation_threads.ts b/packages/db/src/schema/document_annotation_threads.ts new file mode 100644 index 00000000..c040fbb5 --- /dev/null +++ b/packages/db/src/schema/document_annotation_threads.ts @@ -0,0 +1,70 @@ +import type { + DocumentAnnotationAnchorConfidence, + DocumentAnnotationAnchorSelector, + DocumentAnnotationAnchorState, + DocumentAnnotationThreadStatus, +} from "@paperclipai/shared"; +import { index, integer, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { agents } from "./agents.js"; +import { companies } from "./companies.js"; +import { documentRevisions } from "./document_revisions.js"; +import { documents } from "./documents.js"; +import { issues } from "./issues.js"; + +export const documentAnnotationThreads = pgTable( + "document_annotation_threads", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id), + issueId: uuid("issue_id").notNull().references(() => issues.id, { onDelete: "cascade" }), + documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }), + documentKey: text("document_key").notNull(), + status: text("status").$type().notNull().default("open"), + anchorState: text("anchor_state").$type().notNull().default("active"), + originalRevisionId: uuid("original_revision_id").references(() => documentRevisions.id, { onDelete: "set null" }), + originalRevisionNumber: integer("original_revision_number").notNull(), + currentRevisionId: uuid("current_revision_id").references(() => documentRevisions.id, { onDelete: "set null" }), + currentRevisionNumber: integer("current_revision_number").notNull(), + selectedText: text("selected_text").notNull(), + prefixText: text("prefix_text").notNull().default(""), + suffixText: text("suffix_text").notNull().default(""), + normalizedStart: integer("normalized_start").notNull(), + normalizedEnd: integer("normalized_end").notNull(), + markdownStart: integer("markdown_start").notNull(), + markdownEnd: integer("markdown_end").notNull(), + anchorConfidence: text("anchor_confidence") + .$type() + .notNull() + .default("exact"), + anchorSelector: jsonb("anchor_selector").$type().notNull(), + createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }), + createdByUserId: text("created_by_user_id"), + resolvedByAgentId: uuid("resolved_by_agent_id").references(() => agents.id, { onDelete: "set null" }), + resolvedByUserId: text("resolved_by_user_id"), + resolvedAt: timestamp("resolved_at", { withTimezone: true }), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyDocumentStatusIdx: index("document_annotation_threads_company_document_status_idx").on( + table.companyId, + table.documentId, + table.status, + ), + companyIssueStatusIdx: index("document_annotation_threads_company_issue_status_idx").on( + table.companyId, + table.issueId, + table.status, + ), + companyCurrentRevisionOpenIdx: index("document_annotation_threads_company_current_revision_open_idx").on( + table.companyId, + table.documentId, + table.currentRevisionId, + table.status, + ), + companyAnchorStateIdx: index("document_annotation_threads_company_anchor_state_idx").on( + table.companyId, + table.anchorState, + ), + }), +); diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index 2afb0406..141dd51f 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -55,6 +55,9 @@ export { issueAttachments } from "./issue_attachments.js"; export { documents } from "./documents.js"; export { documentRevisions } from "./document_revisions.js"; export { issueDocuments } from "./issue_documents.js"; +export { documentAnnotationThreads } from "./document_annotation_threads.js"; +export { documentAnnotationComments } from "./document_annotation_comments.js"; +export { documentAnnotationAnchorSnapshots } from "./document_annotation_anchor_snapshots.js"; export { heartbeatRuns } from "./heartbeat_runs.js"; export { heartbeatRunEvents } from "./heartbeat_run_events.js"; export { heartbeatRunWatchdogDecisions } from "./heartbeat_run_watchdog_decisions.js"; diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index c102fa73..c6933bd7 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -281,6 +281,22 @@ export function isSystemIssueDocumentKey(key: string): key is SystemIssueDocumen export const ISSUE_REFERENCE_SOURCE_KINDS = ["title", "description", "comment", "document"] as const; export type IssueReferenceSourceKind = (typeof ISSUE_REFERENCE_SOURCE_KINDS)[number]; +export const DOCUMENT_ANNOTATION_THREAD_STATUSES = ["open", "resolved"] as const; +export type DocumentAnnotationThreadStatus = (typeof DOCUMENT_ANNOTATION_THREAD_STATUSES)[number]; + +export const DOCUMENT_ANNOTATION_ANCHOR_STATES = ["active", "stale", "orphaned"] as const; +export type DocumentAnnotationAnchorState = (typeof DOCUMENT_ANNOTATION_ANCHOR_STATES)[number]; + +export const DOCUMENT_ANNOTATION_ANCHOR_CONFIDENCES = [ + "exact", + "duplicate", + "fuzzy", + "ambiguous", + "missing", +] as const; +export type DocumentAnnotationAnchorConfidence = + (typeof DOCUMENT_ANNOTATION_ANCHOR_CONFIDENCES)[number]; + export const ISSUE_EXECUTION_POLICY_MODES = ["normal", "auto"] as const; export type IssueExecutionPolicyMode = (typeof ISSUE_EXECUTION_POLICY_MODES)[number]; diff --git a/packages/shared/src/document-anchors.ts b/packages/shared/src/document-anchors.ts new file mode 100644 index 00000000..2efc96cd --- /dev/null +++ b/packages/shared/src/document-anchors.ts @@ -0,0 +1,464 @@ +import type { + DocumentAnnotationAnchorConfidence, + DocumentAnnotationAnchorState, +} from "./constants.js"; +import type { + DocumentAnnotationAnchorSelector, + DocumentAnnotationAnchorSnapshot, + DocumentTextPosition, + DocumentTextProjection, + DocumentTextRange, +} from "./types/document-annotation.js"; + +export interface CreateDocumentAnchorSelectorOptions { + contextLength?: number; +} + +export interface VerifyDocumentAnchorSelectorInput { + markdown: string; + selector: DocumentAnnotationAnchorSelector; + contextLength?: number; +} + +export interface VerifyDocumentAnchorSelectorResult { + ok: boolean; + anchor: DocumentAnnotationAnchorSnapshot | null; + projection: DocumentTextProjection; + reason: "verified" | "quote_mismatch" | "position_mismatch" | "invalid_range"; +} + +export interface RemapDocumentAnchorInput { + previousAnchor: DocumentAnnotationAnchorSnapshot; + nextMarkdown: string; + contextLength?: number; +} + +export interface RemapDocumentAnchorResult { + anchorState: DocumentAnnotationAnchorState; + confidence: DocumentAnnotationAnchorConfidence; + anchor: DocumentAnnotationAnchorSnapshot | null; + projection: DocumentTextProjection; + reason: "exact" | "duplicate" | "fuzzy" | "ambiguous" | "missing"; +} + +interface Candidate { + start: number; + end: number; + score: number; + reason: RemapDocumentAnchorResult["reason"]; +} + +const DEFAULT_CONTEXT_LENGTH = 48; + +export function normalizeAnchorText(value: string): string { + return value.replace(/\s+/g, " ").trim(); +} + +export function projectMarkdownToText(markdown: string): DocumentTextProjection { + const builder = new ProjectionBuilder(markdown); + const lines = markdown.match(/[^\n]*(?:\n|$)/g) ?? [markdown]; + let offset = 0; + let inFence = false; + + for (const rawLine of lines) { + if (rawLine === "") continue; + const hasNewline = rawLine.endsWith("\n"); + const line = hasNewline ? rawLine.slice(0, -1) : rawLine; + const fenceMatch = line.match(/^\s*(```+|~~~+)/); + + if (fenceMatch) { + inFence = !inFence; + offset += rawLine.length; + builder.addSeparator(offset - (hasNewline ? 1 : 0)); + continue; + } + + if (inFence) { + builder.addText(line, offset); + builder.addSeparator(offset + line.length); + offset += rawLine.length; + continue; + } + + const { text, sourceOffset } = stripBlockSyntax(line, offset); + addInlineMarkdownText(builder, text, sourceOffset); + builder.addSeparator(offset + line.length); + offset += rawLine.length; + } + + return builder.toProjection(); +} + +export function resolveProjectionRange( + projection: DocumentTextProjection, + normalizedStart: number, + normalizedEnd: number, +): DocumentTextRange | null { + if ( + normalizedStart < 0 + || normalizedEnd <= normalizedStart + || normalizedEnd > projection.text.length + || normalizedStart >= projection.positions.length + || normalizedEnd - 1 >= projection.positions.length + ) { + return null; + } + + return { + text: projection.text.slice(normalizedStart, normalizedEnd), + normalizedStart, + normalizedEnd, + markdownStart: projection.positions[normalizedStart]?.sourceStart ?? 0, + markdownEnd: projection.positions[normalizedEnd - 1]?.sourceEnd ?? 0, + }; +} + +export function createDocumentAnchorSelector( + projection: DocumentTextProjection, + range: DocumentTextRange, + options: CreateDocumentAnchorSelectorOptions = {}, +): DocumentAnnotationAnchorSelector { + const contextLength = options.contextLength ?? DEFAULT_CONTEXT_LENGTH; + return { + quote: { + exact: range.text, + prefix: projection.text.slice(Math.max(0, range.normalizedStart - contextLength), range.normalizedStart), + suffix: projection.text.slice(range.normalizedEnd, range.normalizedEnd + contextLength), + }, + position: { + normalizedStart: range.normalizedStart, + normalizedEnd: range.normalizedEnd, + markdownStart: range.markdownStart, + markdownEnd: range.markdownEnd, + }, + }; +} + +export function selectorToAnchorSnapshot(selector: DocumentAnnotationAnchorSelector): DocumentAnnotationAnchorSnapshot { + return { + selectedText: selector.quote.exact, + prefixText: selector.quote.prefix, + suffixText: selector.quote.suffix, + normalizedStart: selector.position.normalizedStart, + normalizedEnd: selector.position.normalizedEnd, + markdownStart: selector.position.markdownStart, + markdownEnd: selector.position.markdownEnd, + }; +} + +export function anchorSnapshotToSelector(anchor: DocumentAnnotationAnchorSnapshot): DocumentAnnotationAnchorSelector { + return { + quote: { + exact: anchor.selectedText, + prefix: anchor.prefixText, + suffix: anchor.suffixText, + }, + position: { + normalizedStart: anchor.normalizedStart, + normalizedEnd: anchor.normalizedEnd, + markdownStart: anchor.markdownStart, + markdownEnd: anchor.markdownEnd, + }, + }; +} + +export function verifyDocumentAnchorSelector( + input: VerifyDocumentAnchorSelectorInput, +): VerifyDocumentAnchorSelectorResult { + const projection = projectMarkdownToText(input.markdown); + const range = resolveProjectionRange( + projection, + input.selector.position.normalizedStart, + input.selector.position.normalizedEnd, + ); + if (!range) { + return { ok: false, anchor: null, projection, reason: "invalid_range" }; + } + + if (normalizeAnchorText(range.text) !== normalizeAnchorText(input.selector.quote.exact)) { + return { ok: false, anchor: null, projection, reason: "quote_mismatch" }; + } + + if ( + range.markdownStart !== input.selector.position.markdownStart + || range.markdownEnd !== input.selector.position.markdownEnd + ) { + return { ok: false, anchor: null, projection, reason: "position_mismatch" }; + } + + const selector = createDocumentAnchorSelector(projection, range, { + contextLength: input.contextLength ?? DEFAULT_CONTEXT_LENGTH, + }); + return { ok: true, anchor: selectorToAnchorSnapshot(selector), projection, reason: "verified" }; +} + +export function remapDocumentAnchor(input: RemapDocumentAnchorInput): RemapDocumentAnchorResult { + const projection = projectMarkdownToText(input.nextMarkdown); + const contextLength = input.contextLength ?? DEFAULT_CONTEXT_LENGTH; + const quote = normalizeAnchorText(input.previousAnchor.selectedText); + if (!quote) { + return { anchorState: "orphaned", confidence: "missing", anchor: null, projection, reason: "missing" }; + } + + const exactCandidates = findOccurrences(projection.text, quote).map((start) => scoreCandidate({ + projection, + start, + end: start + quote.length, + previousAnchor: input.previousAnchor, + reason: "exact", + contextLength, + })); + + if (exactCandidates.length > 0) { + exactCandidates.sort((a, b) => b.score - a.score); + const [best, second] = exactCandidates; + if (exactCandidates.length > 1 && (!second || Math.abs(best.score - second.score) < 0.05)) { + return { + anchorState: "stale", + confidence: "ambiguous", + anchor: buildAnchorSnapshot(projection, best.start, best.end, contextLength), + projection, + reason: "ambiguous", + }; + } + return { + anchorState: "active", + confidence: exactCandidates.length === 1 ? "exact" : "duplicate", + anchor: buildAnchorSnapshot(projection, best.start, best.end, contextLength), + projection, + reason: exactCandidates.length === 1 ? "exact" : "duplicate", + }; + } + + const fuzzy = findFuzzyCandidate(projection, input.previousAnchor, contextLength); + if (fuzzy && fuzzy.score >= 0.58) { + return { + anchorState: "stale", + confidence: "fuzzy", + anchor: buildAnchorSnapshot(projection, fuzzy.start, fuzzy.end, contextLength), + projection, + reason: "fuzzy", + }; + } + + return { anchorState: "orphaned", confidence: "missing", anchor: null, projection, reason: "missing" }; +} + +function stripBlockSyntax(line: string, absoluteOffset: number): { text: string; sourceOffset: number } { + const blockMatch = line.match(/^\s{0,3}(?:(#{1,6})\s+|(?:[-+*]|\d+[.)])\s+|>\s?)/); + if (!blockMatch) return { text: line, sourceOffset: absoluteOffset }; + return { text: line.slice(blockMatch[0].length), sourceOffset: absoluteOffset + blockMatch[0].length }; +} + +function addInlineMarkdownText(builder: ProjectionBuilder, text: string, sourceOffset: number): void { + for (let index = 0; index < text.length; index += 1) { + const char = text[index] ?? ""; + const absolute = sourceOffset + index; + const rest = text.slice(index); + + const image = rest.match(/^!\[([^\]]*)\]\(([^)]*)\)/); + if (image) { + const altStart = absolute + 2; + builder.addText(image[1] ?? "", altStart); + index += image[0].length - 1; + continue; + } + + const link = rest.match(/^\[([^\]]+)\]\(([^)]*)\)/); + if (link) { + const labelStart = absolute + 1; + builder.addText(link[1] ?? "", labelStart); + index += link[0].length - 1; + continue; + } + + if (char === "`") { + const closing = text.indexOf("`", index + 1); + if (closing > index + 1) { + builder.addText(text.slice(index + 1, closing), absolute + 1); + index = closing; + continue; + } + } + + if (char === "|" || char === "\t") { + builder.addSeparator(absolute); + continue; + } + + if (isMarkdownFormattingChar(char, text, index)) continue; + + builder.addChar(char, absolute, absolute + 1); + } +} + +function isMarkdownFormattingChar(char: string, text: string, index: number): boolean { + if (char === "*" || char === "_" || char === "~") return true; + if (char === "\\" && index + 1 < text.length) return true; + return false; +} + +function findOccurrences(text: string, quote: string): number[] { + const starts: number[] = []; + let start = text.indexOf(quote); + while (start !== -1) { + starts.push(start); + start = text.indexOf(quote, start + 1); + } + return starts; +} + +function scoreCandidate(args: { + projection: DocumentTextProjection; + start: number; + end: number; + previousAnchor: DocumentAnnotationAnchorSnapshot; + reason: Candidate["reason"]; + contextLength: number; +}): Candidate { + const before = args.projection.text.slice(Math.max(0, args.start - args.contextLength), args.start); + const after = args.projection.text.slice(args.end, args.end + args.contextLength); + const prefixScore = suffixOverlapScore(args.previousAnchor.prefixText, before); + const suffixScore = prefixOverlapScore(args.previousAnchor.suffixText, after); + const distance = Math.abs(args.start - args.previousAnchor.normalizedStart); + const proximity = 1 / (1 + distance / 200); + return { + start: args.start, + end: args.end, + score: prefixScore * 0.35 + suffixScore * 0.35 + proximity * 0.3, + reason: args.reason, + }; +} + +function findFuzzyCandidate( + projection: DocumentTextProjection, + previousAnchor: DocumentAnnotationAnchorSnapshot, + contextLength: number, +): Candidate | null { + const words = normalizeAnchorText(previousAnchor.selectedText).split(" ").filter(Boolean); + if (words.length === 0) return null; + const textWords = [...projection.text.matchAll(/\S+/g)].map((match) => ({ + text: match[0], + start: match.index ?? 0, + end: (match.index ?? 0) + match[0].length, + })); + const windowSizes = new Set([words.length - 1, words.length, words.length + 1, words.length + 2].filter((n) => n > 0)); + let best: Candidate | null = null; + + for (const size of windowSizes) { + for (let index = 0; index + size <= textWords.length; index += 1) { + const window = textWords.slice(index, index + size); + const candidateText = window.map((word) => word.text).join(" "); + const similarity = similarityScore(normalizeAnchorText(previousAnchor.selectedText), candidateText); + if (similarity < 0.45) continue; + const scored = scoreCandidate({ + projection, + start: window[0]?.start ?? 0, + end: window[window.length - 1]?.end ?? 0, + previousAnchor, + reason: "fuzzy", + contextLength, + }); + scored.score = scored.score * 0.35 + similarity * 0.65; + if (!best || scored.score > best.score) best = scored; + } + } + + return best; +} + +function buildAnchorSnapshot( + projection: DocumentTextProjection, + normalizedStart: number, + normalizedEnd: number, + contextLength: number, +): DocumentAnnotationAnchorSnapshot { + const range = resolveProjectionRange(projection, normalizedStart, normalizedEnd); + if (!range) { + return { + selectedText: "", + prefixText: "", + suffixText: "", + normalizedStart, + normalizedEnd, + markdownStart: 0, + markdownEnd: 0, + }; + } + const selector = createDocumentAnchorSelector(projection, range, { contextLength }); + return selectorToAnchorSnapshot(selector); +} + +function prefixOverlapScore(expectedPrefix: string, actualPrefix: string): number { + const expected = normalizeAnchorText(expectedPrefix); + const actual = normalizeAnchorText(actualPrefix); + if (!expected) return 0.5; + for (let size = Math.min(expected.length, actual.length); size > 0; size -= 1) { + if (expected.slice(0, size) === actual.slice(0, size)) return size / expected.length; + } + return 0; +} + +function suffixOverlapScore(expectedPrefix: string, actualPrefix: string): number { + const expected = normalizeAnchorText(expectedPrefix); + const actual = normalizeAnchorText(actualPrefix); + if (!expected) return 0.5; + for (let size = Math.min(expected.length, actual.length); size > 0; size -= 1) { + if (expected.slice(-size) === actual.slice(-size)) return size / expected.length; + } + return 0; +} + +function similarityScore(left: string, right: string): number { + if (left === right) return 1; + const leftWords = new Set(left.toLowerCase().split(/\s+/).filter(Boolean)); + const rightWords = new Set(right.toLowerCase().split(/\s+/).filter(Boolean)); + const intersection = [...leftWords].filter((word) => rightWords.has(word)).length; + const union = new Set([...leftWords, ...rightWords]).size || 1; + const jaccard = intersection / union; + const lengthRatio = Math.min(left.length, right.length) / Math.max(left.length, right.length, 1); + return jaccard * 0.75 + lengthRatio * 0.25; +} + +class ProjectionBuilder { + private text = ""; + private positions: DocumentTextPosition[] = []; + private pendingSpace: DocumentTextPosition | null = null; + + constructor(private readonly source: string) {} + + addText(text: string, sourceOffset: number): void { + for (let index = 0; index < text.length; index += 1) { + this.addChar(text[index] ?? "", sourceOffset + index, sourceOffset + index + 1); + } + } + + addSeparator(sourceOffset: number): void { + this.addChar(" ", sourceOffset, sourceOffset + 1); + } + + addChar(char: string, sourceStart: number, sourceEnd: number): void { + if (/\s/.test(char)) { + if (this.text.length > 0 && !this.pendingSpace) { + this.pendingSpace = { sourceStart, sourceEnd }; + } + return; + } + + if (this.pendingSpace && this.text.length > 0) { + this.text += " "; + this.positions.push(this.pendingSpace); + } + this.pendingSpace = null; + this.text += char; + this.positions.push({ sourceStart, sourceEnd }); + } + + toProjection(): DocumentTextProjection { + return { + source: this.source, + text: this.text, + positions: this.positions, + }; + } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 6094e648..597307c4 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -45,6 +45,9 @@ export { SYSTEM_ISSUE_DOCUMENT_KEYS, isSystemIssueDocumentKey, ISSUE_REFERENCE_SOURCE_KINDS, + DOCUMENT_ANNOTATION_THREAD_STATUSES, + DOCUMENT_ANNOTATION_ANCHOR_STATES, + DOCUMENT_ANNOTATION_ANCHOR_CONFIDENCES, ISSUE_EXECUTION_POLICY_MODES, ISSUE_EXECUTION_STAGE_TYPES, ISSUE_MONITOR_SCHEDULED_BY, @@ -164,6 +167,9 @@ export { type IssueTreeHoldStatus, type SystemIssueDocumentKey, type IssueReferenceSourceKind, + type DocumentAnnotationThreadStatus, + type DocumentAnnotationAnchorState, + type DocumentAnnotationAnchorConfidence, type IssueExecutionPolicyMode, type IssueExecutionStageType, type IssueMonitorScheduledBy, @@ -290,6 +296,13 @@ export type { CompanySkillUsageAgent, CompanySkillDetail, CompanySkillUpdateStatus, + CompanySkillAuditSeverity, + CompanySkillAuditVerdict, + CompanySkillUpdateHoldReason, + CompanySkillAuditFinding, + CompanySkillAuditResult, + CompanySkillInstallUpdateRequest, + CompanySkillResetRequest, CompanySkillImportRequest, CompanySkillImportResult, CompanySkillProjectScanRequest, @@ -299,6 +312,14 @@ export type { CompanySkillCreateRequest, CompanySkillFileDetail, CompanySkillFileUpdateRequest, + CatalogSkillKind, + CatalogSkillFileKind, + CatalogSkillFile, + CatalogSkill, + CatalogSkillListQuery, + CatalogSkillFileDetail, + CompanySkillInstallCatalogRequest, + CompanySkillInstallCatalogResult, AgentSkillSyncMode, AgentSkillState, AgentSkillOrigin, @@ -376,6 +397,20 @@ export type { IssueWorkProductProvider, IssueWorkProductStatus, IssueWorkProductReviewState, + CreateDocumentAnnotationCommentRequest, + CreateDocumentAnnotationThreadRequest, + DocumentAnnotationAnchorRemapSnapshot, + DocumentAnnotationAnchorSelector, + DocumentAnnotationAnchorSnapshot, + DocumentAnnotationComment, + DocumentAnnotationTextPositionSelector, + DocumentAnnotationTextQuoteSelector, + DocumentAnnotationThread, + DocumentAnnotationThreadWithComments, + DocumentTextPosition, + DocumentTextProjection, + DocumentTextRange, + UpdateDocumentAnnotationThreadRequest, Issue, IssueAssigneeAdapterOverrides, IssueBlockerAttention, @@ -551,7 +586,6 @@ export type { CompanyPortabilityImportRequest, CompanyPortabilityImportResult, CompanyPortabilityExportRequest, - CompanyPortabilitySecretEntry, EnvBinding, EnvPlainBinding, EnvSecretRefBinding, @@ -655,6 +689,22 @@ export { type IssueReferenceMatch, } from "./issue-references.js"; +export { + anchorSnapshotToSelector, + createDocumentAnchorSelector, + normalizeAnchorText, + projectMarkdownToText, + remapDocumentAnchor, + resolveProjectionRange, + selectorToAnchorSnapshot, + verifyDocumentAnchorSelector, + type CreateDocumentAnchorSelectorOptions, + type RemapDocumentAnchorInput, + type RemapDocumentAnchorResult, + type VerifyDocumentAnchorSelectorInput, + type VerifyDocumentAnchorSelectorResult, +} from "./document-anchors.js"; + export { sidebarOrderPreferenceSchema, upsertSidebarOrderPreferenceSchema, @@ -796,6 +846,18 @@ export { type CreateProjectWorkspace, type UpdateProjectWorkspace, projectExecutionWorkspacePolicySchema, + createDocumentAnnotationCommentSchema, + createDocumentAnnotationThreadSchema, + documentAnnotationAnchorConfidenceSchema, + documentAnnotationAnchorSelectorSchema, + documentAnnotationAnchorStateSchema, + documentAnnotationTextPositionSelectorSchema, + documentAnnotationTextQuoteSelectorSchema, + documentAnnotationThreadStatusSchema, + updateDocumentAnnotationThreadSchema, + type CreateDocumentAnnotationComment, + type CreateDocumentAnnotationThread, + type UpdateDocumentAnnotationThread, companySearchQuerySchema, COMPANY_SEARCH_DEFAULT_LIMIT, COMPANY_SEARCH_MAX_LIMIT, @@ -1013,8 +1075,9 @@ export { companySkillUsageAgentSchema, companySkillDetailSchema, companySkillUpdateStatusSchema, + companySkillAuditFindingSchema, + companySkillAuditResultSchema, companySkillImportSchema, - companySkillUpdateAuthSchema, companySkillProjectScanRequestSchema, companySkillProjectScanSkippedSchema, companySkillProjectScanConflictSchema, @@ -1022,6 +1085,15 @@ export { companySkillCreateSchema, companySkillFileDetailSchema, companySkillFileUpdateSchema, + catalogSkillKindSchema, + catalogSkillFileSchema, + catalogSkillSchema, + catalogSkillListQuerySchema, + catalogSkillFileDetailSchema, + companySkillInstallCatalogSchema, + companySkillInstallCatalogResultSchema, + companySkillInstallUpdateSchema, + companySkillResetSchema, portabilityIncludeSchema, portabilityEnvInputSchema, portabilityCompanyManifestEntrySchema, diff --git a/packages/shared/src/types/company-skill.ts b/packages/shared/src/types/company-skill.ts index 29c3b58a..b244b71c 100644 --- a/packages/shared/src/types/company-skill.ts +++ b/packages/shared/src/types/company-skill.ts @@ -51,6 +51,10 @@ export interface CompanySkillListItem { sourceLabel: string | null; sourceBadge: CompanySkillSourceBadge; sourcePath: string | null; + catalogKind: "bundled" | "optional" | null; + originHash: string | null; + packageName: string | null; + packageVersion: string | null; } export interface CompanySkillUsageAgent { @@ -84,6 +88,49 @@ export interface CompanySkillUpdateStatus { currentRef: string | null; latestRef: string | null; hasUpdate: boolean; + installedHash: string | null; + originHash: string | null; + userModifiedAt: string | null; + updateHoldReason: CompanySkillUpdateHoldReason | null; + auditVerdict: CompanySkillAuditVerdict | null; + auditCodes: string[]; +} + +export type CompanySkillAuditSeverity = "warning" | "error"; + +export type CompanySkillAuditVerdict = "pass" | "warning" | "fail"; + +export type CompanySkillUpdateHoldReason = + | "local_modifications" + | "audit_hard_stop" + | "origin_unavailable" + | "compatibility_invalid" + | "operator_hold"; + +export interface CompanySkillAuditFinding { + code: string; + severity: CompanySkillAuditSeverity; + message: string; + path: string | null; +} + +export interface CompanySkillAuditResult { + skillId: string; + installedHash: string | null; + originHash: string | null; + verdict: CompanySkillAuditVerdict; + codes: string[]; + findings: CompanySkillAuditFinding[]; + scannedAt: string; + scanVersion: string; +} + +export interface CompanySkillInstallUpdateRequest { + force?: boolean; +} + +export interface CompanySkillResetRequest { + force?: boolean; } export interface CompanySkillImportRequest { @@ -155,3 +202,64 @@ export interface CompanySkillFileUpdateRequest { path: string; content: string; } + +export type CatalogSkillKind = "bundled" | "optional"; + +export type CatalogSkillFileKind = CompanySkillFileInventoryEntry["kind"]; + +export interface CatalogSkillFile { + path: string; + kind: CatalogSkillFileKind; + sizeBytes: number; + sha256: string; +} + +export interface CatalogSkill { + id: string; + key: string; + kind: CatalogSkillKind; + category: string; + slug: string; + name: string; + description: string; + path: string; + entrypoint: "SKILL.md"; + trustLevel: CompanySkillTrustLevel; + compatibility: CompanySkillCompatibility; + defaultInstall: boolean; + recommendedForRoles: string[]; + requires: string[]; + tags: string[]; + files: CatalogSkillFile[]; + contentHash: string; + packageName?: string; + packageVersion?: string; +} + +export interface CatalogSkillListQuery { + kind?: CatalogSkillKind; + category?: string; + q?: string; +} + +export interface CatalogSkillFileDetail { + catalogSkillId: string; + path: string; + kind: CatalogSkillFileKind; + content: string; + language: string | null; + markdown: boolean; +} + +export interface CompanySkillInstallCatalogRequest { + catalogSkillId: string; + slug?: string | null; + force?: boolean; +} + +export interface CompanySkillInstallCatalogResult { + action: "created" | "updated" | "unchanged"; + skill: CompanySkill; + catalogSkill: CatalogSkill; + warnings: string[]; +} diff --git a/packages/shared/src/types/document-annotation.ts b/packages/shared/src/types/document-annotation.ts new file mode 100644 index 00000000..e7e36248 --- /dev/null +++ b/packages/shared/src/types/document-annotation.ts @@ -0,0 +1,134 @@ +import type { + DocumentAnnotationAnchorConfidence, + DocumentAnnotationAnchorState, + DocumentAnnotationThreadStatus, + IssueCommentAuthorType, +} from "../constants.js"; + +export interface DocumentTextPosition { + sourceStart: number; + sourceEnd: number; +} + +export interface DocumentTextProjection { + source: string; + text: string; + positions: DocumentTextPosition[]; +} + +export interface DocumentTextRange { + text: string; + normalizedStart: number; + normalizedEnd: number; + markdownStart: number; + markdownEnd: number; +} + +export interface DocumentAnnotationTextQuoteSelector { + exact: string; + prefix: string; + suffix: string; +} + +export interface DocumentAnnotationTextPositionSelector { + normalizedStart: number; + normalizedEnd: number; + markdownStart: number; + markdownEnd: number; +} + +export interface DocumentAnnotationAnchorSelector { + quote: DocumentAnnotationTextQuoteSelector; + position: DocumentAnnotationTextPositionSelector; +} + +export interface DocumentAnnotationAnchorSnapshot { + selectedText: string; + prefixText: string; + suffixText: string; + normalizedStart: number; + normalizedEnd: number; + markdownStart: number; + markdownEnd: number; +} + +export interface DocumentAnnotationThread { + id: string; + companyId: string; + issueId: string; + documentId: string; + documentKey: string; + status: DocumentAnnotationThreadStatus; + anchorState: DocumentAnnotationAnchorState; + anchorConfidence: DocumentAnnotationAnchorConfidence; + originalRevisionId: string | null; + originalRevisionNumber: number; + currentRevisionId: string | null; + currentRevisionNumber: number; + selectedText: string; + prefixText: string; + suffixText: string; + normalizedStart: number; + normalizedEnd: number; + markdownStart: number; + markdownEnd: number; + anchorSelector: DocumentAnnotationAnchorSelector; + createdByAgentId: string | null; + createdByUserId: string | null; + resolvedByAgentId: string | null; + resolvedByUserId: string | null; + resolvedAt: Date | null; + createdAt: Date; + updatedAt: Date; +} + +export interface DocumentAnnotationComment { + id: string; + companyId: string; + threadId: string; + issueId: string; + documentId: string; + body: string; + authorType: IssueCommentAuthorType; + authorAgentId: string | null; + authorUserId: string | null; + createdByRunId: string | null; + createdAt: Date; + updatedAt: Date; +} + +export interface DocumentAnnotationAnchorRemapSnapshot { + id: string; + companyId: string; + threadId: string; + documentId: string; + fromRevisionId: string | null; + fromRevisionNumber: number | null; + toRevisionId: string | null; + toRevisionNumber: number; + previousAnchor: DocumentAnnotationAnchorSnapshot; + nextAnchor: DocumentAnnotationAnchorSnapshot | null; + anchorState: DocumentAnnotationAnchorState; + anchorConfidence: DocumentAnnotationAnchorConfidence; + failureReason: string | null; + createdAt: Date; +} + +export interface DocumentAnnotationThreadWithComments extends DocumentAnnotationThread { + comments: DocumentAnnotationComment[]; +} + +export interface CreateDocumentAnnotationThreadRequest { + baseRevisionId: string; + baseRevisionNumber: number; + selector: DocumentAnnotationAnchorSelector; + body: string; +} + +export interface CreateDocumentAnnotationCommentRequest { + body: string; +} + +export interface UpdateDocumentAnnotationThreadRequest { + status?: DocumentAnnotationThreadStatus; +} diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 52cbb7c5..868f3afa 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -51,6 +51,13 @@ export type { CompanySkillUsageAgent, CompanySkillDetail, CompanySkillUpdateStatus, + CompanySkillAuditSeverity, + CompanySkillAuditVerdict, + CompanySkillUpdateHoldReason, + CompanySkillAuditFinding, + CompanySkillAuditResult, + CompanySkillInstallUpdateRequest, + CompanySkillResetRequest, CompanySkillImportRequest, CompanySkillImportResult, CompanySkillProjectScanRequest, @@ -60,6 +67,14 @@ export type { CompanySkillCreateRequest, CompanySkillFileDetail, CompanySkillFileUpdateRequest, + CatalogSkillKind, + CatalogSkillFileKind, + CatalogSkillFile, + CatalogSkill, + CatalogSkillListQuery, + CatalogSkillFileDetail, + CompanySkillInstallCatalogRequest, + CompanySkillInstallCatalogResult, } from "./company-skill.js"; export type { AgentSkillSyncMode, @@ -89,6 +104,22 @@ export type { AdapterEnvironmentTestResult, } from "./agent.js"; export type { AssetImage } from "./asset.js"; +export type { + CreateDocumentAnnotationCommentRequest, + CreateDocumentAnnotationThreadRequest, + DocumentAnnotationAnchorRemapSnapshot, + DocumentAnnotationAnchorSelector, + DocumentAnnotationAnchorSnapshot, + DocumentAnnotationComment, + DocumentAnnotationTextPositionSelector, + DocumentAnnotationTextQuoteSelector, + DocumentAnnotationThread, + DocumentAnnotationThreadWithComments, + DocumentTextPosition, + DocumentTextProjection, + DocumentTextRange, + UpdateDocumentAnnotationThreadRequest, +} from "./document-annotation.js"; export type { Project, ProjectCodebase, ProjectCodebaseOrigin, ProjectGoalRef, ProjectManagedByPlugin, ProjectWorkspace } from "./project.js"; export type { CompanySearchHighlight, @@ -386,7 +417,6 @@ export type { CompanyPortabilityImportRequest, CompanyPortabilityImportResult, CompanyPortabilityExportRequest, - CompanyPortabilitySecretEntry, } from "./company-portability.js"; export type { JsonSchema, diff --git a/packages/shared/src/validators/company-skill.ts b/packages/shared/src/validators/company-skill.ts index c81999a3..2d813ec0 100644 --- a/packages/shared/src/validators/company-skill.ts +++ b/packages/shared/src/validators/company-skill.ts @@ -35,6 +35,10 @@ export const companySkillListItemSchema = companySkillSchema.extend({ editableReason: z.string().nullable(), sourceLabel: z.string().nullable(), sourceBadge: companySkillSourceBadgeSchema, + catalogKind: z.enum(["bundled", "optional"]).nullable(), + originHash: z.string().nullable(), + packageName: z.string().nullable(), + packageVersion: z.string().nullable(), }); export const companySkillUsageAgentSchema = z.object({ @@ -64,15 +68,48 @@ export const companySkillUpdateStatusSchema = z.object({ currentRef: z.string().nullable(), latestRef: z.string().nullable(), hasUpdate: z.boolean(), + installedHash: z.string().nullable(), + originHash: z.string().nullable(), + userModifiedAt: z.string().nullable(), + updateHoldReason: z.enum([ + "local_modifications", + "audit_hard_stop", + "origin_unavailable", + "compatibility_invalid", + "operator_hold", + ]).nullable(), + auditVerdict: z.enum(["pass", "warning", "fail"]).nullable(), + auditCodes: z.array(z.string()), }); +export const companySkillAuditFindingSchema = z.object({ + code: z.string().min(1), + severity: z.enum(["warning", "error"]), + message: z.string().min(1), + path: z.string().nullable(), +}); + +export const companySkillAuditResultSchema = z.object({ + skillId: z.string().uuid(), + installedHash: z.string().nullable(), + originHash: z.string().nullable(), + verdict: z.enum(["pass", "warning", "fail"]), + codes: z.array(z.string()), + findings: z.array(companySkillAuditFindingSchema), + scannedAt: z.string().min(1), + scanVersion: z.string().min(1), +}); + +export const companySkillInstallUpdateSchema = z.object({ + force: z.boolean().optional(), +}).default({}); + +export const companySkillResetSchema = z.object({ + force: z.boolean().optional(), +}).default({}); + export const companySkillImportSchema = z.object({ source: z.string().min(1), - authToken: z.string().min(1).optional(), -}); - -export const companySkillUpdateAuthSchema = z.object({ - authToken: z.string().min(1).nullable(), }); export const companySkillProjectScanRequestSchema = z.object({ @@ -136,8 +173,70 @@ export const companySkillFileUpdateSchema = z.object({ content: z.string(), }); +export const catalogSkillKindSchema = z.enum(["bundled", "optional"]); + +export const catalogSkillFileSchema = z.object({ + path: z.string().min(1), + kind: z.enum(["skill", "markdown", "reference", "script", "asset", "other"]), + sizeBytes: z.number().int().nonnegative(), + sha256: z.string().min(1), +}); + +export const catalogSkillSchema = z.object({ + id: z.string().min(1), + key: z.string().min(1), + kind: catalogSkillKindSchema, + category: z.string().min(1), + slug: z.string().min(1), + name: z.string().min(1), + description: z.string(), + path: z.string().min(1), + entrypoint: z.literal("SKILL.md"), + trustLevel: companySkillTrustLevelSchema, + compatibility: companySkillCompatibilitySchema, + defaultInstall: z.boolean(), + recommendedForRoles: z.array(z.string()), + requires: z.array(z.string()), + tags: z.array(z.string()), + files: z.array(catalogSkillFileSchema), + contentHash: z.string().min(1), + packageName: z.string().min(1).optional(), + packageVersion: z.string().min(1).optional(), +}); + +export const catalogSkillListQuerySchema = z.object({ + kind: catalogSkillKindSchema.optional(), + category: z.string().min(1).optional(), + q: z.string().min(1).optional(), +}); + +export const catalogSkillFileDetailSchema = z.object({ + catalogSkillId: z.string().min(1), + path: z.string().min(1), + kind: z.enum(["skill", "markdown", "reference", "script", "asset", "other"]), + content: z.string(), + language: z.string().nullable(), + markdown: z.boolean(), +}); + +export const companySkillInstallCatalogSchema = z.object({ + catalogSkillId: z.string().min(1), + slug: z.string().min(1).nullable().optional(), + force: z.boolean().optional(), +}); + +export const companySkillInstallCatalogResultSchema = z.object({ + action: z.enum(["created", "updated", "unchanged"]), + skill: companySkillSchema, + catalogSkill: catalogSkillSchema, + warnings: z.array(z.string()), +}); + export type CompanySkillImport = z.infer; export type CompanySkillProjectScan = z.infer; export type CompanySkillCreate = z.infer; export type CompanySkillFileUpdate = z.infer; -export type CompanySkillUpdateAuth = z.infer; +export type CatalogSkillListQuery = z.infer; +export type CompanySkillInstallCatalog = z.infer; +export type CompanySkillInstallUpdate = z.infer; +export type CompanySkillReset = z.infer; diff --git a/packages/shared/src/validators/document-annotation.ts b/packages/shared/src/validators/document-annotation.ts new file mode 100644 index 00000000..f8194240 --- /dev/null +++ b/packages/shared/src/validators/document-annotation.ts @@ -0,0 +1,65 @@ +import { z } from "zod"; +import { + DOCUMENT_ANNOTATION_ANCHOR_CONFIDENCES, + DOCUMENT_ANNOTATION_ANCHOR_STATES, + DOCUMENT_ANNOTATION_THREAD_STATUSES, +} from "../constants.js"; +import { multilineTextSchema } from "./text.js"; + +export const documentAnnotationThreadStatusSchema = z.enum(DOCUMENT_ANNOTATION_THREAD_STATUSES); +export const documentAnnotationAnchorStateSchema = z.enum(DOCUMENT_ANNOTATION_ANCHOR_STATES); +export const documentAnnotationAnchorConfidenceSchema = z.enum(DOCUMENT_ANNOTATION_ANCHOR_CONFIDENCES); + +export const documentAnnotationTextQuoteSelectorSchema = z.object({ + exact: z.string().min(1).max(10_000), + prefix: z.string().max(1_000).default(""), + suffix: z.string().max(1_000).default(""), +}).strict(); + +export const documentAnnotationTextPositionSelectorSchema = z.object({ + normalizedStart: z.number().int().nonnegative(), + normalizedEnd: z.number().int().nonnegative(), + markdownStart: z.number().int().nonnegative(), + markdownEnd: z.number().int().nonnegative(), +}).strict().superRefine((value, ctx) => { + if (value.normalizedEnd <= value.normalizedStart) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "normalizedEnd must be greater than normalizedStart", + path: ["normalizedEnd"], + }); + } + if (value.markdownEnd <= value.markdownStart) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "markdownEnd must be greater than markdownStart", + path: ["markdownEnd"], + }); + } +}); + +export const documentAnnotationAnchorSelectorSchema = z.object({ + quote: documentAnnotationTextQuoteSelectorSchema, + position: documentAnnotationTextPositionSelectorSchema, +}).strict(); + +export const createDocumentAnnotationThreadSchema = z.object({ + baseRevisionId: z.string().uuid(), + baseRevisionNumber: z.number().int().positive(), + selector: documentAnnotationAnchorSelectorSchema, + body: multilineTextSchema.pipe(z.string().min(1).max(20_000)), +}).strict(); + +export const createDocumentAnnotationCommentSchema = z.object({ + body: multilineTextSchema.pipe(z.string().min(1).max(20_000)), +}).strict(); + +export const updateDocumentAnnotationThreadSchema = z.object({ + status: documentAnnotationThreadStatusSchema.optional(), +}).strict().refine((value) => value.status != null, { + message: "At least one field must be provided", +}); + +export type CreateDocumentAnnotationThread = z.infer; +export type CreateDocumentAnnotationComment = z.infer; +export type UpdateDocumentAnnotationThread = z.infer; diff --git a/packages/shared/src/validators/index.ts b/packages/shared/src/validators/index.ts index 676b51ed..d85be593 100644 --- a/packages/shared/src/validators/index.ts +++ b/packages/shared/src/validators/index.ts @@ -67,8 +67,9 @@ export { companySkillUsageAgentSchema, companySkillDetailSchema, companySkillUpdateStatusSchema, + companySkillAuditFindingSchema, + companySkillAuditResultSchema, companySkillImportSchema, - companySkillUpdateAuthSchema, companySkillProjectScanRequestSchema, companySkillProjectScanSkippedSchema, companySkillProjectScanConflictSchema, @@ -76,11 +77,23 @@ export { companySkillCreateSchema, companySkillFileDetailSchema, companySkillFileUpdateSchema, + catalogSkillKindSchema, + catalogSkillFileSchema, + catalogSkillSchema, + catalogSkillListQuerySchema, + catalogSkillFileDetailSchema, + companySkillInstallCatalogSchema, + companySkillInstallCatalogResultSchema, + companySkillInstallUpdateSchema, + companySkillResetSchema, type CompanySkillImport, type CompanySkillProjectScan, type CompanySkillCreate, type CompanySkillFileUpdate, - type CompanySkillUpdateAuth, + type CatalogSkillListQuery, + type CompanySkillInstallCatalog, + type CompanySkillInstallUpdate, + type CompanySkillReset, } from "./company-skill.js"; export { agentSkillStateSchema, @@ -154,6 +167,21 @@ export { type ProjectExecutionWorkspacePolicy, } from "./project.js"; +export { + createDocumentAnnotationCommentSchema, + createDocumentAnnotationThreadSchema, + documentAnnotationAnchorConfidenceSchema, + documentAnnotationAnchorSelectorSchema, + documentAnnotationAnchorStateSchema, + documentAnnotationTextPositionSelectorSchema, + documentAnnotationTextQuoteSelectorSchema, + documentAnnotationThreadStatusSchema, + updateDocumentAnnotationThreadSchema, + type CreateDocumentAnnotationComment, + type CreateDocumentAnnotationThread, + type UpdateDocumentAnnotationThread, +} from "./document-annotation.js"; + export { createIssueSchema, createIssueInputSchema, diff --git a/packages/skills-catalog/catalog/bundled/docs/doc-maintenance/SKILL.md b/packages/skills-catalog/catalog/bundled/docs/doc-maintenance/SKILL.md new file mode 100644 index 00000000..6aaf8831 --- /dev/null +++ b/packages/skills-catalog/catalog/bundled/docs/doc-maintenance/SKILL.md @@ -0,0 +1,75 @@ +--- +name: doc-maintenance +description: Keep project docs aligned with recent code and feature changes — detect drift, update affected pages, and add release-relevant notes without rewriting unchanged sections. +key: paperclipai/bundled/docs/doc-maintenance +recommendedForRoles: + - engineer + - product + - devrel +tags: + - docs + - documentation + - release-notes +--- + +# Doc Maintenance + +Keep the documentation honest with minimum churn. The goal is alignment between docs and behavior, not stylistic rewrites or cosmetic re-organization. Reviewers should be able to read a diff and see "this updates docs to match recent behavior changes". + +## When to use + +- A PR or recent set of merges changed user-visible behavior: CLI flags, API shapes, default values, configuration keys, endpoints, environment variables, supported versions. +- A user-reported bug traced back to outdated documentation. +- A release is being cut and the docs need a pass against the merged commits. +- A new feature shipped but only the engineer's PR description describes how to use it. + +## When not to use + +- The change is internal-only (private helper rename, refactor) with no user-visible impact. +- You want to "improve the docs" without a behavior anchor. That is a separate scoped project, not maintenance — make a plan first. + +## The pass + +1. **Establish the baseline.** Get the commit range you are documenting against (since last release tag, since last merged-doc commit, or since a specific PR). +2. **Enumerate user-visible changes.** Read commits and PR descriptions. List, for each change, what a user can now do differently. +3. **Map changes to docs.** For each change, find every page that mentions the affected concept. Common targets: README, CLI reference, API reference, configuration reference, migration guide, FAQ, examples. +4. **Update precisely.** Edit only the lines that need to change. Do not rewrap paragraphs you did not modify — it pollutes the diff. +5. **Add new entries where needed.** New CLI flag → CLI reference entry. New env var → configuration reference entry. New endpoint → API reference entry. Don't only add it to the changelog. +6. **Update examples and snippets.** Code blocks in docs are wrong faster than prose. Re-run any example that touches new behavior. +7. **Write the release note.** One sentence per user-visible change. Group by Added / Changed / Fixed / Deprecated / Removed. Link to the relevant PRs and docs section. +8. **Cross-check.** Search the docs for the old behavior wording and remove or update stragglers. + +## Style baseline + +- Voice: second person ("you can pass `--json` to ..."). Avoid "we" except in narrative pages. +- Tense: present, not future. The behavior exists once shipped. +- Headings: imperative ("Configure the cache") or noun-phrase ("Cache configuration"), match the surrounding page. +- Code blocks: include the language tag so syntax highlighting works. +- Cross-links: link the first mention of a concept on each page; do not link every occurrence. +- Avoid promising future behavior. If something is unreleased, mark it `experimental` or omit it. + +## Drift detection + +A doc page is drifting if any of these are true: + +- It documents a flag, key, or endpoint that no longer exists. +- An example does not run as written. +- A default value in the docs does not match the code. +- A supported-versions list excludes a version the project actually supports, or includes one it dropped. +- A "Coming soon" section references a feature that shipped or was cancelled. + +When you find drift, fix it in the same pass and note it in the release note's `Fixed` group. + +## Release-note rules + +- One sentence per item. If two sentences are needed, the item is likely two items. +- User impact first, internal cause second. `Faster cold start (avoid full bundle download on first run)` beats `Refactor bootstrap loader`. +- Link the PR for engineering readers and the docs page for users. +- Mark breaking changes explicitly: `**Breaking:**` prefix. Include migration steps inline or via link. + +## Anti-patterns + +- Massive doc PRs that bundle stylistic rewrites with real updates. Reviewers cannot tell which lines reflect actual behavior changes. +- "Updated docs" commit messages with no detail. Make the commit say what changed and why. +- Adding to the changelog without updating the reference docs the changelog points to. +- Marking a feature as available before its code lands. Documentation must follow behavior, not promise it. diff --git a/packages/skills-catalog/catalog/bundled/paperclip-operations/issue-triage/SKILL.md b/packages/skills-catalog/catalog/bundled/paperclip-operations/issue-triage/SKILL.md new file mode 100644 index 00000000..281c4293 --- /dev/null +++ b/packages/skills-catalog/catalog/bundled/paperclip-operations/issue-triage/SKILL.md @@ -0,0 +1,74 @@ +--- +name: issue-triage +description: Triage Paperclip inbox issues that are stale, blocked, in-review, or assigned-but-not-progressing, and decide a single next action per issue (resume, reassign, unblock, escalate, or close). +key: paperclipai/bundled/paperclip-operations/issue-triage +recommendedForRoles: + - manager + - ceo + - engineer +tags: + - paperclip + - triage + - inbox + - workflow +--- + +# Issue Triage + +Convert a noisy inbox into a small set of clear next actions. Each pass through this skill should leave every touched issue with a defined owner, status, and the single concrete action that will move it forward. + +## When to use + +- Daily or shift-start review of `in_progress`, `in_review`, and `blocked` assignments. +- An inbox has many open assignments and no clear priority. +- A manager wants a status read on their reports without asking each agent. +- You are woken by a comment that suggests an old issue stalled. + +## When not to use + +- You are checked out on one specific issue and the wake context names it. Work that issue, do not triage the whole inbox. +- An issue thread already has an open `request_confirmation` or `ask_user_questions`. Wait for the response — re-triage is noise. + +## Inputs + +- `GET /api/agents/me/inbox-lite` for the compact assignment list. +- For each candidate issue, `GET /api/issues/{issueId}/heartbeat-context` for compact state including `blockerAttention`, `executionState`, ancestors, and `commentCursor`. +- Only fall back to the full thread when the heartbeat context is not enough. + +## Per-issue triage decision + +For each issue, classify into exactly one of: + +1. **Resume** — execution path is alive. Confirm the assignee is set and let the heartbeat continue. Do not comment. +2. **Wake-needed** — assignee is stalled with no live continuation. Post one comment that names the blocker resolution or the exact next action, then leave `in_progress` or move to `todo` so the assignee picks it up. +3. **Reassign** — the assignee is not the right specialty. Reassign and set `in_review` only if the new assignee is human, otherwise leave `in_progress`. +4. **Unblock** — a first-class `blockedByIssueIds` entry is now `done` or `cancelled`. If `cancelled`, replace or remove it from `blockedByIssueIds`. The blockers-resolved wake will fire automatically when all are `done`. +5. **Escalate** — the issue needs board, CTO, or user input. Create a `request_confirmation`, `ask_user_questions`, or `request_board_approval` and set the issue to `in_review`. +6. **Close** — work is complete, duplicate, or no longer relevant. Set `done` or `cancelled` with a one-line reason. + +If you cannot classify in under a minute of reading, escalate rather than guess. + +## Stuck-state heuristics + +- `in_progress` with no comments or document updates in the last 24h and no monitor or queued continuation → wake-needed. +- `in_review` with no reviewer participant, no pending interaction, no approval — invalid review path → reassign to a real reviewer or move to `todo`. +- `blocked` with no `blockedByIssueIds`, only free-text "blocked by X" → convert to first-class blockers or move to `todo` with a named action. +- `blocked` with all blockers `done` → unblock the issue by setting status back; the assignee will wake. +- Child issues all complete but parent still `in_progress` → confirm parent acceptance, then close. + +## Don't-do list + +- Do not @-mention agents during triage; mentions cost budget. Use direct reassignment instead. +- Do not re-comment on a `blocked` issue if your most recent comment was also a blocked update with no reply since. +- Do not cancel cross-team issues. Reassign to the responsible manager with a comment. +- Do not change status without a comment that explains the change. + +## Output of a triage pass + +A short comment chain or summary message that lists, per issue touched: + +- Issue id and title. +- Verdict (resume / wake-needed / reassign / unblock / escalate / close). +- The one action you took or asked for. + +This is the bar for "the triage is done." diff --git a/packages/skills-catalog/catalog/bundled/paperclip-operations/task-planning/SKILL.md b/packages/skills-catalog/catalog/bundled/paperclip-operations/task-planning/SKILL.md new file mode 100644 index 00000000..5304322f --- /dev/null +++ b/packages/skills-catalog/catalog/bundled/paperclip-operations/task-planning/SKILL.md @@ -0,0 +1,84 @@ +--- +name: task-planning +description: Turn a Paperclip issue or request into a structured implementation plan with child task graph, blockers, owners, and acceptance criteria, then save it as the issue `plan` document. +key: paperclipai/bundled/paperclip-operations/task-planning +recommendedForRoles: + - manager + - engineer + - product +tags: + - paperclip + - planning + - issues + - delegation +--- + +# Task Planning + +Produce implementation plans that the Paperclip executor can actually run: explicit child issues, real blockers, named owners, and a defined acceptance bar. Avoid plans that read well but cannot be split into work. + +## When to use + +- An issue asks you to "plan", "scope", "break down", "design the rollout", "propose the work", or similar. +- A user wants a written plan before approving implementation. +- A manager needs to delegate non-trivial work and the shape of the work is not obvious yet. +- You inherited an issue too large to deliver in one heartbeat and need to split it. + +## When not to use + +- The issue is a single small change you can ship in the same heartbeat. Just ship it. +- The issue is forensic ("why did this break"). Use a diagnosis skill first; plan only after the root cause is named. +- A current `plan` document already exists and the change is minor. Update that document; do not start fresh. + +## Outputs + +1. An updated issue document with key `plan` (markdown). +2. A short comment on the issue that links to the plan document and names the next action. +3. Where the plan requires approval, an issue-thread interaction of kind `request_confirmation` bound to the latest plan revision. + +Do not create implementation subtasks until the plan is accepted. + +## Plan structure + +Required sections, in order: + +1. **Goal** — one paragraph. What changes for the user, the operator, or the system once this work lands. +2. **Context reviewed** — bullet list of documents, files, and prior issues you read. Lets reviewers spot missing inputs. +3. **Constraints and non-goals** — what must hold (compatibility, security, performance) and what this plan deliberately will not do. +4. **Approach** — the chosen path, with a short rationale. If you considered alternatives, name them and why you rejected them. +5. **Work breakdown** — ordered list of child issues. Each child has: + - Title in imperative form. + - Owner specialty (Engineer, QA, Designer, Security, DevRel, Manager, etc.). + - Scope and deliverables. + - Acceptance criteria. + - Blocks/blocked-by relationships expressed by phase letter or child title. +6. **Acceptance** — the bar for the parent issue. How the user knows the whole thing is done. +7. **Risks and mitigations** — short list. Skip if there are none. +8. **Deferrals** — what is intentionally pushed to follow-up issues, with why. + +## Rules of thumb for splitting + +- One child issue, one specialty. If two specialties have to coordinate inside the same issue, split it. +- One child issue, one acceptance verdict. If a reviewer would say "this is half done", split it. +- A child must be checkout-able by the owner from its title and description alone. Reviewers should not have to re-read the parent plan to understand a child. +- Order children by real blocker chains, not by author preference. Parallel children should explicitly say `blockers: none`. +- Avoid `polish` or `cleanup` child issues without acceptance criteria — they never close. + +## Filing the plan + +Use the Paperclip API to write the plan document, then comment: + +- `PUT /api/issues/{issueId}/documents/plan` with the markdown body. If `plan` already exists, include the latest `baseRevisionId`. +- `POST /api/issues/{issueId}/comments` with a short summary that links the plan: `//issues/#document-plan`. +- If approval is required: `POST /api/issues/{issueId}/interactions` with `kind: request_confirmation`, `targetRevisionId` set to the new plan revision, `continuationPolicy: wake_assignee`, and `idempotencyKey: "confirmation:{issueId}:plan:{revisionId}"`. +- Set the issue to `in_review` after creating the confirmation. Stay assigned so the acceptance wakes the planner. + +When the plan is accepted, see the companion skill for converting accepted plans into Paperclip executable tasks. + +## Anti-patterns + +- Plan disguised as a description edit. Use the `plan` document. +- "Phases A–Z" with no work breakdown inside the phases. +- Children with descriptions that say "see parent" — they fail at delegation time. +- Acceptance written as "code review approval". Reviewers need a behavior bar, not a process bar. +- Plans that bury blocker chains in prose. Use explicit blocked-by lines. diff --git a/packages/skills-catalog/catalog/bundled/quality/qa-acceptance/SKILL.md b/packages/skills-catalog/catalog/bundled/quality/qa-acceptance/SKILL.md new file mode 100644 index 00000000..5adec874 --- /dev/null +++ b/packages/skills-catalog/catalog/bundled/quality/qa-acceptance/SKILL.md @@ -0,0 +1,93 @@ +--- +name: qa-acceptance +description: Produce QA acceptance criteria and a manual validation plan for a feature change — golden path, edge cases, error states, performance limits, and explicit pass/fail evidence. +key: paperclipai/bundled/quality/qa-acceptance +recommendedForRoles: + - qa + - engineer + - product +tags: + - qa + - acceptance + - validation + - testing +--- + +# QA Acceptance + +Write acceptance criteria that a reviewer can run against the running app and decide pass or fail without asking the author. The criteria are the contract — automated tests cover correctness, QA covers feature-level behavior. + +## When to use + +- A feature change is heading to QA and needs a written validation plan. +- A reviewer is asked to verify a PR that touches user-visible behavior. +- An incident postmortem requires a regression check before reopen-prevention. +- A release candidate needs a pre-cut smoke pass. + +## When not to use + +- The change is unit-test-only (utility refactor, internal naming). Acceptance criteria are unnecessary churn. +- You are asked to write tests against API contracts. Use contract testing, not feature QA. + +## Acceptance criteria format + +Each criterion is a single, independently-verifiable statement: + +```md +- **Given** , **when** , **then** . +``` + +Example: + +```md +- **Given** a CSV export with 0 rows, **when** the user clicks Export, **then** the file downloads with only the header row and the UI shows "Exported 0 rows". +``` + +Avoid criteria that combine multiple `when`s or `then`s. Split them. + +## What every plan must cover + +1. **Golden path.** The most common successful flow, end to end. +2. **Empty and minimum states.** Zero items, one item, missing optional inputs. +3. **Boundary inputs.** Max length strings, max numeric values, unicode, RTL text where applicable. +4. **Error states.** Network failure, permission denied, validation failures, conflict (409), not found (404). +5. **Concurrency and ordering.** Two users acting at once, race against background jobs, refresh during mutation. +6. **Performance envelope.** The largest realistic input the change must handle without UI hangs or timeouts. +7. **Backward compatibility.** Existing data, existing URLs, persisted user preferences continue to work. +8. **Telemetry and audit.** Events, logs, or activity entries the change is supposed to emit. + +If a section is genuinely not applicable, write "N/A: " — do not silently omit. + +## Evidence + +Each criterion needs evidence on the verification pass: + +- Screenshot or short clip for UI behavior. +- Copied console / network output for API behavior. +- Log snippet or activity row for telemetry. +- Timing measurement for performance criteria. + +"Looks good to me" without evidence is not a pass. + +## Quarantine and follow-up + +- A failing criterion blocks acceptance unless explicitly waived by the owner with a tracked follow-up issue. +- "Known issue" without a linked follow-up is not a waiver. +- If you add a new criterion mid-pass, restart the pass — partial coverage hides regressions. + +## Handoff back to the author + +Return the validation plan with three sections: + +- **Pass.** Criteria that passed, with one-line evidence summaries. +- **Fail.** Criteria that failed, with the exact reproduction. +- **Blocked.** Criteria you could not run, with why. + +The author owns turning failures into either fixes or accepted deferrals. + +## Anti-patterns + +- Acceptance phrased as test plan ("write a Cypress test for X"). Acceptance is what is true after the change ships; tests are how you check. +- Criteria that depend on inspecting implementation details (selectors, query plans). Stay observable. +- Long checklists with no priority. Mark must-pass criteria distinctly from nice-to-have. +- Validation reports that say "passed" with no evidence. Reviewers cannot audit those. diff --git a/packages/skills-catalog/catalog/bundled/software-development/github-pr-workflow/SKILL.md b/packages/skills-catalog/catalog/bundled/software-development/github-pr-workflow/SKILL.md new file mode 100644 index 00000000..6ce844f1 --- /dev/null +++ b/packages/skills-catalog/catalog/bundled/software-development/github-pr-workflow/SKILL.md @@ -0,0 +1,93 @@ +--- +name: github-pr-workflow +description: Prepare a GitHub pull request from a feature branch — branch hygiene, commit shape, title/body, verification notes, screenshots for UI work, and replies to review comments. +key: paperclipai/bundled/software-development/github-pr-workflow +recommendedForRoles: + - engineer +tags: + - github + - pull-requests + - code-review + - release +--- + +# GitHub Pull Request Workflow + +Ship a PR a reviewer can land without follow-up clarifying questions. The aim is high signal in the title and body, evidence the change works, and clean replies when feedback comes in. + +## When to use + +- You are about to open a PR for a change that is functionally complete. +- A reviewer left comments and you need to respond and push fixes. +- A PR has been open more than a day and needs to be brought back into shape (stale conflicts, missing description, missing verification). + +## When not to use + +- The change is not yet functionally complete. Finish the work first; draft PRs that bounce on review are noise. +- The repository uses a non-GitHub forge. Adjust to that forge's conventions; do not force GitHub-isms. + +## Branch hygiene before opening + +- Rebase or merge from the target base so the diff is current. +- Squash WIP commits into reviewable units. Prefer one commit per logical change; do not force one-commit-per-PR if the work is genuinely multi-step. +- Confirm tests, typecheck, and lint pass locally. Note any deliberate skips in the PR body. +- Remove debug prints, commented-out code, and `TODO` markers that are not tracked. + +## PR title + +- Imperative mood, under 70 characters. +- Lead with the user-visible change, not the file touched. `Allow CSV export from reports table` beats `Update reports.tsx`. +- If the repo uses an issue prefix convention (`PAP-1234:`, `[security]`), follow it. +- No trailing period. + +## PR body + +Use this structure: + +```md +## Summary +- 1–3 bullets describing what changed and why. + +## Implementation notes +- Anything non-obvious in the diff: trade-offs, dropped alternatives, gotchas. +- Migration or config implications. + +## Verification +- The exact commands or steps you ran. +- Screenshots or short clips for UI changes (required if pixels moved). +- Edge cases you exercised by hand. + +## Risk and rollback +- What breaks if this is reverted, and how to revert cleanly. +``` + +Skip the `Risk and rollback` section only for clearly trivial PRs (typos, docs). + +## Verification evidence + +- Tests passing in CI is necessary, not sufficient. Reviewers also need to know the change behaves correctly end to end. +- For UI work, include screenshots of the golden path and one edge case. Tag dark and light mode if the project supports both. +- For migrations, include a dry-run plan and reversal steps. +- For performance changes, include a before/after measurement, not adjectives. + +## Replying to review comments + +- Reply on every comment, even with just "fixed in " — silent fixes leave the reviewer guessing. +- Push fixes as new commits while review is active; do not amend during review unless the reviewer agrees. +- If you disagree with feedback, say so with one sentence of rationale and let the reviewer decide. Don't escalate over comments. +- Re-request review explicitly after pushing changes. + +## Merge checklist + +- All required checks green. +- All review comments resolved. +- PR title/body still accurate (update if scope changed mid-review). +- Linked issue moves to `in_review` or `done` per project convention. +- Delete the branch after merge unless it is a long-lived integration branch. + +## Anti-patterns + +- PR description that says "see commits". Reviewers should not need to read the log. +- Mixing refactor and behavior change in the same PR with no separation in the body. +- "Address feedback" commits that bundle unrelated edits. One commit per round of feedback is fine; one commit for everything in flight is not. +- Force-pushing during active review without telling the reviewer. diff --git a/packages/skills-catalog/catalog/optional/browser/agent-browser/SKILL.md b/packages/skills-catalog/catalog/optional/browser/agent-browser/SKILL.md new file mode 100644 index 00000000..105b1f56 --- /dev/null +++ b/packages/skills-catalog/catalog/optional/browser/agent-browser/SKILL.md @@ -0,0 +1,93 @@ +--- +name: agent-browser +description: Drive a real browser to inspect or interact with a web page or app — navigate, take screenshots, read console and network, fill simple forms — for verification tasks, not unattended automation. +key: paperclipai/optional/browser/agent-browser +recommendedForRoles: + - qa + - engineer + - researcher +tags: + - browser + - puppeteer + - playwright + - verification +--- + +# Agent Browser + +Use a controlled browser to verify behavior, capture evidence, or extract information from web pages that a static fetch cannot reach (SPAs, login-gated pages, dynamic content). This skill is about supervised verification, not unattended scraping. + +## When to use + +- You need a screenshot of a deployed page or a local dev server to confirm a UI change. +- You need to read JavaScript-rendered content that `curl`/`wget` will not see. +- A user reports a UI bug and you need to reproduce it interactively to capture console errors, network requests, or layout state. +- You need to walk through a short flow (load page, click, observe) to verify acceptance criteria. + +## When not to use + +- The page is reachable as static HTML. Use `curl`/HTTP fetch — it is cheaper, faster, and more reliable. +- The task is unattended large-scale scraping. That belongs to a dedicated scraper with rate limits, robots.txt handling, and a real user agent policy — not this skill. +- The site is behind authentication you do not own credentials for, or whose terms of service prohibit automation. +- The site involves sensitive accounts (banking, healthcare, government) where automation risks lockout or compliance issues. + +## Before launching the browser + +- Confirm the URL and what state should be true after navigation. +- Decide what evidence is needed: full-page screenshot, viewport screenshot, console log, network trace, HTML snapshot, extracted text. +- Decide the viewport size that matters for the task (mobile vs desktop). Default to a desktop size unless the task is mobile-specific. +- For local dev servers, confirm the server is running and the port is what you expect. + +## Driving the browser + +A typical verification session: + +1. **Launch with a real-looking user agent** when the target is the public internet; an unrealistic UA flags automation traffic. +2. **Set a sane viewport** (e.g., 1366×768 desktop, 390×844 iPhone-ish). +3. **Navigate and wait for the right signal.** Prefer waiting for a specific selector or network-idle over arbitrary sleeps. +4. **Capture evidence immediately** after the wait condition succeeds, before any interaction perturbs the state. +5. **Interact deliberately.** One click at a time, with a wait between actions; re-screenshot after each meaningful state change. +6. **Read the console and network panels** for unexpected errors, 4xx/5xx responses, or slow requests. +7. **Close the browser cleanly** when done. Long-running browser sessions leak memory and hold ports. + +## What evidence to record + +For a verification task, deliver: + +- A full-page or viewport screenshot of each meaningful state. +- The console log, filtered to warnings/errors. +- Any non-2xx network response with the URL, status, and a short response body excerpt. +- A short narration: "Navigated to X, observed Y, clicked Z, observed W." + +For a UI bug repro, also record: + +- The exact reproduction steps the user can follow. +- Viewport size and (where relevant) device pixel ratio. +- Whether the bug reproduces on first load vs after interaction. + +## Login-gated pages + +- Prefer programmatic auth (API token, magic link) over UI login. +- If UI login is the only path, the user must provide credentials explicitly for this run. Never reuse credentials outside the session. +- Do not store credentials in the session log, screenshot, or returned output. + +## Performance and politeness + +- Throttle to one navigation per few seconds when touching shared infra. +- Respect `robots.txt` for public sites you are inspecting at any volume. +- Cancel navigations if a page exceeds a reasonable timeout (e.g., 30s); the page is broken or rate-limiting you. +- Do not retry forever on failure. Retry once with a longer timeout, then escalate. + +## Common failure modes + +- **Selector not found.** Page changed, or you are waiting before render. Take a screenshot to see actual state; adjust the selector. +- **Click does nothing.** The element is offscreen, covered by a modal, or in a shadow DOM. Scroll into view or pierce the shadow root. +- **Headless detection.** Some sites detect headless Chrome and serve a different page. Use a non-headless mode or a fingerprint-realistic configuration only when authorized. +- **Cross-origin iframe blocking.** Iframes you do not own cannot be inspected; the page must offer the data outside the iframe or the task is infeasible. + +## Anti-patterns + +- Long unsupervised browser sessions that drift from the original task. +- Scraping behind authentication you do not own. +- Captioning a screenshot with "looks good" without saying what state was loaded and what selectors confirmed it. +- Treating a passing screenshot as proof of correctness across viewports you did not actually test. diff --git a/packages/skills-catalog/catalog/optional/content/release-announcement/SKILL.md b/packages/skills-catalog/catalog/optional/content/release-announcement/SKILL.md new file mode 100644 index 00000000..8870aa65 --- /dev/null +++ b/packages/skills-catalog/catalog/optional/content/release-announcement/SKILL.md @@ -0,0 +1,128 @@ +--- +name: release-announcement +description: Write a release announcement — changelog, blog post, in-app note, or social post — that leads with user impact, names the audience, and includes upgrade/migration steps without filler. +key: paperclipai/optional/content/release-announcement +recommendedForRoles: + - devrel + - product + - writer +tags: + - release + - changelog + - announcement + - communication +--- + +# Release Announcement + +Write the channel-appropriate announcement for a release without churn. Different surfaces need different shapes: a changelog entry is not a blog post is not a social card. The bar is: a reader of the chosen surface can decide in under 30 seconds whether this release affects them, and if so what to do. + +## When to use + +- A version, feature, or fix is shipping and needs writeup for at least one surface. +- A previously private feature is going GA. +- A breaking change needs broadcast before users hit it. + +## When not to use + +- An internal-only change with no user impact. Update internal docs; do not announce. +- The release is incomplete (still in active development). Wait until it ships, even if marketing wants the post. + +## Determine the audience and channel first + +| Audience | Best channel | Tone | +|---|---|---| +| Existing power users | Changelog, in-app note | Terse, factual, links | +| Engineering teams adopting your API | Release notes, dev blog | Examples, migration steps, version pins | +| Prospective customers | Landing page, marketing blog | Story arc, problem → solution, social proof | +| Broad audience | Social post, email newsletter | One-sentence pitch, link to depth | +| Internal team | Slack/Discord post | What changed, who to ping if it breaks | + +Pick the audience for *this* writeup. One release often needs several writeups; do not blend them. + +## Universal structure + +Whatever the channel, lead with: + +1. **What changed.** One sentence in the user's vocabulary. +2. **Who it affects.** Which user role / use case. +3. **What to do.** Migrate now / opt-in / no action needed. + +Everything else is depth that supports those three. + +## Channel templates + +### Changelog entry (terse) + +```md +## v1.42.0 — 2026-05-26 + +### Added +- . ([#1234](link)) + +### Changed +- . ([#1235](link)) + +### Fixed +- . ([#1236](link)) + +### Deprecated +- . Replaced by . Removal planned for v. + +### Breaking +- . **Migration:** or . +``` + +### Release notes (for adopters) + +Same as changelog, plus: + +- Migration guide section with before/after code. +- Compatibility table (versions, runtimes, OS). +- Known issues and workarounds. +- Acknowledgements (contributors, reporters of fixed bugs). + +### Dev blog post (300–800 words) + +- **Hook (1 paragraph):** the problem the release solves, in a real-world scenario. +- **What's new (3–5 bullets with sub-paragraphs):** features, with one code or screenshot example each. +- **Upgrade (1 paragraph):** how to upgrade, what to check. +- **What's next:** one sentence about the next direction. Avoid promises. + +### In-app note + +- 1 sentence. +- 1 link. +- Dismiss after seen. + +### Social post + +- 1 sentence pitch. +- 1 link. +- 1 image or short clip. +- No threadbait. If it needs a thread, write a blog post instead. + +## Writing rules + +- Lead with the user, not the team. `You can now export to CSV` beats `We've added CSV export`. +- Numbers beat adjectives. `60% faster cold start` beats `much faster`. Cite the methodology. +- Show, don't just tell. One code snippet, one screenshot — more is noise. +- Date the post. Undated release content rots fastest. +- Link the migration path explicitly. Do not bury it. +- Mark breaking changes with `**Breaking:**` prefix. Repeat in the email/social channel. + +## Avoid + +- "We are excited to announce" filler. +- Lists of changes that mix user-visible and internal items. +- Marketing claims without a way to verify. +- Promised dates for unshipped work. +- Pre-announcing something the team has not yet committed to ship. + +## Post-publish checklist + +- Changelog is in source control alongside the release. +- Blog post date matches actual ship date. +- All links work (release tag, PRs, docs sections). +- Breaking changes are also in the upgrade guide, not only the post. +- Internal team is notified before the public post goes live, not after. diff --git a/packages/skills-catalog/catalog/optional/product/design-critique/SKILL.md b/packages/skills-catalog/catalog/optional/product/design-critique/SKILL.md new file mode 100644 index 00000000..a3a1607a --- /dev/null +++ b/packages/skills-catalog/catalog/optional/product/design-critique/SKILL.md @@ -0,0 +1,121 @@ +--- +name: design-critique +description: Give a structured product design critique — user job clarity, hierarchy, affordance, error states, accessibility, and consistency — focused on what to change, in what order, and why. +key: paperclipai/optional/product/design-critique +recommendedForRoles: + - designer + - product + - engineer +tags: + - design + - product + - ux + - review +--- + +# Product Design Critique + +A structured critique pass for a screen, flow, or component. The output is a prioritized list of changes a designer or engineer can act on — not adjectives. Critique is not redesign; recommend, do not rebuild. + +## When to use + +- A designer or engineer asks for feedback on a screen, mock, or live UI. +- A feature is shipping and someone wants a final UX read. +- A flow is suspected of causing user drop-off and you want a pre-research read before instrumentation. + +## When not to use + +- The user wants a redesign. That is a design project, not a critique. +- The work is so early that no concrete artifact exists. Sketch with them instead of critiquing air. +- You have no context on the user job. Ask for it first; design critique without user context devolves into taste. + +## Pre-critique context + +Before opening a screen, get: + +- **Who is the user.** Specific role and competence, not "users". +- **What job they are doing on this screen.** One sentence. +- **What success looks like.** What the user can do after this screen that they could not before. +- **Where this screen sits in the larger flow.** What precedes and follows. + +If any of these is missing, ask. Critique without these is opinion. + +## The pass (in order) + +1. **Clarity of the user job.** + - Within 3 seconds of opening, is it obvious what this screen is for? + - Does the primary action match the user's actual job, or a designer's preferred path? + +2. **Visual hierarchy.** + - The most important thing on the screen should be the most prominent (size, weight, position, color). + - Secondary actions should look secondary. Tertiary should be findable but not loud. + - Headings should chunk content into the right groups for the task. + +3. **Affordance and signifiers.** + - Clickable things look clickable. + - Disabled things look disabled and explain why on hover/focus. + - Drag, scroll, or swipe interactions are discoverable, not hidden. + +4. **States.** + - Empty state (no data) is designed, not a blank rectangle. + - Loading state communicates progress, not just spins. + - Error states say what went wrong and what to do next, in the user's words. + - Success state confirms without celebrating banal actions. + +5. **Inputs and forms.** + - Labels visible, not just placeholders. + - Validation runs at the right time (on blur, not on every keystroke unless the user is in a known-format field). + - Required fields marked. + - Field order matches the user's mental order, not the database order. + +6. **Accessibility.** + - Sufficient color contrast (WCAG AA at minimum; AAA where reasonable). + - Focus order is logical for keyboard navigation. + - Interactive elements are reachable without a mouse. + - Critical information is not color-only (icons, text, position back it up). + - Touch targets at least 44×44 px on mobile. + +7. **Consistency.** + - Tokens, components, and patterns match the rest of the product. + - "Borrowed" patterns from other products are intentional, not accidental drift. + +8. **Copy.** + - Buttons are verbs that name the outcome ("Save changes" beats "Submit"). + - Microcopy explains, does not decorate. + - Tone matches the product voice. + +9. **Edge cases.** + - Long content (long names, many items, RTL languages). + - Tiny content (one item, zero items). + - Slow network and offline behavior. + - Permissions denied. + +## Output format + +Group findings by severity, then by category. Each finding is one issue and one suggested fix. + +```md +## Design critique: + +### Must-fix (blocks ship) +- **:** . **Try:** . + +### Should-fix (before broader rollout) +- **:** . **Try:** . + +### Nice-to-fix (when there's room) +- **:** . **Try:** . + +### Strengths to keep +- +``` + +Always include the "strengths to keep" section. It is not flattery — it is signal to the designer about what not to change in the next round. + +## Anti-patterns + +- "I would do it differently" without saying what or why. That is preference, not critique. +- Long critiques that bury must-fix items under nice-to-haves. +- Suggesting net-new features under the guise of a critique. +- Ignoring user context and grading on taste. +- Treating a critique as approval. State approval explicitly if asked; otherwise critique is feedback, not sign-off. diff --git a/packages/skills-catalog/generated/catalog.json b/packages/skills-catalog/generated/catalog.json new file mode 100644 index 00000000..5c54e613 --- /dev/null +++ b/packages/skills-catalog/generated/catalog.json @@ -0,0 +1,285 @@ +{ + "schemaVersion": 1, + "packageName": "@paperclipai/skills-catalog", + "packageVersion": "0.3.1", + "generatedAt": "2026-05-28T03:02:49.579Z", + "skills": [ + { + "id": "paperclipai:bundled:docs:doc-maintenance", + "key": "paperclipai/bundled/docs/doc-maintenance", + "kind": "bundled", + "category": "docs", + "slug": "doc-maintenance", + "name": "doc-maintenance", + "description": "Keep project docs aligned with recent code and feature changes — detect drift, update affected pages, and add release-relevant notes without rewriting unchanged sections.", + "path": "catalog/bundled/docs/doc-maintenance", + "entrypoint": "SKILL.md", + "trustLevel": "markdown_only", + "compatibility": "compatible", + "defaultInstall": false, + "recommendedForRoles": [ + "engineer", + "product", + "devrel" + ], + "requires": [], + "tags": [ + "docs", + "documentation", + "release-notes" + ], + "files": [ + { + "path": "SKILL.md", + "kind": "skill", + "sizeBytes": 4478, + "sha256": "fb0353386c5e5e5e13bcbb3233f044e3dccecf371f429d6328f26c26d7cb6169" + } + ], + "contentHash": "sha256:2e02299210fd17c1fe1867b4ee8c144a11b6fe1fe481f83b8268cfbaaf10f9aa" + }, + { + "id": "paperclipai:bundled:paperclip-operations:issue-triage", + "key": "paperclipai/bundled/paperclip-operations/issue-triage", + "kind": "bundled", + "category": "paperclip-operations", + "slug": "issue-triage", + "name": "issue-triage", + "description": "Triage Paperclip inbox issues that are stale, blocked, in-review, or assigned-but-not-progressing, and decide a single next action per issue (resume, reassign, unblock, escalate, or close).", + "path": "catalog/bundled/paperclip-operations/issue-triage", + "entrypoint": "SKILL.md", + "trustLevel": "markdown_only", + "compatibility": "compatible", + "defaultInstall": false, + "recommendedForRoles": [ + "manager", + "ceo", + "engineer" + ], + "requires": [], + "tags": [ + "paperclip", + "triage", + "inbox", + "workflow" + ], + "files": [ + { + "path": "SKILL.md", + "kind": "skill", + "sizeBytes": 4042, + "sha256": "df5bdc8bf5e017b7ba5f70a4b5323fad51d0c323278f386580f26cf43ad09160" + } + ], + "contentHash": "sha256:88dc13560371fb364963782cb4f6eeb4090fcde92ee3774479428ed6b90e11c1" + }, + { + "id": "paperclipai:bundled:paperclip-operations:task-planning", + "key": "paperclipai/bundled/paperclip-operations/task-planning", + "kind": "bundled", + "category": "paperclip-operations", + "slug": "task-planning", + "name": "task-planning", + "description": "Turn a Paperclip issue or request into a structured implementation plan with child task graph, blockers, owners, and acceptance criteria, then save it as the issue `plan` document.", + "path": "catalog/bundled/paperclip-operations/task-planning", + "entrypoint": "SKILL.md", + "trustLevel": "markdown_only", + "compatibility": "compatible", + "defaultInstall": false, + "recommendedForRoles": [ + "manager", + "engineer", + "product" + ], + "requires": [], + "tags": [ + "paperclip", + "planning", + "issues", + "delegation" + ], + "files": [ + { + "path": "SKILL.md", + "kind": "skill", + "sizeBytes": 4649, + "sha256": "2ff61e12dfaa4cf8cc548529fd176f55f1b1f5292ff9dd3eb2cb331417ab5e4e" + } + ], + "contentHash": "sha256:4fb46a4bcefad4fd46fae48c433ee497112509a8e19fb8a7745ead44d219b498" + }, + { + "id": "paperclipai:bundled:quality:qa-acceptance", + "key": "paperclipai/bundled/quality/qa-acceptance", + "kind": "bundled", + "category": "quality", + "slug": "qa-acceptance", + "name": "qa-acceptance", + "description": "Produce QA acceptance criteria and a manual validation plan for a feature change — golden path, edge cases, error states, performance limits, and explicit pass/fail evidence.", + "path": "catalog/bundled/quality/qa-acceptance", + "entrypoint": "SKILL.md", + "trustLevel": "markdown_only", + "compatibility": "compatible", + "defaultInstall": false, + "recommendedForRoles": [ + "qa", + "engineer", + "product" + ], + "requires": [], + "tags": [ + "qa", + "acceptance", + "validation", + "testing" + ], + "files": [ + { + "path": "SKILL.md", + "kind": "skill", + "sizeBytes": 3861, + "sha256": "c631b437ab26d104af6cdb963d8f679a9341439041b3cb3ec8835f4ff551b378" + } + ], + "contentHash": "sha256:32372dacaf62e93454b9855968c4eec96456ba78b509f450b3dfaa48e31ef356" + }, + { + "id": "paperclipai:bundled:software-development:github-pr-workflow", + "key": "paperclipai/bundled/software-development/github-pr-workflow", + "kind": "bundled", + "category": "software-development", + "slug": "github-pr-workflow", + "name": "github-pr-workflow", + "description": "Prepare a GitHub pull request from a feature branch — branch hygiene, commit shape, title/body, verification notes, screenshots for UI work, and replies to review comments.", + "path": "catalog/bundled/software-development/github-pr-workflow", + "entrypoint": "SKILL.md", + "trustLevel": "markdown_only", + "compatibility": "compatible", + "defaultInstall": false, + "recommendedForRoles": [ + "engineer" + ], + "requires": [], + "tags": [ + "github", + "pull-requests", + "code-review", + "release" + ], + "files": [ + { + "path": "SKILL.md", + "kind": "skill", + "sizeBytes": 3970, + "sha256": "f498ec4ebb1779dea37adeb1db8a8b22316282798e35ee02e2fc5ff627d7e261" + } + ], + "contentHash": "sha256:90f278c89aa0711be150c1cd2456ca25620d02f36995b113ca9837d756a37f6c" + }, + { + "id": "paperclipai:optional:browser:agent-browser", + "key": "paperclipai/optional/browser/agent-browser", + "kind": "optional", + "category": "browser", + "slug": "agent-browser", + "name": "agent-browser", + "description": "Drive a real browser to inspect or interact with a web page or app — navigate, take screenshots, read console and network, fill simple forms — for verification tasks, not unattended automation.", + "path": "catalog/optional/browser/agent-browser", + "entrypoint": "SKILL.md", + "trustLevel": "markdown_only", + "compatibility": "compatible", + "defaultInstall": false, + "recommendedForRoles": [ + "qa", + "engineer", + "researcher" + ], + "requires": [], + "tags": [ + "browser", + "puppeteer", + "playwright", + "verification" + ], + "files": [ + { + "path": "SKILL.md", + "kind": "skill", + "sizeBytes": 5133, + "sha256": "362f7b9d02297782bc6f0c093f495b8a0304a75bcf4b42e5c280a42b1f757b7d" + } + ], + "contentHash": "sha256:eabb2c9f7b5e1a27ebb1e05a711d61433a266478154cd671a685e99e67aadea2" + }, + { + "id": "paperclipai:optional:content:release-announcement", + "key": "paperclipai/optional/content/release-announcement", + "kind": "optional", + "category": "content", + "slug": "release-announcement", + "name": "release-announcement", + "description": "Write a release announcement — changelog, blog post, in-app note, or social post — that leads with user impact, names the audience, and includes upgrade/migration steps without filler.", + "path": "catalog/optional/content/release-announcement", + "entrypoint": "SKILL.md", + "trustLevel": "markdown_only", + "compatibility": "compatible", + "defaultInstall": false, + "recommendedForRoles": [ + "devrel", + "product", + "writer" + ], + "requires": [], + "tags": [ + "release", + "changelog", + "announcement", + "communication" + ], + "files": [ + { + "path": "SKILL.md", + "kind": "skill", + "sizeBytes": 4416, + "sha256": "062810ac34e9edc89efa701fec2eee60f16949d1944cc2cae49803cb91e8cbf4" + } + ], + "contentHash": "sha256:f22a9ed696e6614c6db2757a149f48b3295e81f78c27d065d9cb164cf4f8a9bd" + }, + { + "id": "paperclipai:optional:product:design-critique", + "key": "paperclipai/optional/product/design-critique", + "kind": "optional", + "category": "product", + "slug": "design-critique", + "name": "design-critique", + "description": "Give a structured product design critique — user job clarity, hierarchy, affordance, error states, accessibility, and consistency — focused on what to change, in what order, and why.", + "path": "catalog/optional/product/design-critique", + "entrypoint": "SKILL.md", + "trustLevel": "markdown_only", + "compatibility": "compatible", + "defaultInstall": false, + "recommendedForRoles": [ + "designer", + "product", + "engineer" + ], + "requires": [], + "tags": [ + "design", + "product", + "ux", + "review" + ], + "files": [ + { + "path": "SKILL.md", + "kind": "skill", + "sizeBytes": 4851, + "sha256": "022e619baf6cc25725946279cb8052d22af090dd6cd6dc8c20f17867f71a5d8e" + } + ], + "contentHash": "sha256:429f94df398a0697042b5bbe4755b1ff1a230aa5f41d99118ad37493ac65d21c" + } + ] +} diff --git a/packages/skills-catalog/package.json b/packages/skills-catalog/package.json new file mode 100644 index 00000000..134c6beb --- /dev/null +++ b/packages/skills-catalog/package.json @@ -0,0 +1,49 @@ +{ + "name": "@paperclipai/skills-catalog", + "version": "0.3.1", + "license": "MIT", + "homepage": "https://github.com/paperclipai/paperclip", + "bugs": { + "url": "https://github.com/paperclipai/paperclip/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/paperclipai/paperclip", + "directory": "packages/skills-catalog" + }, + "type": "module", + "exports": { + ".": "./src/index.ts", + "./types": "./src/types.ts", + "./catalog.json": "./generated/catalog.json" + }, + "publishConfig": { + "access": "public", + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" + }, + "./types": { + "types": "./dist/src/types.d.ts", + "import": "./dist/src/types.js" + }, + "./catalog.json": "./dist/generated/catalog.json" + }, + "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts" + }, + "files": [ + "catalog", + "dist", + "generated" + ], + "scripts": { + "build": "pnpm run build:manifest && tsc -p tsconfig.json", + "build:manifest": "node ../../cli/node_modules/tsx/dist/cli.mjs scripts/build-catalog-manifest.ts", + "clean": "rm -rf dist", + "test": "pnpm -w exec vitest run --root packages/skills-catalog --config vitest.config.ts", + "typecheck": "tsc -p tsconfig.json --noEmit", + "validate": "node ../../cli/node_modules/tsx/dist/cli.mjs scripts/validate-catalog.ts" + } +} diff --git a/packages/skills-catalog/scripts/build-catalog-manifest.ts b/packages/skills-catalog/scripts/build-catalog-manifest.ts new file mode 100644 index 00000000..d7adb1c2 --- /dev/null +++ b/packages/skills-catalog/scripts/build-catalog-manifest.ts @@ -0,0 +1,15 @@ +import { fileURLToPath } from "node:url"; +import path from "node:path"; +import { writeCatalogManifest } from "../src/catalog-builder.js"; + +const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const result = await writeCatalogManifest(packageDir); + +if (result.errors.length > 0) { + for (const error of result.errors) { + console.error(`- ${error}`); + } + process.exitCode = 1; +} else { + console.log(`Wrote generated/catalog.json with ${result.manifest.skills.length} catalog skills.`); +} diff --git a/packages/skills-catalog/scripts/validate-catalog.ts b/packages/skills-catalog/scripts/validate-catalog.ts new file mode 100644 index 00000000..4ba179c5 --- /dev/null +++ b/packages/skills-catalog/scripts/validate-catalog.ts @@ -0,0 +1,15 @@ +import { fileURLToPath } from "node:url"; +import path from "node:path"; +import { validateCatalog } from "../src/catalog-builder.js"; + +const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const result = await validateCatalog(packageDir); + +if (result.errors.length > 0) { + for (const error of result.errors) { + console.error(`- ${error}`); + } + process.exitCode = 1; +} else { + console.log(`Catalog manifest is valid with ${result.manifest.skills.length} catalog skills.`); +} diff --git a/packages/skills-catalog/src/catalog-builder.test.ts b/packages/skills-catalog/src/catalog-builder.test.ts new file mode 100644 index 00000000..9f06a761 --- /dev/null +++ b/packages/skills-catalog/src/catalog-builder.test.ts @@ -0,0 +1,165 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + buildCatalogManifest, + formatCatalogManifest, + validateCatalog, +} from "./catalog-builder.js"; + +const tempDirs: string[] = []; + +describe("skills catalog manifest", () => { + afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true }))); + }); + + it("builds stable manifest entries from catalog skill directories", async () => { + const packageDir = await createCatalogPackage(); + await writeSkill(packageDir, "bundled", "software-development", "github-pr-workflow", { + frontmatter: [ + "name: GitHub PR Workflow", + "description: Prepare pull requests and verification notes.", + "key: paperclipai/bundled/software-development/github-pr-workflow", + "recommendedForRoles:", + " - engineer", + "tags:", + " - github", + " - pull-requests", + ], + files: { + "references/checklist.md": "# Checklist\n", + }, + }); + + const result = await buildCatalogManifest({ + packageDir, + generatedAt: "2026-05-26T00:00:00.000Z", + }); + + expect(result.errors).toEqual([]); + expect(result.manifest.skills).toHaveLength(1); + expect(result.manifest.skills[0]).toMatchObject({ + id: "paperclipai:bundled:software-development:github-pr-workflow", + key: "paperclipai/bundled/software-development/github-pr-workflow", + kind: "bundled", + category: "software-development", + slug: "github-pr-workflow", + name: "GitHub PR Workflow", + trustLevel: "markdown_only", + compatibility: "compatible", + recommendedForRoles: ["engineer"], + tags: ["github", "pull-requests"], + }); + expect(result.manifest.skills[0]!.files.map((file) => file.path)).toEqual([ + "SKILL.md", + "references/checklist.md", + ]); + expect(result.manifest.skills[0]!.contentHash).toMatch(/^sha256:[a-f0-9]{64}$/); + }); + + it("reports frontmatter, directory, uniqueness, and inventory errors together", async () => { + const packageDir = await createCatalogPackage(); + await writeSkill(packageDir, "bundled", "Bad_Category", "duplicate", { + frontmatter: [ + "name: Duplicate", + "key: paperclipai/bundled/software-development/other", + "recommendedForRoles: engineer", + ], + }); + await writeSkill(packageDir, "optional", "software-development", "duplicate", { + frontmatter: [ + "name: Duplicate Optional", + "description: Optional duplicate slug.", + ], + }); + await fs.mkdir(path.join(packageDir, "catalog", "bundled", "software-development", "missing-skill"), { + recursive: true, + }); + await fs.mkdir(path.join(packageDir, "catalog", "misc"), { recursive: true }); + await fs.writeFile(path.join(packageDir, "catalog", "misc", "SKILL.md"), "# Misplaced\n", "utf8"); + + const result = await buildCatalogManifest({ + packageDir, + generatedAt: "2026-05-26T00:00:00.000Z", + }); + + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.stringContaining("catalog/misc/SKILL.md is not under catalog////SKILL.md"), + expect.stringContaining("catalog/bundled/software-development/missing-skill is missing SKILL.md"), + expect.stringContaining("has invalid category"), + expect.stringContaining("frontmatter must include description"), + expect.stringContaining("key must be paperclipai/bundled/Bad_Category/duplicate"), + expect.stringContaining("field recommendedForRoles must be an array of strings"), + expect.stringContaining("Duplicate catalog slug \"duplicate\""), + ]), + ); + }); + + it("detects stale generated manifests", async () => { + const packageDir = await createCatalogPackage(); + await writeSkill(packageDir, "bundled", "software-development", "review", { + frontmatter: [ + "name: Review", + "description: Review implementation work.", + ], + }); + await fs.mkdir(path.join(packageDir, "generated"), { recursive: true }); + await fs.writeFile( + path.join(packageDir, "generated", "catalog.json"), + formatCatalogManifest({ + schemaVersion: 1, + packageName: "@paperclipai/skills-catalog", + packageVersion: "0.3.1", + generatedAt: "2026-05-26T00:00:00.000Z", + skills: [], + }), + "utf8", + ); + + const result = await validateCatalog(packageDir); + + expect(result.errors).toContain( + "generated/catalog.json is stale. Run pnpm --filter @paperclipai/skills-catalog build:manifest.", + ); + }); +}); + +async function createCatalogPackage() { + const packageDir = await fs.mkdtemp(path.join(os.tmpdir(), "skills-catalog-")); + tempDirs.push(packageDir); + await fs.mkdir(path.join(packageDir, "catalog", "bundled"), { recursive: true }); + await fs.mkdir(path.join(packageDir, "catalog", "optional"), { recursive: true }); + await fs.writeFile( + path.join(packageDir, "package.json"), + JSON.stringify({ version: "0.3.1" }), + "utf8", + ); + return packageDir; +} + +async function writeSkill( + packageDir: string, + kind: "bundled" | "optional", + category: string, + slug: string, + options: { + frontmatter: string[]; + files?: Record; + }, +) { + const skillDir = path.join(packageDir, "catalog", kind, category, slug); + await fs.mkdir(skillDir, { recursive: true }); + await fs.writeFile( + path.join(skillDir, "SKILL.md"), + `---\n${options.frontmatter.join("\n")}\n---\n\nUse this skill.\n`, + "utf8", + ); + for (const [relativePath, content] of Object.entries(options.files ?? {})) { + const filePath = path.join(skillDir, relativePath); + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, content, "utf8"); + } +} diff --git a/packages/skills-catalog/src/catalog-builder.ts b/packages/skills-catalog/src/catalog-builder.ts new file mode 100644 index 00000000..ac0502fe --- /dev/null +++ b/packages/skills-catalog/src/catalog-builder.ts @@ -0,0 +1,443 @@ +import { createHash } from "node:crypto"; +import { existsSync } from "node:fs"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { + asBoolean, + asString, + asStringArray, + parseFrontmatterMarkdown, +} from "./frontmatter.js"; +import type { + CatalogManifest, + CatalogSkill, + CatalogSkillFile, + CatalogSkillFileKind, + CatalogSkillKind, + CatalogTrustLevel, +} from "./types.js"; + +const CATALOG_PACKAGE_NAME = "@paperclipai/skills-catalog"; +const CATALOG_SCHEMA_VERSION = 1; +const SKILL_ENTRYPOINT = "SKILL.md"; +const MAX_CATALOG_FILE_BYTES = 1024 * 1024; +const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; +const CATALOG_KINDS = new Set(["bundled", "optional"]); + +interface SkillCandidate { + kind: CatalogSkillKind; + category: string; + slug: string; + absolutePath: string; +} + +interface BuildCatalogManifestOptions { + packageDir: string; + generatedAt?: string; +} + +interface BuildCatalogManifestResult { + manifest: CatalogManifest; + errors: string[]; +} + +export function formatCatalogManifest(manifest: CatalogManifest): string { + return `${JSON.stringify(manifest, null, 2)}\n`; +} + +export async function buildExpectedCatalogManifest( + packageDir: string, +): Promise { + const existing = await readExistingManifest(packageDir); + const firstPass = await buildCatalogManifest({ + packageDir, + generatedAt: existing?.generatedAt ?? new Date().toISOString(), + }); + + if (existing && sameManifestExceptGeneratedAt(existing, firstPass.manifest)) { + return firstPass; + } + + return buildCatalogManifest({ + packageDir, + generatedAt: new Date().toISOString(), + }); +} + +export async function buildCatalogManifest( + options: BuildCatalogManifestOptions, +): Promise { + const packageDir = path.resolve(options.packageDir); + const packageJson = await readPackageJson(packageDir); + const errors: string[] = []; + const candidates = await discoverSkillCandidates(packageDir, errors); + const skills: CatalogSkill[] = []; + + collectCandidateUniquenessErrors(candidates, errors); + + for (const candidate of candidates) { + const skill = await buildCatalogSkill(packageDir, candidate, errors); + if (skill) skills.push(skill); + } + + skills.sort((a, b) => a.id.localeCompare(b.id)); + collectUniquenessErrors(skills, errors); + + return { + manifest: { + schemaVersion: CATALOG_SCHEMA_VERSION, + packageName: CATALOG_PACKAGE_NAME, + packageVersion: packageJson.version, + generatedAt: options.generatedAt ?? new Date().toISOString(), + skills, + }, + errors, + }; +} + +export async function validateCatalog(packageDir: string): Promise { + const expected = await buildExpectedCatalogManifest(packageDir); + const generatedPath = path.join(packageDir, "generated", "catalog.json"); + const errors = [...expected.errors]; + + let generatedText: string | null = null; + try { + generatedText = await fs.readFile(generatedPath, "utf8"); + JSON.parse(generatedText); + } catch (error) { + errors.push(`generated/catalog.json is missing or invalid: ${errorMessage(error)}`); + } + + if (generatedText !== null) { + const expectedText = formatCatalogManifest(expected.manifest); + if (generatedText !== expectedText) { + errors.push("generated/catalog.json is stale. Run pnpm --filter @paperclipai/skills-catalog build:manifest."); + } + } + + return { + manifest: expected.manifest, + errors, + }; +} + +export async function writeCatalogManifest(packageDir: string) { + const result = await buildExpectedCatalogManifest(packageDir); + if (result.errors.length > 0) return result; + + const generatedDir = path.join(packageDir, "generated"); + await fs.mkdir(generatedDir, { recursive: true }); + await fs.writeFile(path.join(generatedDir, "catalog.json"), formatCatalogManifest(result.manifest), "utf8"); + return result; +} + +async function readPackageJson(packageDir: string) { + const packageJsonPath = path.join(packageDir, "package.json"); + const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8")) as { version?: unknown }; + const version = asString(packageJson.version); + if (!version) throw new Error(`${packageJsonPath} must declare a package version.`); + return { version }; +} + +async function readExistingManifest(packageDir: string): Promise { + try { + return JSON.parse(await fs.readFile(path.join(packageDir, "generated", "catalog.json"), "utf8")) as CatalogManifest; + } catch { + return null; + } +} + +async function discoverSkillCandidates(packageDir: string, errors: string[]) { + const catalogDir = path.join(packageDir, "catalog"); + const candidates: SkillCandidate[] = []; + + if (!existsSync(catalogDir)) { + errors.push("catalog directory is missing."); + return candidates; + } + + await collectMisplacedSkillFiles(catalogDir, errors); + + for (const kind of ["bundled", "optional"] as const) { + const kindDir = path.join(catalogDir, kind); + if (!existsSync(kindDir)) continue; + + for (const categoryEntry of await sortedDirEntries(kindDir)) { + if (!categoryEntry.isDirectory()) continue; + const category = categoryEntry.name; + const categoryDir = path.join(kindDir, category); + + for (const slugEntry of await sortedDirEntries(categoryDir)) { + if (!slugEntry.isDirectory()) continue; + const slug = slugEntry.name; + const skillDir = path.join(categoryDir, slug); + if (!existsSync(path.join(skillDir, SKILL_ENTRYPOINT))) { + errors.push(`${relativePackagePath(packageDir, skillDir)} is missing SKILL.md.`); + continue; + } + candidates.push({ kind, category, slug, absolutePath: skillDir }); + } + } + } + + return candidates; +} + +async function collectMisplacedSkillFiles(catalogDir: string, errors: string[]) { + async function visit(dir: string) { + for (const entry of await sortedDirEntries(dir)) { + const absolutePath = path.join(dir, entry.name); + if (entry.isDirectory()) { + await visit(absolutePath); + continue; + } + if (entry.name !== SKILL_ENTRYPOINT) continue; + + const relativePath = toPosixPath(path.relative(catalogDir, absolutePath)); + const parts = relativePath.split("/"); + const kind = parts[0]; + if (parts.length !== 4 || !CATALOG_KINDS.has(kind as CatalogSkillKind)) { + errors.push(`catalog/${relativePath} is not under catalog////SKILL.md.`); + } + } + } + + await visit(catalogDir); +} + +async function buildCatalogSkill( + packageDir: string, + candidate: SkillCandidate, + errors: string[], +): Promise { + const prefix = relativePackagePath(packageDir, candidate.absolutePath); + validateSlug("category", candidate.category, prefix, errors); + validateSlug("slug", candidate.slug, prefix, errors); + + const id = `paperclipai:${candidate.kind}:${candidate.category}:${candidate.slug}`; + const key = `paperclipai/${candidate.kind}/${candidate.category}/${candidate.slug}`; + const skillMarkdownPath = path.join(candidate.absolutePath, SKILL_ENTRYPOINT); + const parsed = parseFrontmatterMarkdown(await fs.readFile(skillMarkdownPath, "utf8")); + + if (!parsed.hasFrontmatter) { + errors.push(`${prefix}/SKILL.md must start with YAML frontmatter.`); + } + + const name = asString(parsed.frontmatter.name); + if (!name) errors.push(`${prefix}/SKILL.md frontmatter must include name.`); + + const description = asString(parsed.frontmatter.description); + if (!description) errors.push(`${prefix}/SKILL.md frontmatter must include description.`); + + const explicitKey = asString(parsed.frontmatter.key); + if (explicitKey && explicitKey !== key) { + errors.push(`${prefix}/SKILL.md key must be ${key}.`); + } + + const explicitSlug = asString(parsed.frontmatter.slug); + if (explicitSlug && explicitSlug !== candidate.slug) { + errors.push(`${prefix}/SKILL.md slug must be ${candidate.slug}.`); + } + + const defaultInstall = asBoolean(parsed.frontmatter.defaultInstall) ?? false; + const recommendedForRoles = readStringArrayField(parsed.frontmatter.recommendedForRoles, "recommendedForRoles", prefix, errors); + const requires = readStringArrayField(parsed.frontmatter.requires, "requires", prefix, errors); + const tags = readStringArrayField(parsed.frontmatter.tags, "tags", prefix, errors); + const files = await collectSkillFiles(packageDir, candidate.absolutePath, prefix, errors); + + if (!name || !description) return null; + + return { + id, + key, + kind: candidate.kind, + category: candidate.category, + slug: candidate.slug, + name, + description, + path: toPosixPath(path.relative(packageDir, candidate.absolutePath)), + entrypoint: SKILL_ENTRYPOINT, + trustLevel: deriveTrustLevel(files), + compatibility: "compatible", + defaultInstall, + recommendedForRoles, + requires, + tags, + files, + contentHash: buildContentHash(files), + }; +} + +async function collectSkillFiles( + packageDir: string, + skillDir: string, + prefix: string, + errors: string[], +): Promise { + const files: CatalogSkillFile[] = []; + const skillRoot = await fs.realpath(skillDir); + + async function visit(dir: string) { + for (const entry of await sortedDirEntries(dir)) { + const absolutePath = path.join(dir, entry.name); + const lstat = await fs.lstat(absolutePath); + let stat = lstat; + let realPath = absolutePath; + + if (lstat.isSymbolicLink()) { + try { + realPath = await fs.realpath(absolutePath); + stat = await fs.stat(absolutePath); + } catch { + errors.push(`${relativePackagePath(packageDir, absolutePath)} is a broken symlink.`); + continue; + } + if (!isPathInside(skillRoot, realPath)) { + errors.push(`${relativePackagePath(packageDir, absolutePath)} points outside its skill directory.`); + continue; + } + if (stat.isDirectory()) { + errors.push(`${relativePackagePath(packageDir, absolutePath)} is a directory symlink; copy files into the skill directory instead.`); + continue; + } + } + + if (stat.isDirectory()) { + await visit(absolutePath); + continue; + } + if (!stat.isFile()) continue; + + const relativePath = toPosixPath(path.relative(skillDir, absolutePath)); + if (path.isAbsolute(relativePath) || relativePath.split("/").includes("..")) { + errors.push(`${prefix}/${relativePath} has an invalid inventory path.`); + continue; + } + if (stat.size > MAX_CATALOG_FILE_BYTES) { + errors.push(`${prefix}/${relativePath} exceeds ${MAX_CATALOG_FILE_BYTES} bytes.`); + } + + const contents = await fs.readFile(absolutePath); + files.push({ + path: relativePath, + kind: classifyCatalogFile(relativePath), + sizeBytes: stat.size, + sha256: sha256(contents), + }); + } + } + + await visit(skillDir); + files.sort((a, b) => { + if (a.path === SKILL_ENTRYPOINT) return -1; + if (b.path === SKILL_ENTRYPOINT) return 1; + return a.path.localeCompare(b.path); + }); + + if (!files.some((file) => file.path === SKILL_ENTRYPOINT && file.kind === "skill")) { + errors.push(`${prefix} inventory does not contain SKILL.md.`); + } + + return files; +} + +function readStringArrayField( + value: unknown, + field: string, + prefix: string, + errors: string[], +) { + const parsed = asStringArray(value); + if (!parsed) { + errors.push(`${prefix}/SKILL.md frontmatter field ${field} must be an array of strings.`); + return []; + } + return parsed; +} + +function classifyCatalogFile(relativePath: string): CatalogSkillFileKind { + if (relativePath === SKILL_ENTRYPOINT) return "skill"; + if (relativePath.startsWith("references/")) return "reference"; + if (relativePath.startsWith("scripts/")) return "script"; + if (relativePath.startsWith("assets/")) return "asset"; + if (relativePath.endsWith(".md") || relativePath.endsWith(".mdx")) return "markdown"; + return "other"; +} + +function deriveTrustLevel(files: CatalogSkillFile[]): CatalogTrustLevel { + if (files.some((file) => file.kind === "script")) return "scripts_executables"; + if (files.some((file) => file.kind === "asset" || file.kind === "other")) return "assets"; + return "markdown_only"; +} + +function buildContentHash(files: CatalogSkillFile[]) { + const hashInput = files.map((file) => ({ + path: file.path, + sha256: file.sha256, + })); + return `sha256:${sha256(Buffer.from(JSON.stringify(hashInput)))}`; +} + +function collectUniquenessErrors(skills: CatalogSkill[], errors: string[]) { + collectDuplicateErrors(skills, "id", errors); + collectDuplicateErrors(skills, "key", errors); + collectDuplicateErrors(skills, "slug", errors); +} + +function collectCandidateUniquenessErrors(candidates: SkillCandidate[], errors: string[]) { + const projected = candidates.map((candidate) => ({ + id: `paperclipai:${candidate.kind}:${candidate.category}:${candidate.slug}`, + key: `paperclipai/${candidate.kind}/${candidate.category}/${candidate.slug}`, + slug: candidate.slug, + path: toPosixPath(path.join("catalog", candidate.kind, candidate.category, candidate.slug)), + })) as CatalogSkill[]; + collectUniquenessErrors(projected, errors); +} + +function collectDuplicateErrors(fieldSkills: CatalogSkill[], field: "id" | "key" | "slug", errors: string[]) { + const seen = new Map(); + for (const skill of fieldSkills) { + const value = skill[field]; + const first = seen.get(value); + if (first) { + errors.push(`Duplicate catalog ${field} "${value}" in ${first} and ${skill.path}.`); + continue; + } + seen.set(value, skill.path); + } +} + +function validateSlug(label: string, value: string, prefix: string, errors: string[]) { + if (!SLUG_PATTERN.test(value)) { + errors.push(`${prefix} has invalid ${label} "${value}"; use lowercase URL slugs.`); + } +} + +async function sortedDirEntries(dir: string) { + return (await fs.readdir(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name)); +} + +function sameManifestExceptGeneratedAt(a: CatalogManifest, b: CatalogManifest) { + return JSON.stringify({ ...a, generatedAt: "" }) === JSON.stringify({ ...b, generatedAt: "" }); +} + +function sha256(contents: Buffer) { + return createHash("sha256").update(contents).digest("hex"); +} + +function relativePackagePath(packageDir: string, absolutePath: string) { + return toPosixPath(path.relative(packageDir, absolutePath)); +} + +function toPosixPath(input: string) { + return input.split(path.sep).join("/"); +} + +function isPathInside(parent: string, child: string) { + const relativePath = path.relative(parent, child); + return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)); +} + +function errorMessage(error: unknown) { + return error instanceof Error ? error.message : String(error); +} diff --git a/packages/skills-catalog/src/frontmatter.ts b/packages/skills-catalog/src/frontmatter.ts new file mode 100644 index 00000000..e2e431e7 --- /dev/null +++ b/packages/skills-catalog/src/frontmatter.ts @@ -0,0 +1,154 @@ +export interface MarkdownDoc { + frontmatter: Record; + body: string; + hasFrontmatter: boolean; +} + +export function isPlainRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +export function asString(value: unknown): string | null { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +export function asBoolean(value: unknown): boolean | null { + return typeof value === "boolean" ? value : null; +} + +export function asStringArray(value: unknown): string[] | null { + if (value === undefined) return []; + if (!Array.isArray(value)) return null; + + const out: string[] = []; + for (const item of value) { + const text = asString(item); + if (!text) return null; + out.push(text); + } + return out; +} + +export function parseFrontmatterMarkdown(raw: string): MarkdownDoc { + const normalized = raw.replace(/\r\n/g, "\n"); + if (!normalized.startsWith("---\n")) { + return { frontmatter: {}, body: normalized.trim(), hasFrontmatter: false }; + } + + const closing = normalized.indexOf("\n---\n", 4); + if (closing < 0) { + return { frontmatter: {}, body: normalized.trim(), hasFrontmatter: false }; + } + + const frontmatterRaw = normalized.slice(4, closing).trim(); + const body = normalized.slice(closing + 5).trim(); + return { + frontmatter: parseYamlFrontmatter(frontmatterRaw), + body, + hasFrontmatter: true, + }; +} + +function parseYamlFrontmatter(raw: string): Record { + const prepared = prepareYamlLines(raw); + if (prepared.length === 0) return {}; + const parsed = parseYamlBlock(prepared, 0, prepared[0]!.indent); + return isPlainRecord(parsed.value) ? parsed.value : {}; +} + +function prepareYamlLines(raw: string) { + return raw + .split("\n") + .map((line) => ({ + indent: line.match(/^ */)?.[0].length ?? 0, + content: line.trim(), + })) + .filter((line) => line.content.length > 0 && !line.content.startsWith("#")); +} + +function parseYamlBlock( + lines: Array<{ indent: number; content: string }>, + startIndex: number, + indentLevel: number, +): { value: unknown; nextIndex: number } { + let index = startIndex; + if (index >= lines.length || lines[index]!.indent < indentLevel) { + return { value: {}, nextIndex: index }; + } + + const isArray = lines[index]!.indent === indentLevel && lines[index]!.content.startsWith("-"); + if (isArray) { + const values: unknown[] = []; + while (index < lines.length) { + const line = lines[index]!; + if (line.indent < indentLevel) break; + if (line.indent !== indentLevel || !line.content.startsWith("-")) break; + + const remainder = line.content.slice(1).trim(); + index += 1; + if (!remainder) { + const nested = parseYamlBlock(lines, index, indentLevel + 2); + values.push(nested.value); + index = nested.nextIndex; + continue; + } + + values.push(parseYamlScalar(remainder)); + } + return { value: values, nextIndex: index }; + } + + const record: Record = {}; + while (index < lines.length) { + const line = lines[index]!; + if (line.indent < indentLevel) break; + if (line.indent !== indentLevel) { + index += 1; + continue; + } + + const separatorIndex = line.content.indexOf(":"); + if (separatorIndex <= 0) { + index += 1; + continue; + } + + const key = line.content.slice(0, separatorIndex).trim(); + const remainder = line.content.slice(separatorIndex + 1).trim(); + index += 1; + if (!remainder) { + const nested = parseYamlBlock(lines, index, indentLevel + 2); + record[key] = nested.value; + index = nested.nextIndex; + continue; + } + record[key] = parseYamlScalar(remainder); + } + + return { value: record, nextIndex: index }; +} + +function parseYamlScalar(rawValue: string): unknown { + const trimmed = rawValue.trim(); + if (trimmed === "") return ""; + if (trimmed === "null" || trimmed === "~") return null; + if (trimmed === "true") return true; + if (trimmed === "false") return false; + if (trimmed === "[]") return []; + if (trimmed === "{}") return {}; + if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed); + if ( + trimmed.startsWith("\"") || + trimmed.startsWith("[") || + trimmed.startsWith("{") + ) { + try { + return JSON.parse(trimmed); + } catch { + return trimmed; + } + } + return trimmed; +} diff --git a/packages/skills-catalog/src/index.ts b/packages/skills-catalog/src/index.ts new file mode 100644 index 00000000..70fefd2d --- /dev/null +++ b/packages/skills-catalog/src/index.ts @@ -0,0 +1,37 @@ +import catalogManifestJson from "../generated/catalog.json" with { type: "json" }; +import type { CatalogManifest, CatalogSkill } from "./types.js"; + +export type { + CatalogCompatibility, + CatalogManifest, + CatalogSkill, + CatalogSkillFile, + CatalogSkillFileKind, + CatalogSkillKind, + CatalogTrustLevel, + CatalogValidationResult, +} from "./types.js"; + +export const catalogManifest = catalogManifestJson as CatalogManifest; + +export const catalogSkills: CatalogSkill[] = catalogManifest.skills; + +const skillsById = new Map(catalogSkills.map((skill) => [skill.id, skill])); +const skillsByKey = new Map(catalogSkills.map((skill) => [skill.key, skill])); + +export function getCatalogSkill(id: string): CatalogSkill | null { + return skillsById.get(id) ?? null; +} + +export function resolveCatalogSkillRef(ref: string): CatalogSkill | null { + const normalized = ref.trim(); + if (normalized.length === 0) return null; + + const exactMatch = skillsById.get(normalized) ?? skillsByKey.get(normalized); + if (exactMatch) return exactMatch; + + const slugMatches = catalogSkills.filter((skill) => skill.slug === normalized); + if (slugMatches.length === 1) return slugMatches[0]!; + + return null; +} diff --git a/packages/skills-catalog/src/shipped-catalog.test.ts b/packages/skills-catalog/src/shipped-catalog.test.ts new file mode 100644 index 00000000..e6b916a2 --- /dev/null +++ b/packages/skills-catalog/src/shipped-catalog.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, it } from "vitest"; +import { catalogManifest, catalogSkills, resolveCatalogSkillRef } from "./index.js"; +import type { CatalogSkill } from "./types.js"; + +const EXPECTED_BUNDLED_KEYS = [ + "paperclipai/bundled/docs/doc-maintenance", + "paperclipai/bundled/paperclip-operations/issue-triage", + "paperclipai/bundled/paperclip-operations/task-planning", + "paperclipai/bundled/quality/qa-acceptance", + "paperclipai/bundled/software-development/github-pr-workflow", +]; + +const EXPECTED_OPTIONAL_KEYS = [ + "paperclipai/optional/browser/agent-browser", + "paperclipai/optional/content/release-announcement", + "paperclipai/optional/product/design-critique", +]; + +describe("shipped skills catalog", () => { + it("ships the expected bundled and optional skill set", () => { + const bundledKeys = catalogSkills + .filter((skill) => skill.kind === "bundled") + .map((skill) => skill.key) + .sort(); + const optionalKeys = catalogSkills + .filter((skill) => skill.kind === "optional") + .map((skill) => skill.key) + .sort(); + + expect(bundledKeys).toEqual(EXPECTED_BUNDLED_KEYS); + expect(optionalKeys).toEqual(EXPECTED_OPTIONAL_KEYS); + }); + + it("keeps every shipped skill markdown-only until a script-bearing skill clears security review", () => { + const scriptBearing = catalogSkills.filter((skill) => skill.trustLevel !== "markdown_only"); + expect(scriptBearing, formatViolations("script-bearing skills require security review", scriptBearing)).toEqual([]); + }); + + it("populates browse/search-relevant fields for every shipped skill", () => { + const issues: string[] = []; + for (const skill of catalogSkills) { + if (skill.compatibility !== "compatible") { + issues.push(`${skill.key} compatibility=${skill.compatibility}`); + } + if (!skill.description || skill.description.length < 40) { + issues.push(`${skill.key} description must be at least 40 characters for catalog browse/search`); + } + if (skill.recommendedForRoles.length === 0) { + issues.push(`${skill.key} must list recommendedForRoles`); + } + if (skill.tags.length === 0) { + issues.push(`${skill.key} must list tags`); + } + } + expect(issues).toEqual([]); + }); + + it("uses canonical paperclipai keys derived from kind/category/slug", () => { + const violations: string[] = []; + for (const skill of catalogSkills) { + const expectedKey = `paperclipai/${skill.kind}/${skill.category}/${skill.slug}`; + const expectedId = `paperclipai:${skill.kind}:${skill.category}:${skill.slug}`; + if (skill.key !== expectedKey) violations.push(`${skill.key} should be ${expectedKey}`); + if (skill.id !== expectedId) violations.push(`${skill.id} should be ${expectedId}`); + } + expect(violations).toEqual([]); + }); + + it("exposes a stable manifest header for downstream consumers", () => { + expect(catalogManifest.schemaVersion).toBe(1); + expect(catalogManifest.packageName).toBe("@paperclipai/skills-catalog"); + expect(catalogSkills.length).toBe(EXPECTED_BUNDLED_KEYS.length + EXPECTED_OPTIONAL_KEYS.length); + }); + + it("resolves shipped skills by id, key, and unique slug", () => { + const sample = catalogSkills.find((skill) => skill.key === "paperclipai/bundled/software-development/github-pr-workflow"); + expect(sample, "expected github-pr-workflow to ship in the bundled catalog").toBeDefined(); + if (!sample) return; + + expect(resolveCatalogSkillRef(sample.id)).toMatchObject({ key: sample.key }); + expect(resolveCatalogSkillRef(sample.key)).toMatchObject({ key: sample.key }); + expect(resolveCatalogSkillRef(sample.slug)).toMatchObject({ key: sample.key }); + }); +}); + +function formatViolations(label: string, skills: CatalogSkill[]) { + if (skills.length === 0) return label; + const detail = skills.map((skill) => `${skill.key} (${skill.trustLevel})`).join(", "); + return `${label}: ${detail}`; +} diff --git a/packages/skills-catalog/src/types.ts b/packages/skills-catalog/src/types.ts new file mode 100644 index 00000000..0985974e --- /dev/null +++ b/packages/skills-catalog/src/types.ts @@ -0,0 +1,48 @@ +export type CatalogSkillKind = "bundled" | "optional"; + +export type CatalogTrustLevel = "markdown_only" | "assets" | "scripts_executables"; + +export type CatalogCompatibility = "compatible" | "unknown" | "invalid"; + +export type CatalogSkillFileKind = "skill" | "markdown" | "reference" | "script" | "asset" | "other"; + +export interface CatalogSkillFile { + path: string; + kind: CatalogSkillFileKind; + sizeBytes: number; + sha256: string; +} + +export interface CatalogSkill { + id: string; + key: string; + kind: CatalogSkillKind; + category: string; + slug: string; + name: string; + description: string; + path: string; + entrypoint: "SKILL.md"; + trustLevel: CatalogTrustLevel; + compatibility: CatalogCompatibility; + defaultInstall: boolean; + recommendedForRoles: string[]; + requires: string[]; + tags: string[]; + files: CatalogSkillFile[]; + contentHash: string; +} + +export interface CatalogManifest { + schemaVersion: 1; + packageName: "@paperclipai/skills-catalog"; + packageVersion: string; + generatedAt: string; + skills: CatalogSkill[]; +} + +export interface CatalogValidationResult { + valid: boolean; + errors: string[]; + manifest: CatalogManifest; +} diff --git a/packages/skills-catalog/tsconfig.json b/packages/skills-catalog/tsconfig.json new file mode 100644 index 00000000..7f356d83 --- /dev/null +++ b/packages/skills-catalog/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "." + }, + "include": ["generated/**/*.json", "scripts/**/*.ts", "src/**/*.ts"] +} diff --git a/packages/skills-catalog/vitest.config.ts b/packages/skills-catalog/vitest.config.ts new file mode 100644 index 00000000..c1433e6e --- /dev/null +++ b/packages/skills-catalog/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.test.ts"], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 184d1623..f66c43d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -622,6 +622,8 @@ importers: specifier: ^5.7.3 version: 5.9.3 + packages/skills-catalog: {} + server: dependencies: '@aws-sdk/client-s3': diff --git a/server/src/__tests__/company-portability.test.ts b/server/src/__tests__/company-portability.test.ts index 46f30133..fd3a73fd 100644 --- a/server/src/__tests__/company-portability.test.ts +++ b/server/src/__tests__/company-portability.test.ts @@ -14,7 +14,6 @@ const companySvc = { const agentSvc = { list: vi.fn(), - getById: vi.fn(), create: vi.fn(), update: vi.fn(), }; @@ -29,7 +28,6 @@ const accessSvc = { const projectSvc = { list: vi.fn(), - getById: vi.fn(), create: vi.fn(), update: vi.fn(), createWorkspace: vi.fn(), @@ -67,26 +65,6 @@ const assetSvc = { const secretSvc = { normalizeAdapterConfigForPersistence: vi.fn(async (_companyId: string, config: Record) => config), resolveAdapterConfigForRuntime: vi.fn(async (_companyId: string, config: Record) => ({ config, secretKeys: new Set() })), - normalizeEnvBindingsForPersistence: vi.fn(async (_companyId: string, env: unknown) => env as Record), - getById: vi.fn(async (id: string) => { - if (id === "secret-1") return { id: "secret-1", name: "anthropic-api-key", provider: "local_encrypted" }; - if (id === "secret-2") return { id: "secret-2", name: "gh-token", provider: "local_encrypted" }; - return null; - }), - resolveSecretValue: vi.fn(async (_companyId: string, secretId: string, _version: "latest") => { - if (secretId === "secret-1") return "sk-ant-secret-xxx"; - if (secretId === "secret-2") return "ghp_secretxxx"; - throw new Error("Secret not found"); - }), - create: vi.fn(async (companyId: string, input: { name: string; provider: string; value: string; description?: string | null }) => ({ - id: `new-secret-${input.name}`, - companyId, - name: input.name, - provider: input.provider, - description: input.description ?? null, - latestVersion: 1, - })), - getByName: vi.fn(async (_companyId: string, name: string) => null), }; const agentInstructionsSvc = { @@ -138,25 +116,6 @@ vi.mock("../routes/org-chart-svg.js", () => ({ renderOrgChartPng: vi.fn(async () => Buffer.from("png")), })); -const gitSourceMock = vi.hoisted(() => ({ - resolveGitRef: vi.fn(), - openRepoSnapshot: vi.fn(), -})); - -// parseGitSourceUrl stays real (the shim parseGitHubSourceUrl delegates to it -// and is asserted by existing tests). Only the network-touching functions are -// overridable per-test. -vi.mock("../services/git-source.js", async () => { - const actual = await vi.importActual( - "../services/git-source.js", - ); - return { - ...actual, - resolveGitRef: gitSourceMock.resolveGitRef, - openRepoSnapshot: gitSourceMock.openRepoSnapshot, - }; -}); - const { companyPortabilityService, parseGitHubSourceUrl } = await import("../services/company-portability.js"); function asTextFile(entry: CompanyPortabilityFileEntry | undefined) { @@ -500,6 +459,7 @@ describe("company portability", () => { expect(extension).not.toContain("instructionsFilePath"); expect(extension).not.toContain("command:"); expect(extension).not.toContain("secretId"); + expect(extension).not.toContain('type: "secret_ref"'); expect(extension).toContain("inputs:"); expect(extension).toContain("ANTHROPIC_API_KEY:"); expect(extension).toContain('requirement: "optional"'); @@ -678,6 +638,106 @@ describe("company portability", () => { expect(asTextFile(exported.files["skills/paperclipai/paperclip/paperclip/references/api.md"])).toContain("# API"); }); + it("exports catalog skill provenance in portable Paperclip frontmatter", async () => { + const portability = companyPortabilityService({} as any); + const catalogKey = "paperclipai/bundled/software-development/review"; + const originHash = "sha256:catalog-origin"; + const catalogSkill = { + id: "skill-catalog", + companyId: "company-1", + key: catalogKey, + slug: "review", + name: "review", + description: "Catalog review skill", + markdown: "---\nname: review\ndescription: Catalog review skill\n---\n\n# Review\n", + sourceType: "catalog", + sourceLocator: "/tmp/paperclip/catalog/review", + sourceRef: originHash, + trustLevel: "markdown_only", + compatibility: "compatible", + fileInventory: [ + { path: "SKILL.md", kind: "skill" }, + { path: "references/checklist.md", kind: "reference" }, + ], + metadata: { + sourceKind: "catalog", + skillKey: catalogKey, + catalogId: "paperclipai:bundled:software-development:review", + catalogKey, + catalogKind: "bundled", + catalogCategory: "software-development", + catalogPath: "catalog/bundled/software-development/review", + packageName: "@paperclipai/skills-catalog", + packageVersion: "0.3.1", + originHash, + originVersion: "0.3.1", + originSnapshotLocator: "/tmp/local-only-origin", + installedHash: "sha256:installed", + userModifiedAt: "2026-05-01T00:00:00.000Z", + updateHoldReason: "local_modifications", + auditVerdict: "warning", + auditCodes: ["local_modifications"], + auditScannedAt: "2026-05-02T00:00:00.000Z", + auditScanVersion: "skills-audit-v1", + }, + }; + companySkillSvc.listFull.mockResolvedValue([catalogSkill]); + companySkillSvc.readFile.mockImplementation(async (_companyId: string, skillId: string, relativePath: string) => ({ + skillId, + path: relativePath, + kind: relativePath === "SKILL.md" ? "skill" : "reference", + content: relativePath === "SKILL.md" + ? "---\nname: review\ndescription: Catalog review skill\n---\n\n# Review\n" + : "# Checklist\n", + language: "markdown", + markdown: true, + editable: true, + })); + + const exported = await portability.exportBundle("company-1", { + include: { + company: false, + agents: false, + projects: false, + issues: false, + skills: true, + }, + expandReferencedSkills: true, + }); + + const skillMarkdown = asTextFile(exported.files["skills/paperclipai/bundled/software-development/review/SKILL.md"]); + expect(skillMarkdown).toContain("paperclip:"); + expect(skillMarkdown).toContain("catalog:"); + expect(skillMarkdown).toContain(`sourceRef: "${originHash}"`); + expect(skillMarkdown).toContain('catalogId: "paperclipai:bundled:software-development:review"'); + expect(skillMarkdown).toContain(`catalogKey: "${catalogKey}"`); + expect(skillMarkdown).toContain('catalogKind: "bundled"'); + expect(skillMarkdown).toContain('catalogPath: "catalog/bundled/software-development/review"'); + expect(skillMarkdown).toContain('packageName: "@paperclipai/skills-catalog"'); + expect(skillMarkdown).toContain('packageVersion: "0.3.1"'); + expect(skillMarkdown).toContain('installedHash: "sha256:installed"'); + expect(skillMarkdown).toContain('auditVerdict: "warning"'); + expect(skillMarkdown).not.toContain("originSnapshotLocator"); + expect(exported.manifest.skills[0]).toMatchObject({ + key: catalogKey, + sourceType: "catalog", + sourceRef: originHash, + metadata: expect.objectContaining({ + sourceKind: "catalog", + skillKey: catalogKey, + originHash, + catalogId: "paperclipai:bundled:software-development:review", + catalogKey, + catalogKind: "bundled", + catalogPath: "catalog/bundled/software-development/review", + packageName: "@paperclipai/skills-catalog", + packageVersion: "0.3.1", + installedHash: "sha256:installed", + auditCodes: ["local_modifications"], + }), + }); + }); + it("exports only selected skills when skills filter is provided", async () => { const portability = companyPortabilityService({} as any); @@ -1314,9 +1374,6 @@ describe("company portability", () => { requirement: "optional", defaultValue: "", portability: "portable", - secretName: "anthropic-api-key", - secretProvider: "local_encrypted", - type: "secret_ref", }, { key: "GH_TOKEN", @@ -1327,9 +1384,6 @@ describe("company portability", () => { requirement: "optional", defaultValue: "", portability: "portable", - secretName: "gh-token", - secretProvider: "local_encrypted", - type: "secret_ref", }, ]); }); @@ -1453,9 +1507,6 @@ describe("company portability", () => { requirement: "optional", defaultValue: "", portability: "portable", - secretName: null, - secretProvider: null, - type: "plain", }); }); @@ -3063,191 +3114,6 @@ describe("company portability", () => { })); }); - describe("secret env vars", () => { - beforeEach(() => { - // Reset create/getByName to ensure clean state per test - secretSvc.create.mockReset(); - secretSvc.getByName.mockReset(); - secretSvc.getById.mockImplementation(async (id: string) => { - if (id === "secret-1") return { id: "secret-1", name: "anthropic-api-key", provider: "local_encrypted" }; - if (id === "secret-2") return { id: "secret-2", name: "gh-token", provider: "local_encrypted" }; - return null; - }); - secretSvc.resolveSecretValue.mockImplementation(async (_companyId: string, secretId: string) => { - if (secretId === "secret-1") return "sk-ant-secret-xxx"; - if (secretId === "secret-2") return "ghp_secretxxx"; - throw new Error("Secret not found"); - }); - secretSvc.create.mockImplementation(async (companyId: string, input: { name: string; provider: string; value: string; description?: string | null }) => ({ - id: `new-secret-${input.name}`, - companyId, - name: input.name, - provider: input.provider, - description: input.description ?? null, - latestVersion: 1, - })); - secretSvc.getByName.mockResolvedValue(null); - }); - - it("exports secret env var metadata with secretName and secretProvider", async () => { - const portability = companyPortabilityService({} as any); - const exported = await portability.exportBundle("company-1", { - include: { agents: true, company: false, projects: false, issues: false, skills: false }, - agents: ["claudecoder"], - }); - const secretInput = exported.manifest.envInputs.find( - (e: any) => e.key === "ANTHROPIC_API_KEY" && e.kind === "secret", - ); - expect(secretInput).toBeDefined(); - expect(secretInput.secretName).toBe("anthropic-api-key"); - expect(secretInput.secretProvider).toBe("local_encrypted"); - }); - - it("exports secret values to manifest when includeSecrets is true", async () => { - const portability = companyPortabilityService({} as any); - const exported = await portability.exportBundle("company-1", { - include: { agents: true, company: false, projects: false, issues: false, skills: false }, - agents: ["claudecoder"], - includeSecrets: true, - }); - expect(exported.manifest.secrets).toBeDefined(); - expect(exported.manifest.secrets).toContainEqual(expect.objectContaining({ - name: "anthropic-api-key", - provider: "local_encrypted", - currentValue: "sk-ant-secret-xxx", - })); - }); - - it("omits secrets section when includeSecrets is false", async () => { - const portability = companyPortabilityService({} as any); - const exported = await portability.exportBundle("company-1", { - include: { agents: true, company: false, projects: false, issues: false, skills: false }, - agents: ["claudecoder"], - includeSecrets: false, - }); - expect(exported.manifest.secrets).toBeUndefined(); - }); - - it("writes placeholder when resolveSecretValue throws (cross-instance decryption failure)", async () => { - secretSvc.resolveSecretValue.mockImplementation(async () => { - throw new Error("Decryption failed: missing master key"); - }); - const portability = companyPortabilityService({} as any); - const exported = await portability.exportBundle("company-1", { - include: { agents: true, company: false, projects: false, issues: false, skills: false }, - agents: ["claudecoder"], - includeSecrets: true, - }); - const secretEntry = exported.manifest.secrets?.find((s: any) => s.name === "anthropic-api-key"); - expect(secretEntry?.currentValue).toBe(""); - expect(exported.warnings).toContainEqual(expect.stringContaining("could not be decrypted during export")); - }); - - it("imports secrets and remaps secret_ref bindings to new secret IDs", async () => { - const portability = companyPortabilityService({} as any); - agentSvc.create.mockImplementation(async (companyId: string, patch: Record) => ({ - id: "new-agent-1", - companyId, - ...patch, - })); - agentSvc.update.mockImplementation(async (id: string, patch: Record) => patch as any); - agentSvc.getById.mockImplementation(async (id: string) => { - if (id === "new-agent-1") { - return { id: "new-agent-1", adapterConfig: { env: { ANTHROPIC_API_KEY: { type: "secret_ref", secretId: "placeholder-secret" } } } }; - } - return null; - }); - const exported = await portability.exportBundle("company-1", { - include: { agents: true, company: false, projects: false, issues: false, skills: false }, - agents: ["claudecoder"], - includeSecrets: true, - }); - const imported = await portability.importBundle({ - source: { type: "inline", rootPath: exported.rootPath, files: exported.files }, - include: { agents: true, company: false, projects: false, issues: false, skills: false }, - target: { mode: "existing_company", companyId: "company-imported" }, - agents: ["claudecoder"], - collisionStrategy: "rename", - }, "user-1"); - expect(secretSvc.create).toHaveBeenCalled(); - expect(agentSvc.update).toHaveBeenCalledWith( - "new-agent-1", - expect.any(Object), - ); - }); - - it("reuses existing secret on conflict during import", async () => { - secretSvc.getByName.mockImplementation(async (_companyId: string, name: string) => { - if (name === "anthropic-api-key") return { id: "existing-secret-1", name, provider: "local_encrypted" }; - return null; - }); - const portability = companyPortabilityService({} as any); - agentSvc.create.mockImplementation(async (companyId: string, patch: Record) => ({ - id: "new-agent-1", - companyId, - ...patch, - })); - agentSvc.update.mockImplementation(async (id: string, patch: Record) => patch as any); - agentSvc.getById.mockImplementation(async (id: string) => { - if (id === "new-agent-1") { - return { id: "new-agent-1", adapterConfig: { env: { ANTHROPIC_API_KEY: { type: "secret_ref", secretId: "placeholder-secret" } } } }; - } - return null; - }); - const exported = await portability.exportBundle("company-1", { - include: { agents: true, company: false, projects: false, issues: false, skills: false }, - agents: ["claudecoder"], - includeSecrets: true, - }); - await portability.importBundle({ - source: { type: "inline", rootPath: exported.rootPath, files: exported.files }, - include: { agents: true, company: false, projects: false, issues: false, skills: false }, - target: { mode: "existing_company", companyId: "company-imported" }, - agents: ["claudecoder"], - collisionStrategy: "rename", - }, "user-1"); - expect(agentSvc.update).toHaveBeenCalled(); - }); - - it("exports plain env vars faithfully", async () => { - agentSvc.list.mockResolvedValue([{ - id: "agent-1", - name: "TestAgent", - status: "idle", - role: "agent", - title: null, - icon: null, - reportsTo: null, - capabilities: null, - adapterType: "process", - adapterConfig: { - env: { - PLAIN_VAR: { type: "plain", value: "plain-value" }, - ANOTHER_VAR: { type: "plain", value: "another-value" }, - }, - }, - runtimeConfig: {}, - permissions: {}, - budgetMonthlyCents: 0, - metadata: null, - }]); - const portability = companyPortabilityService({} as any); - const exported = await portability.exportBundle("company-1", { - include: { agents: true, company: false, projects: false, issues: false, skills: false }, - agents: ["testagent"], - }); - const plainInputs = exported.manifest.envInputs.filter((e: any) => e.kind === "plain"); - expect(plainInputs).toContainEqual(expect.objectContaining({ - key: "PLAIN_VAR", - defaultValue: "plain-value", - })); - expect(plainInputs).toContainEqual(expect.objectContaining({ - key: "ANOTHER_VAR", - defaultValue: "another-value", - })); - }); - }); - it("nameOverrides applied after collision detection do not re-validate uniqueness", async () => { const portability = companyPortabilityService({} as any); @@ -3398,173 +3264,3 @@ describe("company portability", () => { expect(preview.plan.issuePlans).toHaveLength(0); }); }); - -describe("git source orchestration via resolveSource", () => { - const minimalCompanyMarkdown = "---\ncompany:\n name: Demo\n---\n# Demo\n"; - const githubUrl = "https://git.example.com/acme/co?ref=main&path="; - - function makeSnapshot(overrides: { - files?: string[]; - fileContents?: Record; - binaryContents?: Record; - readBinaryReject?: Error; - } = {}) { - const files = overrides.files ?? ["COMPANY.md"]; - const fileContents = overrides.fileContents ?? { "COMPANY.md": minimalCompanyMarkdown }; - const binaryContents = overrides.binaryContents ?? {}; - return { - sha: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - listFiles: vi.fn(async () => files), - readFile: vi.fn(async (p: string) => { - if (p in fileContents) return fileContents[p]; - throw Object.assign(new Error(`not found: ${p}`), { code: "NotFoundError" }); - }), - readFileOptional: vi.fn(async (p: string) => fileContents[p] ?? null), - readBinary: vi.fn(async (p: string) => { - if (overrides.readBinaryReject) throw overrides.readBinaryReject; - if (p in binaryContents) return binaryContents[p]!; - throw Object.assign(new Error(`not found: ${p}`), { code: "NotFoundError" }); - }), - }; - } - - function setupResolveStub() { - gitSourceMock.resolveGitRef.mockResolvedValue({ - pinnedSha: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - trackingRef: "main", - }); - } - - beforeEach(() => { - gitSourceMock.resolveGitRef.mockReset(); - gitSourceMock.openRepoSnapshot.mockReset(); - companySvc.getById.mockResolvedValue(null); - agentSvc.list.mockResolvedValue([]); - projectSvc.list.mockResolvedValue([]); - issueSvc.list.mockResolvedValue([]); - issueSvc.listComments.mockResolvedValue([]); - companySkillSvc.list.mockResolvedValue([]); - }); - - it("opens a snapshot and walks the tree for a github source", async () => { - setupResolveStub(); - const snapshot = makeSnapshot({ - files: ["COMPANY.md", "README.md", "skills/x/SKILL.md"], - fileContents: { - "COMPANY.md": minimalCompanyMarkdown, - "README.md": "# readme", - "skills/x/SKILL.md": "---\nname: x\n---\n", - }, - }); - gitSourceMock.openRepoSnapshot.mockResolvedValue(snapshot); - - const portability = companyPortabilityService({} as any); - const preview = await portability.previewImport({ - source: { type: "github", url: githubUrl }, - include: { company: true, agents: false, projects: false, issues: false, skills: false }, - target: { mode: "new_company", newCompanyName: "Demo" }, - agents: "all", - collisionStrategy: "rename", - }); - - expect(gitSourceMock.resolveGitRef).toHaveBeenCalledTimes(1); - expect(gitSourceMock.openRepoSnapshot).toHaveBeenCalledTimes(1); - expect(snapshot.listFiles).toHaveBeenCalled(); - expect(snapshot.readFileOptional).toHaveBeenCalledWith("COMPANY.md"); - expect(snapshot.readFile).toHaveBeenCalledWith("README.md"); - expect(snapshot.readFile).toHaveBeenCalledWith("skills/x/SKILL.md"); - expect(preview.errors).toEqual([]); - }); - - it("falls back from main to master when the main ref does not exist", async () => { - setupResolveStub(); - const masterSnap = makeSnapshot(); - // First call (ref=main) rejects; second (ref=master) succeeds. - gitSourceMock.openRepoSnapshot - .mockRejectedValueOnce(new Error("ref not found")) - .mockResolvedValueOnce(masterSnap); - - const portability = companyPortabilityService({} as any); - const preview = await portability.previewImport({ - source: { type: "github", url: githubUrl }, - include: { company: true, agents: false, projects: false, issues: false, skills: false }, - target: { mode: "new_company", newCompanyName: "Demo" }, - agents: "all", - collisionStrategy: "rename", - }); - - expect(gitSourceMock.openRepoSnapshot).toHaveBeenCalledTimes(2); - expect(masterSnap.readFileOptional).toHaveBeenCalledWith("COMPANY.md"); - expect(preview.warnings).toContain("Git ref main not found; falling back to master."); - }); - - it("throws when COMPANY.md is missing on both main and master", async () => { - setupResolveStub(); - const emptySnap = makeSnapshot({ fileContents: {} }); - gitSourceMock.openRepoSnapshot.mockResolvedValue(emptySnap); - - const portability = companyPortabilityService({} as any); - await expect( - portability.previewImport({ - source: { type: "github", url: githubUrl }, - include: { company: true, agents: false, projects: false, issues: false, skills: false }, - target: { mode: "new_company", newCompanyName: "Demo" }, - agents: "all", - collisionStrategy: "rename", - }), - ).rejects.toThrow(/missing COMPANY.md/i); - }); - - it("fetches a referenced company logo as binary", async () => { - setupResolveStub(); - // logoPath lives in .paperclip.yaml (paperclip extension), not COMPANY.md. - const paperclipYaml = "company:\n logoPath: images/logo.png\n"; - const logoBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47]); - const snapshot = makeSnapshot({ - files: ["COMPANY.md", ".paperclip.yaml", "images/logo.png"], - fileContents: { - "COMPANY.md": minimalCompanyMarkdown, - ".paperclip.yaml": paperclipYaml, - }, - binaryContents: { "images/logo.png": logoBytes }, - }); - gitSourceMock.openRepoSnapshot.mockResolvedValue(snapshot); - - const portability = companyPortabilityService({} as any); - await portability.previewImport({ - source: { type: "github", url: githubUrl }, - include: { company: true, agents: false, projects: false, issues: false, skills: false }, - target: { mode: "new_company", newCompanyName: "Demo" }, - agents: "all", - collisionStrategy: "rename", - }); - - expect(snapshot.readBinary).toHaveBeenCalledWith("images/logo.png"); - }); - - it("warns instead of throwing when the logo blob can't be read", async () => { - setupResolveStub(); - const paperclipYaml = "company:\n logoPath: images/logo.png\n"; - const snapshot = makeSnapshot({ - files: ["COMPANY.md", ".paperclip.yaml"], - fileContents: { - "COMPANY.md": minimalCompanyMarkdown, - ".paperclip.yaml": paperclipYaml, - }, - readBinaryReject: new Error("blob missing"), - }); - gitSourceMock.openRepoSnapshot.mockResolvedValue(snapshot); - - const portability = companyPortabilityService({} as any); - const preview = await portability.previewImport({ - source: { type: "github", url: githubUrl }, - include: { company: true, agents: false, projects: false, issues: false, skills: false }, - target: { mode: "new_company", newCompanyName: "Demo" }, - agents: "all", - collisionStrategy: "rename", - }); - - expect(snapshot.readBinary).toHaveBeenCalled(); - expect(preview.warnings.some((w: string) => /Failed to fetch company logo/i.test(w))).toBe(true); - }); -}); diff --git a/server/src/__tests__/environment-runtime.test.ts b/server/src/__tests__/environment-runtime.test.ts index 3b8bf356..c850da81 100644 --- a/server/src/__tests__/environment-runtime.test.ts +++ b/server/src/__tests__/environment-runtime.test.ts @@ -216,7 +216,6 @@ describeEmbeddedPostgres("environmentRuntimeService", () => { return { companyId, - agentId, environment: { id: environmentId, companyId, @@ -1395,298 +1394,4 @@ describeEmbeddedPostgres("environmentRuntimeService", () => { expect(sshRelease).not.toHaveBeenCalled(); expect(acquired.lease.metadata?.driver).toBe("local"); }); - - // ------------------------------------------------------------------------- - // agentId is threaded through plugin RPC params (see protocol.ts — - // PluginEnvironmentAcquireLeaseParams.agentId and - // PluginEnvironmentResumeLeaseParams.agentId). Plugin-backed sandbox - // providers can use this to scope lease state (subdirs, PVCs, etc.) per - // agent without callbacks or DB lookups. The runtime must forward it when - // present and omit it when null/undefined so older plugin SDKs that don't - // declare the field aren't surprised. - // ------------------------------------------------------------------------- - - it("plugin-driver acquireLease: forwards agentId in the RPC payload when present", async () => { - const pluginId = randomUUID(); - const workerManager = { - isRunning: vi.fn(() => true), - call: vi.fn(async (_pluginId: string, method: string) => { - if (method === "environmentAcquireLease") { - return { providerLeaseId: "plugin-lease-agent", metadata: { remoteCwd: "/workspace" } }; - } - return undefined; - }), - } as unknown as PluginWorkerManager; - const runtimeWithPlugin = environmentRuntimeService(db, { pluginWorkerManager: workerManager }); - const { companyId, agentId, environment, runId } = await seedEnvironment({ - driver: "plugin", - name: "Plugin agentId fwd", - config: { - pluginKey: "acme.environments", - driverKey: "fake-plugin", - driverConfig: { template: "base" }, - }, - }); - await db.insert(plugins).values({ - id: pluginId, - pluginKey: "acme.environments", - packageName: "@acme/paperclip-environments", - version: "1.0.0", - apiVersion: 1, - categories: ["automation"], - manifestJson: { - id: "acme.environments", - apiVersion: 1, - version: "1.0.0", - displayName: "Acme", - description: "Test", - author: "Acme", - categories: ["automation"], - capabilities: ["environment.drivers.register"], - entrypoints: { worker: "dist/worker.js" }, - environmentDrivers: [{ driverKey: "fake-plugin", displayName: "Fake", configSchema: { type: "object" } }], - }, - status: "ready", - installOrder: 1, - updatedAt: new Date(), - } as any); - - await runtimeWithPlugin.acquireRunLease({ - companyId, - environment, - issueId: null, - agentId, - heartbeatRunId: runId, - persistedExecutionWorkspace: null, - }); - - expect(workerManager.call).toHaveBeenCalledWith( - pluginId, - "environmentAcquireLease", - expect.objectContaining({ agentId }), - ); - }); - - it("plugin-driver acquireLease: omits agentId from RPC payload when null", async () => { - const pluginId = randomUUID(); - const workerManager = { - isRunning: vi.fn(() => true), - call: vi.fn(async (_pluginId: string, method: string) => { - if (method === "environmentAcquireLease") { - return { providerLeaseId: "plugin-lease-no-agent", metadata: { remoteCwd: "/workspace" } }; - } - return undefined; - }), - } as unknown as PluginWorkerManager; - const runtimeWithPlugin = environmentRuntimeService(db, { pluginWorkerManager: workerManager }); - const { companyId, environment, runId } = await seedEnvironment({ - driver: "plugin", - name: "Plugin agentId null", - config: { - pluginKey: "acme.environments", - driverKey: "fake-plugin", - driverConfig: { template: "base" }, - }, - }); - await db.insert(plugins).values({ - id: pluginId, - pluginKey: "acme.environments", - packageName: "@acme/paperclip-environments", - version: "1.0.0", - apiVersion: 1, - categories: ["automation"], - manifestJson: { - id: "acme.environments", - apiVersion: 1, - version: "1.0.0", - displayName: "Acme", - description: "Test", - author: "Acme", - categories: ["automation"], - capabilities: ["environment.drivers.register"], - entrypoints: { worker: "dist/worker.js" }, - environmentDrivers: [{ driverKey: "fake-plugin", displayName: "Fake", configSchema: { type: "object" } }], - }, - status: "ready", - installOrder: 1, - updatedAt: new Date(), - } as any); - - await runtimeWithPlugin.acquireRunLease({ - companyId, - environment, - issueId: null, - agentId: null, - heartbeatRunId: runId, - persistedExecutionWorkspace: null, - }); - - const payload = (workerManager.call as ReturnType).mock.calls.find( - ([, method]) => method === "environmentAcquireLease", - )?.[2] as Record; - expect(payload).toBeDefined(); - expect(payload.agentId).toBeUndefined(); - expect("agentId" in payload).toBe(false); - }); - - it("sandbox-provider acquireLease: forwards agentId when present", async () => { - const pluginId = randomUUID(); - const workerManager = { - isRunning: vi.fn((id: string) => id === pluginId), - call: vi.fn(async (_pluginId: string, method: string) => { - if (method === "environmentAcquireLease") { - return { providerLeaseId: "sandbox-agent-1", metadata: { reuseLease: false } }; - } - throw new Error(`Unexpected plugin method: ${method}`); - }), - } as unknown as PluginWorkerManager; - const runtimeWithPlugin = environmentRuntimeService(db, { pluginWorkerManager: workerManager }); - const { companyId, agentId, environment, runId } = await seedEnvironment({ - driver: "sandbox", - name: "Sandbox agentId fwd", - config: { - provider: "fake-plugin", - image: "fake:test", - timeoutMs: 30_000, - reuseLease: false, - }, - }); - await db.insert(plugins).values({ - id: pluginId, - pluginKey: "acme.sandbox", - packageName: "@acme/paperclip-sandbox", - version: "1.0.0", - apiVersion: 1, - categories: ["automation"], - manifestJson: { - id: "acme.sandbox", - apiVersion: 1, - version: "1.0.0", - displayName: "Acme Sandbox", - description: "Test", - author: "Acme", - categories: ["automation"], - capabilities: ["environment.drivers.register"], - entrypoints: { worker: "dist/worker.js" }, - environmentDrivers: [{ - driverKey: "fake-plugin", - kind: "sandbox_provider", - displayName: "Fake", - configSchema: { type: "object" }, - }], - }, - status: "ready", - installOrder: 1, - updatedAt: new Date(), - } as any); - - await runtimeWithPlugin.acquireRunLease({ - companyId, - environment, - issueId: null, - agentId, - heartbeatRunId: runId, - persistedExecutionWorkspace: null, - }); - - expect(workerManager.call).toHaveBeenCalledWith( - pluginId, - "environmentAcquireLease", - expect.objectContaining({ agentId }), - expect.any(Number), - ); - }); - - it("sandbox-provider resumeLease: forwards agentId when present", async () => { - const pluginId = randomUUID(); - const calls: { method: string; params: Record }[] = []; - const workerManager = { - isRunning: vi.fn((id: string) => id === pluginId), - call: vi.fn(async (_pluginId: string, method: string, params: Record) => { - calls.push({ method, params }); - if (method === "environmentAcquireLease") { - return { providerLeaseId: "sandbox-resume-1", metadata: { reuseLease: true } }; - } - if (method === "environmentResumeLease") { - return { providerLeaseId: "sandbox-resume-1", metadata: { reuseLease: true } }; - } - throw new Error(`Unexpected plugin method: ${method}`); - }), - } as unknown as PluginWorkerManager; - const runtimeWithPlugin = environmentRuntimeService(db, { pluginWorkerManager: workerManager }); - const { companyId, agentId, environment, runId } = await seedEnvironment({ - driver: "sandbox", - name: "Sandbox agentId resume", - config: { - provider: "fake-plugin", - image: "fake:test", - timeoutMs: 30_000, - reuseLease: true, - }, - }); - await db.insert(plugins).values({ - id: pluginId, - pluginKey: "acme.sandbox", - packageName: "@acme/paperclip-sandbox", - version: "1.0.0", - apiVersion: 1, - categories: ["automation"], - manifestJson: { - id: "acme.sandbox", - apiVersion: 1, - version: "1.0.0", - displayName: "Acme Sandbox", - description: "Test", - author: "Acme", - categories: ["automation"], - capabilities: ["environment.drivers.register"], - entrypoints: { worker: "dist/worker.js" }, - environmentDrivers: [{ - driverKey: "fake-plugin", - kind: "sandbox_provider", - displayName: "Fake", - configSchema: { type: "object" }, - }], - }, - status: "ready", - installOrder: 1, - updatedAt: new Date(), - } as any); - - // First acquire seeds a reusable lease row in DB - await runtimeWithPlugin.acquireRunLease({ - companyId, - environment, - issueId: null, - agentId, - heartbeatRunId: runId, - persistedExecutionWorkspace: null, - }); - - // Second acquire on the same environment + reuseLease=true exercises the - // resume path (host's matcher finds the reusable lease, plugin's - // resumeLease is invoked). - const newRunId = randomUUID(); - await db.insert(heartbeatRuns).values({ - id: newRunId, - companyId, - agentId, - invocationSource: "manual", - status: "running", - createdAt: new Date(), - updatedAt: new Date(), - } as any); - await runtimeWithPlugin.acquireRunLease({ - companyId, - environment, - issueId: null, - agentId, - heartbeatRunId: newRunId, - persistedExecutionWorkspace: null, - }); - - const resumeCall = calls.find((c) => c.method === "environmentResumeLease"); - expect(resumeCall).toBeDefined(); - expect(resumeCall?.params.agentId).toBe(agentId); - }); }); diff --git a/server/src/__tests__/git-source.test.ts b/server/src/__tests__/git-source.test.ts deleted file mode 100644 index ce257817..00000000 --- a/server/src/__tests__/git-source.test.ts +++ /dev/null @@ -1,410 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -const listServerRefs = vi.fn(); -const cloneFn = vi.fn(); -const walkFn = vi.fn(); -const readBlobFn = vi.fn(); -const resolveRefFn = vi.fn(); -const treeFn = vi.fn((args: unknown) => ({ __tree: args })); - -vi.mock("isomorphic-git", () => ({ - default: { - listServerRefs: (...args: unknown[]) => listServerRefs(...args), - clone: (...args: unknown[]) => cloneFn(...args), - walk: (...args: unknown[]) => walkFn(...args), - readBlob: (...args: unknown[]) => readBlobFn(...args), - resolveRef: (...args: unknown[]) => resolveRefFn(...args), - TREE: (...args: unknown[]) => treeFn(...args), - }, -})); - -vi.mock("isomorphic-git/http/node", () => ({ - default: { request: vi.fn() }, -})); - -const { parseGitSourceUrl, resolveGitRef, openRepoSnapshot, buildCloneUrl } = - await import("../services/git-source.js"); - -beforeEach(() => { - listServerRefs.mockReset(); - cloneFn.mockReset(); - walkFn.mockReset(); - readBlobFn.mockReset(); - resolveRefFn.mockReset(); - treeFn.mockClear(); -}); - -afterEach(() => { - vi.restoreAllMocks(); -}); - -describe("parseGitSourceUrl", () => { - it("parses a bare github repo URL", () => { - expect(parseGitSourceUrl("https://github.com/anthropics/claude-code")).toMatchObject({ - cloneUrl: "https://github.com/anthropics/claude-code.git", - hostname: "github.com", - owner: "anthropics", - repo: "claude-code", - ref: null, - basePath: "", - filePath: null, - explicitRef: false, - }); - }); - - it("strips trailing .git from the repo segment", () => { - expect(parseGitSourceUrl("https://example.com/o/r.git")).toMatchObject({ - cloneUrl: "https://example.com/o/r.git", - repo: "r", - }); - }); - - it("parses a github tree URL with subpath", () => { - expect( - parseGitSourceUrl("https://github.com/o/r/tree/develop/sub/dir"), - ).toMatchObject({ - ref: "develop", - basePath: "sub/dir", - filePath: null, - explicitRef: true, - }); - }); - - it("parses a github blob URL as a file path", () => { - expect( - parseGitSourceUrl("https://github.com/o/r/blob/main/path/to/file.md"), - ).toMatchObject({ - ref: "main", - basePath: "path/to", - filePath: "path/to/file.md", - explicitRef: true, - }); - }); - - it("parses a gitea src/branch URL with subpath", () => { - expect( - parseGitSourceUrl("https://git.example.com/o/r/src/branch/main/skills"), - ).toMatchObject({ - cloneUrl: "https://git.example.com/o/r.git", - ref: "main", - basePath: "skills", - filePath: null, - explicitRef: true, - }); - }); - - it("parses a gitea src/tag URL", () => { - expect( - parseGitSourceUrl("https://git.example.com/o/r/src/tag/v1.2.3"), - ).toMatchObject({ - ref: "v1.2.3", - basePath: "", - explicitRef: true, - }); - }); - - it("parses a gitea src/commit URL with file", () => { - expect( - parseGitSourceUrl("https://git.example.com/o/r/src/commit/abc123/dir/SKILL.md"), - ).toMatchObject({ - ref: "abc123", - basePath: "dir", - filePath: "dir/SKILL.md", - }); - }); - - it("parses a gitlab tree URL", () => { - expect( - parseGitSourceUrl("https://gitlab.com/group/proj/-/tree/main/sub"), - ).toMatchObject({ - cloneUrl: "https://gitlab.com/group/proj.git", - ref: "main", - basePath: "sub", - explicitRef: true, - }); - }); - - it("parses a gitlab blob URL", () => { - expect( - parseGitSourceUrl("https://gitlab.com/group/proj/-/blob/main/sub/file.md"), - ).toMatchObject({ - ref: "main", - filePath: "sub/file.md", - basePath: "sub", - }); - }); - - it("rejects non-https URLs", () => { - expect(() => parseGitSourceUrl("http://github.com/o/r")).toThrow(/HTTPS/); - }); - - it("rejects URLs without owner/repo", () => { - expect(() => parseGitSourceUrl("https://github.com/o")).toThrow(); - }); - - it("rejects malformed URLs", () => { - expect(() => parseGitSourceUrl("not a url")).toThrow(); - }); - - it("parses a query-string URL with ?ref= and ?path=", () => { - expect( - parseGitSourceUrl("https://github.com/o/r?ref=feature%2Fdemo&path=subdir"), - ).toMatchObject({ - cloneUrl: "https://github.com/o/r.git", - ref: "feature/demo", - basePath: "subdir", - filePath: null, - explicitRef: true, - }); - }); - - it("parses a query-string URL with only ?ref=", () => { - expect(parseGitSourceUrl("https://github.com/o/r?ref=develop")).toMatchObject({ - ref: "develop", - basePath: "", - explicitRef: true, - }); - }); - - it("parses a query-string URL with only ?path=", () => { - expect(parseGitSourceUrl("https://github.com/o/r?path=sub")).toMatchObject({ - ref: null, - basePath: "sub", - explicitRef: false, - }); - }); - - it("query-string parsing takes precedence over path-style segments", () => { - expect( - parseGitSourceUrl("https://github.com/o/r/tree/main/old?ref=newref&path=newpath"), - ).toMatchObject({ - ref: "newref", - basePath: "newpath", - }); - }); -}); - -describe("buildCloneUrl", () => { - it("produces a .git suffix URL on the given host", () => { - expect(buildCloneUrl("git.example.com", "o", "r")).toBe( - "https://git.example.com/o/r.git", - ); - }); -}); - -describe("resolveGitRef", () => { - it("passes through a 40-hex SHA without hitting the network", async () => { - const parsed = parseGitSourceUrl( - "https://github.com/o/r/tree/0123456789abcdef0123456789abcdef01234567", - ); - const result = await resolveGitRef(parsed); - expect(result).toEqual({ - pinnedSha: "0123456789abcdef0123456789abcdef01234567", - trackingRef: "0123456789abcdef0123456789abcdef01234567", - }); - expect(listServerRefs).not.toHaveBeenCalled(); - }); - - it("returns default branch via HEAD symref when ref is absent", async () => { - listServerRefs.mockResolvedValue([ - { ref: "HEAD", oid: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", target: "refs/heads/main" }, - { ref: "refs/heads/main", oid: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, - { ref: "refs/heads/chore", oid: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, - ]); - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - const result = await resolveGitRef(parsed); - expect(result).toEqual({ - pinnedSha: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - trackingRef: "main", - }); - expect(listServerRefs).toHaveBeenCalledWith( - expect.objectContaining({ - url: "https://git.example.com/o/r.git", - symrefs: true, - protocolVersion: 2, - }), - ); - }); - - it("resolves a named branch to its SHA", async () => { - listServerRefs.mockResolvedValue([ - { ref: "HEAD", oid: "1111111111111111111111111111111111111111", target: "refs/heads/main" }, - { ref: "refs/heads/main", oid: "1111111111111111111111111111111111111111" }, - { ref: "refs/heads/develop", oid: "2222222222222222222222222222222222222222" }, - ]); - const parsed = parseGitSourceUrl("https://git.example.com/o/r/src/branch/develop"); - const result = await resolveGitRef(parsed); - expect(result).toEqual({ - pinnedSha: "2222222222222222222222222222222222222222", - trackingRef: "develop", - }); - }); - - it("prefers a peeled annotated tag over the tag object", async () => { - listServerRefs.mockResolvedValue([ - { ref: "refs/tags/v1.0", oid: "tttttttttttttttttttttttttttttttttttttttt" }, - { ref: "refs/tags/v1.0^{}", oid: "cccccccccccccccccccccccccccccccccccccccc" }, - ]); - const parsed = parseGitSourceUrl("https://git.example.com/o/r/src/tag/v1.0"); - const result = await resolveGitRef(parsed); - expect(result.pinnedSha).toBe("cccccccccccccccccccccccccccccccccccccccc"); - expect(result.trackingRef).toBe("v1.0"); - }); - - it("resolves a lightweight tag when no peeled entry exists", async () => { - listServerRefs.mockResolvedValue([ - { ref: "refs/tags/v2.0", oid: "dddddddddddddddddddddddddddddddddddddddd" }, - ]); - const parsed = parseGitSourceUrl("https://git.example.com/o/r/src/tag/v2.0"); - const result = await resolveGitRef(parsed); - expect(result.pinnedSha).toBe("dddddddddddddddddddddddddddddddddddddddd"); - }); - - it("throws when an explicit ref does not exist", async () => { - listServerRefs.mockResolvedValue([ - { ref: "HEAD", oid: "9999999999999999999999999999999999999999", target: "refs/heads/main" }, - { ref: "refs/heads/main", oid: "9999999999999999999999999999999999999999" }, - ]); - const parsed = parseGitSourceUrl("https://git.example.com/o/r/src/branch/missing"); - await expect(resolveGitRef(parsed)).rejects.toThrow(/Ref 'missing' not found/); - }); - - it("translates network errors into a user-facing message", async () => { - listServerRefs.mockRejectedValue(new Error("ENOTFOUND git.invalid")); - const parsed = parseGitSourceUrl("https://git.invalid/o/r"); - await expect(resolveGitRef(parsed)).rejects.toThrow(/could not connect/i); - }); - - it("translates 401 errors into an auth message", async () => { - listServerRefs.mockRejectedValue(new Error("HTTP Error: 401 Unauthorized")); - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - await expect(resolveGitRef(parsed)).rejects.toThrow(/authentication/i); - }); - - it("translates 404 errors into a repo-not-found message", async () => { - listServerRefs.mockRejectedValue(new Error("HTTP Error: 404 Not Found")); - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - await expect(resolveGitRef(parsed)).rejects.toThrow(/repository not found/i); - }); - - it("sends an onAuth callback when a token is supplied", async () => { - listServerRefs.mockResolvedValue([ - { ref: "HEAD", oid: "1111111111111111111111111111111111111111", target: "refs/heads/main" }, - { ref: "refs/heads/main", oid: "1111111111111111111111111111111111111111" }, - ]); - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - await resolveGitRef(parsed, "tok_abc"); - const callArgs = listServerRefs.mock.calls[0]![0] as { onAuth: () => unknown }; - expect(typeof callArgs.onAuth).toBe("function"); - expect(callArgs.onAuth()).toEqual({ username: "tok_abc", password: "x-oauth-basic" }); - }); - - it("omits onAuth when no token is supplied", async () => { - listServerRefs.mockResolvedValue([ - { ref: "HEAD", oid: "1111111111111111111111111111111111111111", target: "refs/heads/main" }, - { ref: "refs/heads/main", oid: "1111111111111111111111111111111111111111" }, - ]); - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - await resolveGitRef(parsed); - const callArgs = listServerRefs.mock.calls[0]![0] as { onAuth?: unknown }; - expect(callArgs.onAuth).toBeUndefined(); - }); -}); - -describe("openRepoSnapshot", () => { - it("clones at the tracking ref and walks the tree at the resolved SHA", async () => { - cloneFn.mockResolvedValue(undefined); - resolveRefFn.mockResolvedValue("ffffffffffffffffffffffffffffffffffffffff"); - walkFn.mockImplementation(async ({ map }: { map: (filepath: string, entries: Array<{ type: () => Promise }>) => Promise }) => { - await map(".", [{ type: () => Promise.resolve("tree") }]); - await map("README.md", [{ type: () => Promise.resolve("blob") }]); - await map("skills/x/SKILL.md", [{ type: () => Promise.resolve("blob") }]); - await map("skills/x", [{ type: () => Promise.resolve("tree") }]); - }); - readBlobFn.mockResolvedValue({ blob: new TextEncoder().encode("hello") }); - - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - const snap = await openRepoSnapshot(parsed, "main", "ffffffffffffffffffffffffffffffffffffffff", "tok"); - - expect(cloneFn).toHaveBeenCalledWith( - expect.objectContaining({ - url: "https://git.example.com/o/r.git", - ref: "main", - singleBranch: true, - depth: 1, - noCheckout: true, - }), - ); - expect(snap.sha).toBe("ffffffffffffffffffffffffffffffffffffffff"); - - const files = await snap.listFiles(); - expect(files).toEqual(["README.md", "skills/x/SKILL.md"]); - - const content = await snap.readFile("README.md"); - expect(content).toBe("hello"); - expect(readBlobFn).toHaveBeenCalledWith( - expect.objectContaining({ - oid: "ffffffffffffffffffffffffffffffffffffffff", - filepath: "README.md", - }), - ); - }); - - it("falls back to the expected SHA as ref when no tracking ref is known", async () => { - cloneFn.mockResolvedValue(undefined); - resolveRefFn.mockResolvedValue("abc1234567890abc1234567890abc1234567890a"); - walkFn.mockImplementation(async () => {}); - - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - await openRepoSnapshot(parsed, null, "abc1234567890abc1234567890abc1234567890a"); - - expect(cloneFn).toHaveBeenCalledWith( - expect.objectContaining({ ref: "abc1234567890abc1234567890abc1234567890a" }), - ); - }); - - it("surfaces a 404 from clone as repository-not-found", async () => { - cloneFn.mockRejectedValue(new Error("HTTP Error: 404 Not Found")); - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - await expect( - openRepoSnapshot(parsed, "main", "1111111111111111111111111111111111111111"), - ).rejects.toThrow(/repository not found/i); - }); - - it("readBinary returns the raw blob bytes", async () => { - cloneFn.mockResolvedValue(undefined); - resolveRefFn.mockResolvedValue("ffffffffffffffffffffffffffffffffffffffff"); - walkFn.mockImplementation(async () => {}); - const bytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47]); - readBlobFn.mockResolvedValue({ blob: bytes }); - - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - const snap = await openRepoSnapshot(parsed, "main", "ffffffffffffffffffffffffffffffffffffffff"); - const result = await snap.readBinary("logo.png"); - expect(result).toBe(bytes); - }); - - it("readFileOptional returns null on NotFoundError", async () => { - cloneFn.mockResolvedValue(undefined); - resolveRefFn.mockResolvedValue("ffffffffffffffffffffffffffffffffffffffff"); - walkFn.mockImplementation(async () => {}); - const err = Object.assign(new Error("missing"), { code: "NotFoundError" }); - readBlobFn.mockRejectedValue(err); - - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - const snap = await openRepoSnapshot(parsed, "main", "ffffffffffffffffffffffffffffffffffffffff"); - const result = await snap.readFileOptional("missing.md"); - expect(result).toBeNull(); - }); - - it("readFileOptional rethrows non-NotFound errors", async () => { - cloneFn.mockResolvedValue(undefined); - resolveRefFn.mockResolvedValue("ffffffffffffffffffffffffffffffffffffffff"); - walkFn.mockImplementation(async () => {}); - readBlobFn.mockRejectedValue(new Error("disk explosion")); - - const parsed = parseGitSourceUrl("https://git.example.com/o/r"); - const snap = await openRepoSnapshot(parsed, "main", "ffffffffffffffffffffffffffffffffffffffff"); - await expect(snap.readFileOptional("any.md")).rejects.toThrow(/disk explosion/); - }); -}); diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index 7c0f9ec9..124a51d9 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -1218,9 +1218,13 @@ export function agentRoutes( companyId: string, adapterType: string, config: Record, + options: { + materializeMissing?: boolean; + } = {}, ) { const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(companyId, { - materializeMissing: shouldMaterializeRuntimeSkillsForAdapter(adapterType), + materializeMissing: options.materializeMissing + ?? shouldMaterializeRuntimeSkillsForAdapter(adapterType), }); return { ...config, @@ -1487,6 +1491,7 @@ export function agentRoutes( agent.companyId, agent.adapterType, runtimeConfig, + { materializeMissing: false }, ); const snapshot = await adapter.listSkills({ agentId: agent.id, diff --git a/server/src/routes/company-skills.ts b/server/src/routes/company-skills.ts index 2ed51987..1fb82298 100644 --- a/server/src/routes/company-skills.ts +++ b/server/src/routes/company-skills.ts @@ -1,17 +1,21 @@ import { Router, type Request } from "express"; import type { Db } from "@paperclipai/db"; import { + catalogSkillListQuerySchema, companySkillCreateSchema, companySkillFileUpdateSchema, companySkillImportSchema, - companySkillUpdateAuthSchema, + companySkillInstallCatalogSchema, + companySkillInstallUpdateSchema, companySkillProjectScanRequestSchema, + companySkillResetSchema, } from "@paperclipai/shared"; import { trackSkillImported } from "@paperclipai/shared/telemetry"; import { validate } from "../middleware/validate.js"; import { accessService, agentService, companySkillService, logActivity } from "../services/index.js"; +import { getCatalogSkillOrThrow, listCatalogSkills, readCatalogSkillFile } from "../services/skills-catalog.js"; import { forbidden } from "../errors.js"; -import { assertCompanyAccess, getActorInfo } from "./authz.js"; +import { assertAuthenticated, assertCompanyAccess, getActorInfo } from "./authz.js"; import { getTelemetryClient } from "../telemetry.js"; type SkillTelemetryInput = { @@ -33,6 +37,12 @@ export function companySkillRoutes(db: Db) { return Boolean((agent.permissions as Record).canCreateAgents); } + function asString(value: unknown): string | null { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; + } + function deriveTrackedSkillRef(skill: SkillTelemetryInput): string | null { if (skill.sourceType === "skills_sh") { return skill.key; @@ -40,9 +50,19 @@ export function companySkillRoutes(db: Db) { if (skill.sourceType !== "github") { return null; } + const hostname = asString(skill.metadata?.hostname); + if (hostname !== "github.com") { + return null; + } return skill.key; } + function firstQueryString(value: unknown): string | undefined { + if (typeof value === "string") return value; + if (Array.isArray(value) && typeof value[0] === "string") return value[0]; + return undefined; + } + async function assertCanMutateCompanySkills(req: Request, companyId: string) { assertCompanyAccess(req, companyId); @@ -72,6 +92,29 @@ export function companySkillRoutes(db: Db) { throw forbidden("Missing permission: can create agents"); } + router.get("/skills/catalog", async (req, res) => { + assertAuthenticated(req); + const query = catalogSkillListQuerySchema.parse({ + kind: firstQueryString(req.query.kind), + category: firstQueryString(req.query.category), + q: firstQueryString(req.query.q), + }); + res.json(listCatalogSkills(query)); + }); + + router.get("/skills/catalog/:catalogId/files", async (req, res) => { + assertAuthenticated(req); + const catalogRef = firstQueryString(req.query.ref) ?? (req.params.catalogId as string); + const relativePath = firstQueryString(req.query.path) ?? "SKILL.md"; + res.json(await readCatalogSkillFile(catalogRef, relativePath)); + }); + + router.get("/skills/catalog/:catalogId", async (req, res) => { + assertAuthenticated(req); + const catalogRef = firstQueryString(req.query.ref) ?? (req.params.catalogId as string); + res.json(getCatalogSkillOrThrow(catalogRef)); + }); + router.get("/companies/:companyId/skills", async (req, res) => { const companyId = req.params.companyId as string; assertCompanyAccess(req, companyId); @@ -185,8 +228,7 @@ export function companySkillRoutes(db: Db) { const companyId = req.params.companyId as string; await assertCanMutateCompanySkills(req, companyId); const source = String(req.body.source ?? ""); - const authToken = typeof req.body.authToken === "string" ? req.body.authToken.trim() : undefined; - const result = await svc.importFromSource(companyId, source, authToken || undefined); + const result = await svc.importFromSource(companyId, source); const actor = getActorInfo(req); await logActivity(db, { @@ -219,6 +261,38 @@ export function companySkillRoutes(db: Db) { }, ); + router.post( + "/companies/:companyId/skills/install-catalog", + validate(companySkillInstallCatalogSchema), + async (req, res) => { + const companyId = req.params.companyId as string; + await assertCanMutateCompanySkills(req, companyId); + const result = await svc.installFromCatalog(companyId, req.body); + + const actor = getActorInfo(req); + await logActivity(db, { + companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: result.action === "created" ? "company.skill_catalog_installed" : "company.skill_catalog_updated", + entityType: "company_skill", + entityId: result.skill.id, + details: { + action: result.action, + catalogId: result.catalogSkill.id, + catalogKey: result.catalogSkill.key, + slug: result.skill.slug, + originHash: result.catalogSkill.contentHash, + warningCount: result.warnings.length, + }, + }); + + res.status(result.action === "created" ? 201 : 200).json(result); + }, + ); + router.post( "/companies/:companyId/skills/scan-projects", validate(companySkillProjectScanRequestSchema), @@ -281,44 +355,13 @@ export function companySkillRoutes(db: Db) { res.json(result); }); - router.post("/companies/:companyId/skills/:skillId/install-update", async (req, res) => { - const companyId = req.params.companyId as string; - const skillId = req.params.skillId as string; - await assertCanMutateCompanySkills(req, companyId); - const result = await svc.installUpdate(companyId, skillId); - if (!result) { - res.status(404).json({ error: "Skill not found" }); - return; - } - - const actor = getActorInfo(req); - await logActivity(db, { - companyId, - actorType: actor.actorType, - actorId: actor.actorId, - agentId: actor.agentId, - runId: actor.runId, - action: "company.skill_update_installed", - entityType: "company_skill", - entityId: result.id, - details: { - slug: result.slug, - sourceRef: result.sourceRef, - }, - }); - - res.json(result); - }); - - router.patch( - "/companies/:companyId/skills/:skillId/auth", - validate(companySkillUpdateAuthSchema), + router.post( + "/companies/:companyId/skills/:skillId/audit", async (req, res) => { const companyId = req.params.companyId as string; const skillId = req.params.skillId as string; await assertCanMutateCompanySkills(req, companyId); - const authToken = req.body.authToken as string | null; - const result = await svc.updateSkillAuth(companyId, skillId, authToken); + const result = await svc.auditSkill(companyId, skillId); if (!result) { res.status(404).json({ error: "Skill not found" }); return; @@ -331,11 +374,95 @@ export function companySkillRoutes(db: Db) { actorId: actor.actorId, agentId: actor.agentId, runId: actor.runId, - action: authToken ? "company.skill_auth_updated" : "company.skill_auth_removed", + action: "company.skill_audited", + entityType: "company_skill", + entityId: skillId, + details: { + verdict: result.verdict, + codes: result.codes, + installedHash: result.installedHash, + originHash: result.originHash, + scanVersion: result.scanVersion, + }, + }); + + res.json(result); + }, + ); + + router.post( + "/companies/:companyId/skills/:skillId/install-update", + validate(companySkillInstallUpdateSchema), + async (req, res) => { + const companyId = req.params.companyId as string; + const skillId = req.params.skillId as string; + await assertCanMutateCompanySkills(req, companyId); + const before = await svc.getById(companyId, skillId); + const result = await svc.installUpdate(companyId, skillId, req.body); + if (!result) { + res.status(404).json({ error: "Skill not found" }); + return; + } + + const actor = getActorInfo(req); + await logActivity(db, { + companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "company.skill_update_installed", entityType: "company_skill", entityId: result.id, details: { slug: result.slug, + previousOriginHash: before?.metadata?.originHash ?? before?.sourceRef ?? null, + previousOriginVersion: before?.metadata?.originVersion ?? null, + newOriginHash: result.metadata?.originHash ?? result.sourceRef, + newOriginVersion: result.metadata?.originVersion ?? null, + driftDetected: Boolean(before?.metadata?.userModifiedAt), + force: Boolean(req.body.force), + auditVerdict: result.metadata?.auditVerdict ?? null, + }, + }); + + res.json(result); + }, + ); + + router.post( + "/companies/:companyId/skills/:skillId/reset", + validate(companySkillResetSchema), + async (req, res) => { + const companyId = req.params.companyId as string; + const skillId = req.params.skillId as string; + await assertCanMutateCompanySkills(req, companyId); + const before = await svc.getById(companyId, skillId); + const result = await svc.resetSkill(companyId, skillId, req.body); + if (!result) { + res.status(404).json({ error: "Skill not found" }); + return; + } + + const actor = getActorInfo(req); + await logActivity(db, { + companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "company.skill_reset", + entityType: "company_skill", + entityId: result.id, + details: { + slug: result.slug, + previousOriginHash: before?.metadata?.originHash ?? before?.sourceRef ?? null, + previousOriginVersion: before?.metadata?.originVersion ?? null, + newOriginHash: result.metadata?.originHash ?? result.sourceRef, + newOriginVersion: result.metadata?.originVersion ?? null, + driftDetected: Boolean(before?.metadata?.userModifiedAt), + force: Boolean(req.body.force), + auditVerdict: result.metadata?.auditVerdict ?? null, }, }); diff --git a/server/src/services/catalog-provenance.ts b/server/src/services/catalog-provenance.ts new file mode 100644 index 00000000..5321fe7b --- /dev/null +++ b/server/src/services/catalog-provenance.ts @@ -0,0 +1,65 @@ +export const PORTABLE_CATALOG_PROVENANCE_STRING_KEYS = [ + "sourceRef", + "originHash", + "catalogId", + "catalogKey", + "catalogKind", + "catalogCategory", + "catalogPath", + "packageName", + "packageVersion", + "originVersion", + "installedHash", + "userModifiedAt", + "updateHoldReason", + "auditVerdict", + "auditScannedAt", + "auditScanVersion", +] as const; + +function asCatalogString(value: unknown) { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +export function readCatalogStringList(value: unknown) { + if (!Array.isArray(value)) return null; + const entries = value.map((entry) => asCatalogString(entry)).filter((entry): entry is string => Boolean(entry)); + return entries.length === value.length ? entries : null; +} + +function isCatalogRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +export function readPortableCatalogProvenance( + metadata: Record | null, + canonicalKey: string | null = null, +) { + const paperclip = isCatalogRecord(metadata?.paperclip) ? metadata.paperclip : null; + const catalog = isCatalogRecord(paperclip?.catalog) ? paperclip.catalog : null; + if (!catalog) return null; + + const sourceRef = asCatalogString(catalog.sourceRef) ?? asCatalogString(catalog.originHash); + const normalized: Record = { + ...(canonicalKey ? { skillKey: canonicalKey } : {}), + sourceKind: "catalog", + }; + const catalogSkillKey = asCatalogString(catalog.skillKey); + if (!canonicalKey && catalogSkillKey) normalized.skillKey = catalogSkillKey; + + for (const key of PORTABLE_CATALOG_PROVENANCE_STRING_KEYS) { + if (key === "sourceRef") continue; + const value = asCatalogString(catalog[key]); + if (value) normalized[key] = value; + } + if (sourceRef && !normalized.originHash) normalized.originHash = sourceRef; + const auditCodes = readCatalogStringList(catalog.auditCodes); + if (auditCodes) normalized.auditCodes = auditCodes; + + return { + sourceRef, + metadata: normalized, + }; +} diff --git a/server/src/services/company-portability.ts b/server/src/services/company-portability.ts index f44ac3dc..8646f664 100644 --- a/server/src/services/company-portability.ts +++ b/server/src/services/company-portability.ts @@ -27,11 +27,9 @@ import type { CompanyPortabilityIssueManifestEntry, CompanyPortabilitySidebarOrder, CompanyPortabilitySkillManifestEntry, - CompanyPortabilitySecretEntry, CompanySkill, AgentEnvConfig, RoutineVariable, - SecretProvider, } from "@paperclipai/shared"; import { AGENT_DEFAULT_MAX_CONCURRENT_RUNS, @@ -56,8 +54,8 @@ import { } from "@paperclipai/adapter-utils/server-utils"; import { requireOpenCodeModelId } from "@paperclipai/adapter-opencode-local/server"; import { findServerAdapter } from "../adapters/index.js"; -import { forbidden, HttpError, notFound, unprocessable } from "../errors.js"; -import { openRepoSnapshot, parseGitSourceUrl, resolveGitRef, type RepoSnapshot } from "./git-source.js"; +import { forbidden, notFound, unprocessable } from "../errors.js"; +import { ghFetch, gitHubApiBase, resolveRawGitHubUrl } from "./github-fetch.js"; import type { StorageService } from "../storage/types.js"; import { accessService } from "./access.js"; import { agentService } from "./agents.js"; @@ -72,6 +70,12 @@ import { issueService } from "./issues.js"; import { projectService } from "./projects.js"; import { routineService } from "./routines.js"; import { secretService } from "./secrets.js"; +import { + PORTABLE_CATALOG_PROVENANCE_STRING_KEYS, + readCatalogStringList, + readPortableCatalogProvenance, +} from "./catalog-provenance.js"; +import { normalizePortablePath } from "./portable-path.js"; /** Build OrgNode tree from manifest agent list (slug + reportsToSlug). */ function buildOrgTreeFromManifest(agents: CompanyPortabilityManifest["agents"]): OrgNode[] { @@ -230,6 +234,28 @@ function readSkillSourceKind(skill: CompanySkill) { return asString(metadata?.sourceKind); } +function buildPortableCatalogProvenance(skill: CompanySkill) { + if (skill.sourceType !== "catalog") return null; + const metadata = isPlainRecord(skill.metadata) ? skill.metadata : null; + const provenance: Record = { + skillKey: skill.key, + }; + + const sourceRef = asString(skill.sourceRef) ?? asString(metadata?.originHash); + if (sourceRef) provenance.sourceRef = sourceRef; + + for (const key of PORTABLE_CATALOG_PROVENANCE_STRING_KEYS) { + if (key === "sourceRef") continue; + const value = asString(metadata?.[key]); + if (value) provenance[key] = value; + } + + const auditCodes = readCatalogStringList(metadata?.auditCodes); + if (auditCodes) provenance.auditCodes = auditCodes; + + return Object.keys(provenance).length > 1 ? provenance : null; +} + function deriveLocalExportNamespace(skill: CompanySkill, slug: string) { const metadata = isPlainRecord(skill.metadata) ? skill.metadata : null; const candidates = [ @@ -405,7 +431,7 @@ function normalizePortableProjectEnv(value: unknown): AgentEnvConfig | null { return parsed.success ? parsed.data : null; } -async function extractPortableScopedEnvInputs( +function extractPortableScopedEnvInputs( scope: { label: string; warningPrefix: string; @@ -414,11 +440,7 @@ async function extractPortableScopedEnvInputs( }, envValue: unknown, warnings: string[], - secrets: { getById: (id: string) => Promise<{ name: string; provider: string; description: string | null; latestVersion: number } | null>; resolveSecretValue: (companyId: string, secretId: string, version: "latest") => Promise }, - secretEntries: CompanyPortabilitySecretEntry[], - includeSecrets: boolean, - companyId: string, -): Promise { +): CompanyPortabilityEnvInput[] { if (!isPlainRecord(envValue)) return []; const env = envValue as Record; const inputs: CompanyPortabilityEnvInput[] = []; @@ -430,7 +452,6 @@ async function extractPortableScopedEnvInputs( } if (isPlainRecord(binding) && binding.type === "secret_ref") { - const secret = await secrets.getById(String(binding.secretId)); inputs.push({ key, description: `Provide ${key} for ${scope.label}`, @@ -440,33 +461,7 @@ async function extractPortableScopedEnvInputs( requirement: "optional", defaultValue: "", portability: "portable", - secretName: secret?.name ?? null, - secretProvider: secret?.provider ?? null, }); - if (includeSecrets && secret && binding.secretId) { - const alreadyExported = secretEntries.some((e) => e.name === secret.name); - if (!alreadyExported) { - try { - const resolvedValue = await secrets.resolveSecretValue(companyId, String(binding.secretId), "latest"); - secretEntries.push({ - name: secret.name, - provider: secret.provider as SecretProvider, - description: secret.description, - latestVersion: secret.latestVersion, - currentValue: resolvedValue, - }); - } catch { - secretEntries.push({ - name: secret.name, - provider: secret.provider as SecretProvider, - description: secret.description, - latestVersion: secret.latestVersion, - currentValue: ``, - }); - warnings.push(`Secret "${secret.name}" could not be decrypted during export. Placeholder written.`); - } - } - } continue; } @@ -476,6 +471,9 @@ async function extractPortableScopedEnvInputs( const portability = defaultValue && isAbsoluteCommand(defaultValue) ? "system_dependent" : "portable"; + if (portability === "system_dependent") { + warnings.push(`${scope.warningPrefix} env ${key} default was exported as system-dependent.`); + } inputs.push({ key, description: `Optional default for ${key} on ${scope.label}`, @@ -491,6 +489,9 @@ async function extractPortableScopedEnvInputs( if (typeof binding === "string") { const portability = isAbsoluteCommand(binding) ? "system_dependent" : "portable"; + if (portability === "system_dependent") { + warnings.push(`${scope.warningPrefix} env ${key} default was exported as system-dependent.`); + } inputs.push({ key, description: `Optional default for ${key} on ${scope.label}`, @@ -598,14 +599,11 @@ type AgentLike = { }; type EnvInputRecord = { - type?: "secret_ref" | "plain"; kind: "secret" | "plain"; requirement: "required" | "optional"; default?: string | null; description?: string | null; portability?: "portable" | "system_dependent"; - secretName?: string | null; - secretProvider?: string | null; }; const COMPANY_LOGO_CONTENT_TYPE_EXTENSIONS: Record = { @@ -1445,20 +1443,6 @@ function normalizeInclude(input?: Partial): CompanyPo }; } -function normalizePortablePath(input: string) { - const normalized = input.replace(/\\/g, "/").replace(/^\.\/+/, ""); - const parts: string[] = []; - for (const segment of normalized.split("/")) { - if (!segment || segment === ".") continue; - if (segment === "..") { - if (parts.length > 0) parts.pop(); - continue; - } - parts.push(segment); - } - return parts.join("/"); -} - function resolvePortablePath(fromPath: string, targetPath: string) { const baseDir = path.posix.dirname(fromPath.replace(/\\/g, "/")); return normalizePortablePath(path.posix.join(baseDir, targetPath.replace(/\\/g, "/"))); @@ -1747,15 +1731,11 @@ function isAbsoluteCommand(value: string) { return path.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value); } -async function extractPortableEnvInputs( +function extractPortableEnvInputs( agentSlug: string, envValue: unknown, warnings: string[], - secrets: { getById: (id: string) => Promise<{ name: string; provider: string; description: string | null; latestVersion: number } | null>; resolveSecretValue: (companyId: string, secretId: string, version: "latest") => Promise }, - secretEntries: CompanyPortabilitySecretEntry[], - includeSecrets: boolean, - companyId: string, -): Promise { +): CompanyPortabilityEnvInput[] { return extractPortableScopedEnvInputs( { label: `agent ${agentSlug}`, @@ -1765,22 +1745,14 @@ async function extractPortableEnvInputs( }, envValue, warnings, - secrets, - secretEntries, - includeSecrets, - companyId, ); } -async function extractPortableProjectEnvInputs( +function extractPortableProjectEnvInputs( projectSlug: string, envValue: unknown, warnings: string[], - secrets: { getById: (id: string) => Promise<{ name: string; provider: string; description: string | null; latestVersion: number } | null>; resolveSecretValue: (companyId: string, secretId: string, version: "latest") => Promise }, - secretEntries: CompanyPortabilitySecretEntry[], - includeSecrets: boolean, - companyId: string, -): Promise { +): CompanyPortabilityEnvInput[] { return extractPortableScopedEnvInputs( { label: `project ${projectSlug}`, @@ -1790,10 +1762,6 @@ async function extractPortableProjectEnvInputs( }, envValue, warnings, - secrets, - secretEntries, - includeSecrets, - companyId, ); } @@ -2172,12 +2140,14 @@ async function withSkillSourceMetadata(skill: CompanySkill, markdown: string) { if (sourceEntry) { metadata.sources = [...existingSources, sourceEntry]; } + const catalogProvenance = buildPortableCatalogProvenance(skill); metadata.skillKey = skill.key; metadata.paperclipSkillKey = skill.key; metadata.paperclip = { ...(isPlainRecord(metadata.paperclip) ? metadata.paperclip : {}), skillKey: skill.key, slug: skill.slug, + ...(catalogProvenance ? { catalog: catalogProvenance } : {}), }; const frontmatter = { ...parsed.frontmatter, @@ -2339,6 +2309,42 @@ function parseFrontmatterMarkdown(raw: string): MarkdownDoc { }; } +async function fetchText(url: string) { + const response = await ghFetch(url); + if (!response.ok) { + throw unprocessable(`Failed to fetch ${url}: ${response.status}`); + } + return response.text(); +} + +async function fetchOptionalText(url: string) { + const response = await ghFetch(url); + if (response.status === 404) return null; + if (!response.ok) { + throw unprocessable(`Failed to fetch ${url}: ${response.status}`); + } + return response.text(); +} + +async function fetchBinary(url: string) { + const response = await ghFetch(url); + if (!response.ok) { + throw unprocessable(`Failed to fetch ${url}: ${response.status}`); + } + return Buffer.from(await response.arrayBuffer()); +} + +async function fetchJson(url: string): Promise { + const response = await ghFetch(url, { + headers: { + accept: "application/vnd.github+json", + }, + }); + if (!response.ok) { + throw unprocessable(`Failed to fetch ${url}: ${response.status}`); + } + return response.json() as Promise; +} function dedupeEnvInputs(values: CompanyPortabilityManifest["envInputs"]) { const seen = new Set(); @@ -2362,13 +2368,6 @@ function buildEnvInputMap(inputs: CompanyPortabilityEnvInput[]) { if (input.defaultValue !== null) entry.default = input.defaultValue; if (input.description) entry.description = input.description; if (input.portability === "system_dependent") entry.portability = "system_dependent"; - if (input.secretName) { - entry.secretName = input.secretName; - entry.type = "secret_ref"; - } else { - entry.type = "plain"; - } - if (input.secretProvider) entry.secretProvider = input.secretProvider; env[input.key] = entry; } return env; @@ -2413,9 +2412,6 @@ function readAgentEnvInputs( requirement: record.requirement === "required" ? "required" : "optional", defaultValue: typeof record.default === "string" ? record.default : null, portability: record.portability === "system_dependent" ? "system_dependent" : "portable", - secretName: record.secretName ?? null, - secretProvider: record.secretProvider ?? null, - type: record.type, }]; }); } @@ -2440,9 +2436,6 @@ function readProjectEnvInputs( requirement: record.requirement === "required" ? "required" : "optional", defaultValue: typeof record.default === "string" ? record.default : null, portability: record.portability === "system_dependent" ? "system_dependent" : "portable", - secretName: record.secretName ?? null, - secretProvider: record.secretProvider ?? null, - type: record.type, }]; }); } @@ -2489,7 +2482,6 @@ function buildManifestFromPackageFiles( const paperclipProjects = isPlainRecord(paperclipExtension.projects) ? paperclipExtension.projects : {}; const paperclipTasks = isPlainRecord(paperclipExtension.tasks) ? paperclipExtension.tasks : {}; const paperclipRoutines = isPlainRecord(paperclipExtension.routines) ? paperclipExtension.routines : {}; - const paperclipSecrets = Array.isArray(paperclipExtension.secrets) ? paperclipExtension.secrets : []; const companyName = asString(companyFrontmatter.name) ?? opts?.sourceLabel?.companyName @@ -2573,7 +2565,6 @@ function buildManifestFromPackageFiles( projects: [], issues: [], envInputs: [], - secrets: paperclipSecrets.length > 0 ? paperclipSecrets : undefined, }; const warnings: string[] = []; @@ -2693,10 +2684,17 @@ function buildManifestFromPackageFiles( normalizedMetadata = { sourceKind: "url", }; - } else if (metadata) { - normalizedMetadata = { - sourceKind: "catalog", - }; + } else { + const catalogProvenance = readPortableCatalogProvenance(metadata); + if (catalogProvenance) { + sourceType = "catalog"; + sourceRef = catalogProvenance.sourceRef; + normalizedMetadata = catalogProvenance.metadata; + } else if (metadata) { + normalizedMetadata = { + sourceKind: "catalog", + }; + } } const key = deriveManifestSkillKey(frontmatter, slug, normalizedMetadata, sourceType, sourceLocator); @@ -2828,37 +2826,52 @@ function normalizeGitHubSourcePath(value: string | null | undefined) { export function parseGitHubSourceUrl(rawUrl: string) { const url = new URL(rawUrl); - // Handle the portability-specific companyPath query param before delegating, - // since git-source has no notion of it. - const queryCompanyPath = normalizeGitHubSourcePath(url.searchParams.get("companyPath")); - - const parsed = parseGitSourceUrl(rawUrl); - - let companyPath: string; - let basePath = parsed.basePath; - if (queryCompanyPath) { - companyPath = queryCompanyPath; - if (!basePath) { - const derived = path.posix.dirname(companyPath); - basePath = derived === "." ? "" : derived; - } - } else if (parsed.filePath) { - // blob-style URL pointed directly at a file - companyPath = parsed.filePath; - } else if (basePath) { - companyPath = `${basePath}/COMPANY.md`; - } else { - companyPath = "COMPANY.md"; + if (url.protocol !== "https:") { + throw unprocessable("GitHub source URL must use HTTPS"); } - - return { - hostname: parsed.hostname, - owner: parsed.owner, - repo: parsed.repo, - ref: parsed.ref ?? "main", - basePath, - companyPath, - }; + const hostname = url.hostname; + const parts = url.pathname.split("/").filter(Boolean); + if (parts.length < 2) { + throw unprocessable("Invalid GitHub URL"); + } + const owner = parts[0]!; + const repo = parts[1]!.replace(/\.git$/i, ""); + const queryRef = url.searchParams.get("ref")?.trim(); + const queryPath = normalizeGitHubSourcePath(url.searchParams.get("path")); + const queryCompanyPath = normalizeGitHubSourcePath(url.searchParams.get("companyPath")); + if (queryRef || queryPath || queryCompanyPath) { + const companyPath = queryCompanyPath || [queryPath, "COMPANY.md"].filter(Boolean).join("/") || "COMPANY.md"; + let basePath = queryPath; + if (!basePath && companyPath !== "COMPANY.md") { + basePath = path.posix.dirname(companyPath); + if (basePath === ".") basePath = ""; + } + return { + hostname, + owner, + repo, + ref: queryRef || "main", + basePath, + companyPath, + }; + } + let ref = "main"; + let basePath = ""; + let companyPath = "COMPANY.md"; + if (parts[2] === "tree") { + ref = parts[3] ?? "main"; + basePath = parts.slice(4).join("/"); + } else if (parts[2] === "blob") { + ref = parts[3] ?? "main"; + const blobPath = parts.slice(4).join("/"); + if (!blobPath) { + throw unprocessable("Invalid GitHub blob URL"); + } + companyPath = blobPath; + basePath = path.posix.dirname(blobPath); + if (basePath === ".") basePath = ""; + } + return { hostname, owner, repo, ref, basePath, companyPath }; } @@ -2962,38 +2975,30 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { ); } - const sourceUrl = source.url; - const parsed = parseGitHubSourceUrl(sourceUrl); + const parsed = parseGitHubSourceUrl(source.url); + let ref = parsed.ref; const warnings: string[] = []; const companyRelativePath = parsed.companyPath === "COMPANY.md" ? [parsed.basePath, "COMPANY.md"].filter(Boolean).join("/") : parsed.companyPath; - - async function openSnapshot(refName: string): Promise { - const ps = parseGitSourceUrl(sourceUrl); - const wanted = { ...ps, ref: refName, explicitRef: true }; - const resolved = await resolveGitRef(wanted); - return openRepoSnapshot(wanted, resolved.trackingRef, resolved.pinnedSha); - } - - let ref = parsed.ref; - let snapshot: RepoSnapshot; let companyMarkdown: string | null = null; try { - snapshot = await openSnapshot(ref); - companyMarkdown = await snapshot.readFileOptional(companyRelativePath); + companyMarkdown = await fetchOptionalText( + resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, companyRelativePath), + ); } catch (err) { if (ref === "main") { ref = "master"; - warnings.push("Git ref main not found; falling back to master."); - snapshot = await openSnapshot(ref); - companyMarkdown = await snapshot.readFileOptional(companyRelativePath); + warnings.push("GitHub ref main not found; falling back to master."); + companyMarkdown = await fetchOptionalText( + resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, companyRelativePath), + ); } else { throw err; } } if (!companyMarkdown) { - throw unprocessable("Git company package is missing COMPANY.md"); + throw unprocessable("GitHub company package is missing COMPANY.md"); } const companyPath = parsed.companyPath === "COMPANY.md" @@ -3002,22 +3007,31 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { const files: Record = { [companyPath]: companyMarkdown, }; + const apiBase = gitHubApiBase(parsed.hostname); + const tree = await fetchJson<{ tree?: Array<{ path: string; type: string }> }>( + `${apiBase}/repos/${parsed.owner}/${parsed.repo}/git/trees/${ref}?recursive=1`, + ).catch(() => ({ tree: [] })); const basePrefix = parsed.basePath ? `${parsed.basePath.replace(/^\/+|\/+$/g, "")}/` : ""; - const allPaths = await snapshot.listFiles(); - const candidatePaths = allPaths.filter((entry) => { - if (basePrefix && !entry.startsWith(basePrefix)) return false; - const relative = basePrefix ? entry.slice(basePrefix.length) : entry; - return ( - relative.endsWith(".md") || - relative.startsWith("skills/") || - relative === ".paperclip.yaml" || - relative === ".paperclip.yml" - ); - }); + const candidatePaths = (tree.tree ?? []) + .filter((entry) => entry.type === "blob") + .map((entry) => entry.path) + .filter((entry): entry is string => typeof entry === "string") + .filter((entry) => { + if (basePrefix && !entry.startsWith(basePrefix)) return false; + const relative = basePrefix ? entry.slice(basePrefix.length) : entry; + return ( + relative.endsWith(".md") || + relative.startsWith("skills/") || + relative === ".paperclip.yaml" || + relative === ".paperclip.yml" + ); + }); for (const repoPath of candidatePaths) { const relativePath = basePrefix ? repoPath.slice(basePrefix.length) : repoPath; if (files[relativePath] !== undefined) continue; - files[normalizePortablePath(relativePath)] = await snapshot.readFile(repoPath); + files[normalizePortablePath(relativePath)] = await fetchText( + resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, repoPath), + ); } const companyDoc = parseFrontmatterMarkdown(companyMarkdown); const includeEntries = readIncludeEntries(companyDoc.frontmatter); @@ -3026,7 +3040,9 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { const relativePath = normalizePortablePath(includeEntry.path); if (files[relativePath] !== undefined) continue; if (!(repoPath.endsWith(".md") || repoPath.endsWith(".yaml") || repoPath.endsWith(".yml"))) continue; - files[relativePath] = await snapshot.readFile(repoPath); + files[relativePath] = await fetchText( + resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, repoPath), + ); } const resolved = buildManifestFromPackageFiles(files); @@ -3034,13 +3050,12 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { if (companyLogoPath && !resolved.files[companyLogoPath]) { const repoPath = [parsed.basePath, companyLogoPath].filter(Boolean).join("/"); try { - const binary = await snapshot.readBinary(repoPath); - resolved.files[companyLogoPath] = bufferToPortableBinaryFile( - Buffer.from(binary), - inferContentTypeFromPath(companyLogoPath), + const binary = await fetchBinary( + resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, repoPath), ); + resolved.files[companyLogoPath] = bufferToPortableBinaryFile(binary, inferContentTypeFromPath(companyLogoPath)); } catch (err) { - warnings.push(`Failed to fetch company logo ${companyLogoPath} from git: ${err instanceof Error ? err.message : String(err)}`); + warnings.push(`Failed to fetch company logo ${companyLogoPath} from GitHub: ${err instanceof Error ? err.message : String(err)}`); } } resolved.warnings.unshift(...warnings); @@ -3067,9 +3082,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { const files: Record = {}; const warnings: string[] = []; const envInputs: CompanyPortabilityManifest["envInputs"] = []; - const secretEntries: CompanyPortabilitySecretEntry[] = []; const requestedSidebarOrder = normalizePortableSidebarOrder(input.sidebarOrder); - const includeSecrets = input.includeSecrets === true; const rootPath = normalizeAgentUrlKey(company.name) ?? "company-package"; let companyLogoPath: string | null = null; @@ -3349,14 +3362,10 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { warnings.push(...exportedInstructions.warnings); const envInputsStart = envInputs.length; - const exportedEnvInputs = await extractPortableEnvInputs( + const exportedEnvInputs = extractPortableEnvInputs( slug, (agent.adapterConfig as Record).env, warnings, - secrets, - secretEntries, - includeSecrets, - companyId, ); envInputs.push(...exportedEnvInputs); const adapterDefaultRules = ADAPTER_DEFAULT_RULES_BY_TYPE[agent.adapterType] ?? []; @@ -3433,7 +3442,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { const slug = projectSlugById.get(project.id)!; const projectPath = `projects/${slug}/PROJECT.md`; const envInputsStart = envInputs.length; - const exportedEnvInputs = await extractPortableProjectEnvInputs(slug, project.env, warnings, secrets, secretEntries, includeSecrets, companyId); + const exportedEnvInputs = extractPortableProjectEnvInputs(slug, project.env, warnings); envInputs.push(...exportedEnvInputs); const projectEnvInputs = dedupeEnvInputs( envInputs @@ -3653,20 +3662,8 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { skills: resolved.manifest.skills.length > 0, }; resolved.manifest.envInputs = dedupeEnvInputs(envInputs); - if (includeSecrets) { - resolved.manifest.secrets = secretEntries.length > 0 ? secretEntries : undefined; - } resolved.warnings.unshift(...warnings); - // Rebuild the YAML file to include secrets so files stay in sync with manifest - // Only include secrets - other fields should come from the original YAML structure - if (includeSecrets && resolved.manifest.secrets) { - // Parse existing YAML and add secrets to it - const existingYaml = parseYamlFile(readPortableTextFile(finalFiles, paperclipExtensionPath) ?? "") ?? {}; - existingYaml.secrets = resolved.manifest.secrets; - finalFiles[paperclipExtensionPath] = buildYamlFile(existingYaml, { preserveEmptyStrings: true }); - } - return { rootPath, manifest: resolved.manifest, @@ -4231,7 +4228,6 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { const resultAgents: CompanyPortabilityImportResult["agents"] = []; const resultProjects: CompanyPortabilityImportResult["projects"] = []; const importedSlugToAgentId = new Map(); - const secretNameToId = new Map(); const existingSlugToAgentId = new Map(); const agentStatusById = new Map(); const existingAgents = await agents.list(targetCompany.id); @@ -4263,35 +4259,6 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { } } - // Create secrets in target company and build name->id map - for (const secretEntry of sourceManifest.secrets ?? []) { - if (secretEntry.currentValue.startsWith(" agent.slug === planAgent.slug); @@ -4348,30 +4315,6 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { desiredSkills, mode, ); - - // Reconstruct adapterConfig.env from manifest.envInputs for this agent - const agentEnvInputs = (sourceManifest.envInputs ?? []).filter((e) => e.agentSlug === manifestAgent.slug); - if (agentEnvInputs.length > 0) { - const env: Record = {}; - for (const ei of agentEnvInputs) { - if (ei.kind === "secret" && ei.secretName) { - const newSecretId = secretNameToId.get(ei.secretName); - if (newSecretId) { - env[ei.key] = { type: "secret_ref", secretId: newSecretId }; - } else { - warnings.push(`Env key "${ei.key}" for agent ${manifestAgent.slug} references secret "${ei.secretName}" which was not included in this package. Re-add manually.`); - } - } else if (ei.kind === "secret" && !ei.secretName) { - warnings.push(`Env key "${ei.key}" for agent ${manifestAgent.slug} could not be reconstructed (sensitive binding without secret reference). Re-add manually.`); - } else if (ei.kind === "plain" && ei.defaultValue !== null) { - env[ei.key] = { type: "plain", value: ei.defaultValue }; - } - } - if (Object.keys(env).length > 0) { - normalizedAdapter.adapterConfig.env = await secrets.normalizeEnvBindingsForPersistence(targetCompany.id, env as any, { strictMode: strictSecretsMode }); - } - } - const patch = { name: planAgent.plannedName, role: manifestAgent.role, @@ -4422,9 +4365,10 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { continue; } + const createdStatus = "idle"; let created = await agents.create(targetCompany.id, { ...patch, - status: "idle", + status: createdStatus, }); await access.ensureMembership(targetCompany.id, "agent", created.id, "member", "active"); await access.setPrincipalPermission( @@ -4444,7 +4388,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { } catch (err) { warnings.push(`Failed to materialize instructions bundle for ${manifestAgent.slug}: ${err instanceof Error ? err.message : String(err)}`); } - agentStatusById.set(created.id, created.status ?? "idle"); + agentStatusById.set(created.id, created.status ?? createdStatus); importedSlugToAgentId.set(planAgent.slug, created.id); existingSlugToAgentId.set(normalizeAgentUrlKey(created.name) ?? created.id, created.id); resultAgents.push({ @@ -4493,26 +4437,6 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { ?? null : null; const projectWorkspaceIdByKey = new Map(); - // Build project env from manifest.envInputs filtered by this project - const projectEnvInputs = (sourceManifest.envInputs ?? []).filter((e) => e.projectSlug === planProject.slug); - const reconstructedProjectEnv: Record = {}; - for (const ei of projectEnvInputs) { - if (ei.kind === "secret" && ei.secretName) { - const newSecretId = secretNameToId.get(ei.secretName); - if (newSecretId) { - reconstructedProjectEnv[ei.key] = { type: "secret_ref", secretId: newSecretId }; - } else { - warnings.push(`Env key "${ei.key}" for project ${planProject.slug} references secret "${ei.secretName}" which was not included in this package. Re-add manually.`); - } - } else if (ei.kind === "secret" && !ei.secretName) { - warnings.push(`Env key "${ei.key}" for project ${planProject.slug} could not be reconstructed (sensitive binding without secret reference). Re-add manually.`); - } else if (ei.kind === "plain" && ei.defaultValue !== null) { - reconstructedProjectEnv[ei.key] = { type: "plain", value: ei.defaultValue }; - } - } - const projectEnvConfig = Object.keys(reconstructedProjectEnv).length > 0 - ? await secrets.normalizeEnvBindingsForPersistence(targetCompany.id, reconstructedProjectEnv as any, { strictMode: strictSecretsMode }) - : null; const projectPatch = { name: planProject.plannedName, description: manifestProject.description, @@ -4522,7 +4446,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { status: manifestProject.status && PROJECT_STATUSES.includes(manifestProject.status as any) ? manifestProject.status as typeof PROJECT_STATUSES[number] : "backlog", - env: projectEnvConfig ?? undefined, + env: manifestProject.env, executionWorkspacePolicy: stripPortableProjectExecutionWorkspaceRefs(manifestProject.executionWorkspacePolicy), }; @@ -4601,91 +4525,6 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { } } - // Remap secret_ref bindings in imported agent/project records to target company secret IDs - for (const envInput of sourceManifest.envInputs ?? []) { - if (envInput.kind !== "secret" || !envInput.secretName) continue; - const newSecretId = secretNameToId.get(envInput.secretName); - if (!newSecretId) { - // secret wasn't created (decryption failure or error) — it's already a placeholder in the env - continue; - } - if (envInput.agentSlug) { - const agentId = importedSlugToAgentId.get(envInput.agentSlug); - if (agentId) { - const agent = await agents.getById(agentId); - if (agent) { - const adapterConfig = agent.adapterConfig as Record; - const env = adapterConfig.env as Record | undefined; - let mutated = false; - if (env && typeof env[envInput.key] === "object" && env[envInput.key] !== null) { - const binding = env[envInput.key] as Record; - if (binding.type === "secret_ref" && binding.secretId !== newSecretId) { - binding.secretId = newSecretId; - mutated = true; - } - } - if (mutated) await agents.update(agentId, { adapterConfig }); - } - } - } else if (envInput.projectSlug) { - const projectId = importedSlugToProjectId.get(envInput.projectSlug); - if (projectId) { - const project = await projects.getById(projectId); - if (project && project.env && typeof project.env === "object") { - const env = project.env as Record; - let mutated = false; - if (typeof env[envInput.key] === "object" && env[envInput.key] !== null) { - const binding = env[envInput.key] as Record; - if (binding.type === "secret_ref" && binding.secretId !== newSecretId) { - binding.secretId = newSecretId; - mutated = true; - } - } - if (mutated) await projects.update(projectId, { env: env as import("@paperclipai/shared").AgentEnvConfig }); - } - } - } - } - - // Note: the legacy secret remapping below is kept as a safety net for - // agents/projects that were created/updated before this code existed. - // It can be removed once the inline reconstruction above is stable. - // Reconstruct plain env bindings and fill in missing env keys on imported agents/projects - for (const envInput of sourceManifest.envInputs ?? []) { - if (envInput.kind !== "plain" && !(envInput.kind === "secret" && !envInput.secretName)) continue; - if (!envInput.defaultValue && envInput.kind === "plain") continue; - - if (envInput.agentSlug) { - const agentId = importedSlugToAgentId.get(envInput.agentSlug); - if (!agentId) continue; - const agent = await agents.getById(agentId); - if (!agent) continue; - const adapterConfig = agent.adapterConfig as Record; - const env = (adapterConfig.env as Record) ?? {}; - let mutated = false; - if (!env[envInput.key] && envInput.kind === "plain") { - env[envInput.key] = { type: "plain", value: envInput.defaultValue ?? "" }; - mutated = true; - } - if (mutated) { - adapterConfig.env = env; - await agents.update(agentId, { adapterConfig }); - } - } else if (envInput.projectSlug) { - const projectId = importedSlugToProjectId.get(envInput.projectSlug); - if (!projectId) continue; - const project = await projects.getById(projectId); - if (!project) continue; - const env = (project.env as Record) ?? {}; - let mutated = false; - if (!env[envInput.key] && envInput.kind === "plain") { - env[envInput.key] = { type: "plain", value: envInput.defaultValue ?? "" }; - mutated = true; - } - if (mutated) await projects.update(projectId, { env: env as import("@paperclipai/shared").AgentEnvConfig }); - } - } - if (include.issues) { const routines = routineService(db); for (const manifestIssue of sourceManifest.issues) { diff --git a/server/src/services/company-skills.ts b/server/src/services/company-skills.ts index 3dd8ca88..d78d274f 100644 --- a/server/src/services/company-skills.ts +++ b/server/src/services/company-skills.ts @@ -1,20 +1,26 @@ -import { createHash } from "node:crypto"; +import { createHash, randomUUID } from "node:crypto"; import { promises as fs } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { and, asc, eq, sql } from "drizzle-orm"; +import { and, asc, eq } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { companies, companySkills } from "@paperclipai/db"; import { readPaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils"; import type { PaperclipSkillEntry } from "@paperclipai/adapter-utils/server-utils"; import type { + CatalogSkill, CompanySkill, + CompanySkillAuditFinding, + CompanySkillAuditResult, + CompanySkillAuditVerdict, CompanySkillCreateRequest, CompanySkillCompatibility, CompanySkillDetail, CompanySkillFileDetail, CompanySkillFileInventoryEntry, CompanySkillImportResult, + CompanySkillInstallCatalogRequest, + CompanySkillInstallCatalogResult, CompanySkillListItem, CompanySkillProjectScanConflict, CompanySkillProjectScanRequest, @@ -24,15 +30,28 @@ import type { CompanySkillSourceType, CompanySkillTrustLevel, CompanySkillUpdateStatus, + CompanySkillUpdateHoldReason, CompanySkillUsageAgent, } from "@paperclipai/shared"; import { normalizeAgentUrlKey } from "@paperclipai/shared"; import { resolvePaperclipInstanceRoot } from "../home-paths.js"; -import { notFound, unprocessable } from "../errors.js"; -import { openRepoSnapshot, parseGitSourceUrl, resolveGitRef, type ParsedGitSource, type RepoSnapshot } from "./git-source.js"; +import { conflict, notFound, unprocessable } from "../errors.js"; +import { ghFetch, gitHubApiBase, resolveRawGitHubUrl } from "./github-fetch.js"; import { agentService } from "./agents.js"; import { projectService } from "./projects.js"; -import { secretService } from "./secrets.js"; +import { normalizePortablePath } from "./portable-path.js"; +import { + copyCatalogSkillFile, + getCatalogPackageMetadata, + getCatalogSkillOrThrow, + readCatalogSkillFile, + resolveCatalogSkillReference, +} from "./skills-catalog.js"; +import { + PORTABLE_CATALOG_PROVENANCE_STRING_KEYS, + readCatalogStringList, + readPortableCatalogProvenance, +} from "./catalog-provenance.js"; type CompanySkillRow = typeof companySkills.$inferSelect; type CompanySkillListDbRow = Pick< @@ -123,6 +142,7 @@ type ParsedSkillImportSource = { type SkillSourceMeta = { skillKey?: string; sourceKind?: string; + missingSource?: SkillMissingSourceMarker; hostname?: string; owner?: string; repo?: string; @@ -134,6 +154,28 @@ type SkillSourceMeta = { workspaceId?: string; workspaceName?: string; workspaceCwd?: string; + catalogId?: string; + catalogKind?: string; + originHash?: string; + packageName?: string; + packageVersion?: string; + originVersion?: string; + originSnapshotLocator?: string; + installedHash?: string; + userModifiedAt?: string | null; + updateHoldReason?: CompanySkillUpdateHoldReason | null; + auditVerdict?: CompanySkillAuditVerdict; + auditCodes?: string[]; + auditScannedAt?: string; + auditScanVersion?: string; +}; + +type SkillMissingSourceMarker = { + reason: "local_source_missing"; + sourceType: "local_path"; + sourceLocator: string | null; + sourcePath: string | null; + detectedAt: string; }; export type LocalSkillInventoryMode = "full" | "project_root"; @@ -150,6 +192,10 @@ type RuntimeSkillEntryOptions = { materializeMissing?: boolean; }; +type RuntimeSkillSourceResolution = + | { status: "available"; source: string } + | { status: "missing"; source: string; detail: string }; + const skillInventoryRefreshPromises = new Map>(); function selectCompanySkillColumns() { @@ -216,6 +262,9 @@ const PROJECT_ROOT_SKILL_SUBDIRECTORIES = [ "assets", ] as const; +const SKILL_AUDIT_SCAN_VERSION = "skills-audit-v1"; +const MAX_CATALOG_FILE_BYTES = 1024 * 1024; + function asString(value: unknown): string | null { if (typeof value !== "string") return null; const trimmed = value.trim(); @@ -226,19 +275,6 @@ function isPlainRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } -function normalizePortablePath(input: string) { - const parts: string[] = []; - for (const segment of input.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "").split("/")) { - if (!segment || segment === ".") continue; - if (segment === "..") { - if (parts.length > 0) parts.pop(); - continue; - } - parts.push(segment); - } - return parts.join("/"); -} - function normalizePackageFileMap(files: Record) { const out: Record = {}; for (const [rawPath, content] of Object.entries(files)) { @@ -278,6 +314,21 @@ function hashSkillValue(value: string) { return createHash("sha256").update(value).digest("hex").slice(0, 10); } +function sha256Buffer(value: Buffer | string) { + return createHash("sha256").update(value).digest("hex"); +} + +function buildInventoryContentHash(entries: Array<{ path: string; sha256: string }>) { + const hashInput = entries + .map((entry) => ({ path: normalizePortablePath(entry.path), sha256: entry.sha256 })) + .sort((left, right) => { + if (left.path === "SKILL.md") return -1; + if (right.path === "SKILL.md") return 1; + return left.path.localeCompare(right.path); + }); + return `sha256:${sha256Buffer(Buffer.from(JSON.stringify(hashInput)))}`; +} + function uniqueSkillSlug(baseSlug: string, usedSlugs: Set) { if (!usedSlugs.has(baseSlug)) return baseSlug; let attempt = 2; @@ -541,20 +592,89 @@ function parseFrontmatterMarkdown(raw: string): { frontmatter: Record { try { return new URL(url).hostname; } catch { return url; } })(); - throw unprocessable(`Could not connect to ${hostname}`); - } +async function fetchText(url: string) { + const response = await ghFetch(url); if (!response.ok) { throw unprocessable(`Failed to fetch ${url}: ${response.status}`); } return response.text(); } +async function fetchJson(url: string): Promise { + const response = await ghFetch(url, { + headers: { + accept: "application/vnd.github+json", + }, + }); + if (!response.ok) { + throw unprocessable(`Failed to fetch ${url}: ${response.status}`); + } + return response.json() as Promise; +} + + +async function resolveGitHubDefaultBranch(owner: string, repo: string, apiBase: string) { + const response = await fetchJson<{ default_branch?: string }>( + `${apiBase}/repos/${owner}/${repo}`, + ); + return asString(response.default_branch) ?? "main"; +} + +async function resolveGitHubCommitSha(owner: string, repo: string, ref: string, apiBase: string) { + const response = await fetchJson<{ sha?: string }>( + `${apiBase}/repos/${owner}/${repo}/commits/${encodeURIComponent(ref)}`, + ); + const sha = asString(response.sha); + if (!sha) { + throw unprocessable(`Failed to resolve GitHub ref ${ref}`); + } + return sha; +} + +function parseGitHubSourceUrl(rawUrl: string) { + const url = new URL(rawUrl); + if (url.protocol !== "https:") { + throw unprocessable("GitHub source URL must use HTTPS"); + } + const parts = url.pathname.split("/").filter(Boolean); + if (parts.length < 2) { + throw unprocessable("Invalid GitHub URL"); + } + const owner = parts[0]!; + const repo = parts[1]!.replace(/\.git$/i, ""); + let ref = "main"; + let basePath = ""; + let filePath: string | null = null; + let explicitRef = false; + if (parts[2] === "tree") { + ref = parts[3] ?? "main"; + basePath = parts.slice(4).join("/"); + explicitRef = true; + } else if (parts[2] === "blob") { + ref = parts[3] ?? "main"; + filePath = parts.slice(4).join("/"); + basePath = filePath ? path.posix.dirname(filePath) : ""; + explicitRef = true; + } + return { hostname: url.hostname, owner, repo, ref, basePath, filePath, explicitRef }; +} + +async function resolveGitHubPinnedRef(parsed: ReturnType) { + const apiBase = gitHubApiBase(parsed.hostname); + if (/^[0-9a-f]{40}$/i.test(parsed.ref.trim())) { + return { + pinnedRef: parsed.ref, + trackingRef: parsed.explicitRef ? parsed.ref : null, + }; + } + + const trackingRef = parsed.explicitRef + ? parsed.ref + : await resolveGitHubDefaultBranch(parsed.owner, parsed.repo, apiBase); + const pinnedRef = await resolveGitHubCommitSha(parsed.owner, parsed.repo, trackingRef, apiBase); + return { pinnedRef, trackingRef }; +} + function extractCommandTokens(raw: string) { const matches = raw.match(/"[^"]*"|'[^']*'|\S+/g) ?? []; @@ -717,6 +837,16 @@ function deriveImportedSkillSource( } } + const catalogProvenance = readPortableCatalogProvenance(metadata, canonicalKey); + if (catalogProvenance) { + return { + sourceType: "catalog", + sourceLocator: null, + sourceRef: catalogProvenance.sourceRef, + metadata: catalogProvenance.metadata, + }; + } + return { sourceType: "catalog", sourceLocator: null, @@ -982,7 +1112,6 @@ async function readUrlSkillImports( companyId: string, sourceUrl: string, requestedSkillSlug: string | null = null, - authToken?: string, ): Promise<{ skills: ImportedSkill[]; warnings: string[] }> { const url = sourceUrl.trim(); const warnings: string[] = []; @@ -995,12 +1124,19 @@ async function readUrlSkillImports( return segments.length >= 2 && !parsed.pathname.endsWith(".md"); } catch { return false; } })(); if (looksLikeRepoUrl) { - const parsed = parseGitSourceUrl(url); - const resolved = await resolveGitRef(parsed, authToken); - const snapshot = await openRepoSnapshot(parsed, resolved.trackingRef, resolved.pinnedSha, authToken); - const ref = snapshot.sha; - const trackingRef = resolved.trackingRef; - const allPaths = await snapshot.listFiles(); + const parsed = parseGitHubSourceUrl(url); + const apiBase = gitHubApiBase(parsed.hostname); + const { pinnedRef, trackingRef } = await resolveGitHubPinnedRef(parsed); + let ref = pinnedRef; + const tree = await fetchJson<{ tree?: Array<{ path: string; type: string }> }>( + `${apiBase}/repos/${parsed.owner}/${parsed.repo}/git/trees/${ref}?recursive=1`, + ).catch(() => { + throw unprocessable(`Failed to read GitHub tree for ${url}`); + }); + const allPaths = (tree.tree ?? []) + .filter((entry) => entry.type === "blob") + .map((entry) => entry.path) + .filter((entry): entry is string => typeof entry === "string"); const basePrefix = parsed.basePath ? `${parsed.basePath.replace(/^\/+|\/+$/g, "")}/` : ""; const scopedPaths = basePrefix ? allPaths.filter((entry) => entry.startsWith(basePrefix)) @@ -1014,13 +1150,13 @@ async function readUrlSkillImports( ); if (skillPaths.length === 0) { throw unprocessable( - "No SKILL.md files were found in the provided source.", + "No SKILL.md files were found in the provided GitHub source.", ); } const skills: ImportedSkill[] = []; for (const relativeSkillPath of skillPaths) { const repoSkillPath = basePrefix ? `${basePrefix}${relativeSkillPath}` : relativeSkillPath; - const markdown = await snapshot.readFile(repoSkillPath); + const markdown = await fetchText(resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, repoSkillPath)); const parsedMarkdown = parseFrontmatterMarkdown(markdown); const skillDir = path.posix.dirname(relativeSkillPath); const slug = deriveImportedSkillSlug(parsedMarkdown.frontmatter, path.posix.basename(skillDir)); @@ -1074,15 +1210,15 @@ async function readUrlSkillImports( if (skills.length === 0) { throw unprocessable( requestedSkillSlug - ? `Skill ${requestedSkillSlug} was not found in the provided source.` - : "No SKILL.md files were found in the provided source.", + ? `Skill ${requestedSkillSlug} was not found in the provided GitHub source.` + : "No SKILL.md files were found in the provided GitHub source.", ); } return { skills, warnings }; } if (url.startsWith("http://") || url.startsWith("https://")) { - const markdown = await fetchPlainText(url); + const markdown = await fetchText(url); const parsedMarkdown = parseFrontmatterMarkdown(markdown); const urlObj = new URL(url); const fileName = path.posix.basename(urlObj.pathname); @@ -1180,6 +1316,49 @@ function getSkillMeta(skill: Pick): SkillSourceMeta { return isPlainRecord(skill.metadata) ? skill.metadata as SkillSourceMeta : {}; } +function resolveCatalogSkillIfPresent(reference: string): CatalogSkill | null { + const result = resolveCatalogSkillReference(reference); + if (result.ambiguous) { + throw conflict(`Catalog skill slug "${reference}" is ambiguous. Use an id or key.`); + } + return result.skill; +} + +function getMissingSourceMarker(metadata: Record | null): Record | null { + if (!isPlainRecord(metadata)) return null; + return isPlainRecord(metadata.missingSource) ? metadata.missingSource : null; +} + +function buildMissingLocalSourceMarker( + skill: Pick, +): SkillMissingSourceMarker { + const existing = getMissingSourceMarker(skill.metadata); + return { + reason: "local_source_missing", + sourceType: "local_path", + sourceLocator: skill.sourceLocator ?? null, + sourcePath: normalizeSourceLocatorDirectory(skill.sourceLocator), + detectedAt: asString(existing?.detectedAt) ?? new Date().toISOString(), + }; +} + +function withMissingSourceMarker( + metadata: Record | null, + marker: SkillMissingSourceMarker, +) { + return { + ...(isPlainRecord(metadata) ? metadata : {}), + missingSource: marker, + }; +} + +function withoutMissingSourceMarker(metadata: Record | null) { + if (!isPlainRecord(metadata) || !isPlainRecord(metadata.missingSource)) return metadata; + const next = { ...metadata }; + delete next.missingSource; + return next; +} + function resolveSkillReference( skills: SkillReferenceTarget[], reference: string, @@ -1285,6 +1464,22 @@ function normalizeSourceLocatorDirectory(sourceLocator: string | null) { return path.basename(resolved).toLowerCase() === "skill.md" ? path.dirname(resolved) : resolved; } +async function resolveExistingSkillDirectory(skillDir: string | null) { + if (!skillDir) return null; + const dirStat = await statPath(skillDir); + const skillFileStat = await statPath(path.join(skillDir, "SKILL.md")); + return dirStat?.isDirectory() && skillFileStat?.isFile() ? skillDir : null; +} + +function buildMissingRuntimeSourceDetail(skill: Pick) { + const marker = getMissingSourceMarker(skill.metadata); + const sourcePath = asString(marker?.sourcePath) ?? normalizeSourceLocatorDirectory(skill.sourceLocator); + if (sourcePath) { + return `Company skill "${skill.name}" is in the library, but Paperclip cannot find its local source at ${sourcePath}.`; + } + return `Company skill "${skill.name}" is in the library, but Paperclip cannot find a valid local runtime source for it.`; +} + export async function findMissingLocalSkillIds( skills: Array>, ) { @@ -1325,6 +1520,211 @@ function resolveLocalSkillFilePath(skill: CompanySkill, relativePath: string) { return directPath; } +async function collectSkillFileBytes(skillDir: string): Promise<{ + files: Array<{ path: string; bytes: Buffer; sizeBytes: number; kind: CompanySkillFileInventoryEntry["kind"] }>; + findings: CompanySkillAuditFinding[]; +}> { + const files: Array<{ path: string; bytes: Buffer; sizeBytes: number; kind: CompanySkillFileInventoryEntry["kind"] }> = []; + const findings: CompanySkillAuditFinding[] = []; + const root = path.resolve(skillDir); + + async function visit(current: string) { + const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []); + for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) { + const absolutePath = path.resolve(current, entry.name); + const relativePath = normalizePortablePath(path.relative(root, absolutePath)); + if (!relativePath || relativePath.split("/").includes("..") || path.isAbsolute(relativePath)) { + findings.push({ + code: "path_out_of_tree", + severity: "error", + message: "Resolved file path is outside the skill directory.", + path: relativePath || null, + }); + continue; + } + + const lstat = await fs.lstat(absolutePath).catch(() => null); + if (!lstat) continue; + if (lstat.isSymbolicLink()) { + findings.push({ + code: "symlink", + severity: "error", + message: "Skill files must not be symlinks.", + path: relativePath, + }); + continue; + } + if (lstat.isDirectory()) { + await visit(absolutePath); + continue; + } + if (!lstat.isFile()) continue; + const bytes = await fs.readFile(absolutePath); + files.push({ + path: relativePath, + bytes, + sizeBytes: lstat.size, + kind: classifyInventoryKind(relativePath), + }); + } + } + + await visit(root); + files.sort((left, right) => { + if (left.path === "SKILL.md") return -1; + if (right.path === "SKILL.md") return 1; + return left.path.localeCompare(right.path); + }); + return { files, findings }; +} + +function contentLooksBinary(bytes: Buffer) { + if (bytes.includes(0)) return true; + const text = bytes.toString("utf8"); + return text.includes("\uFFFD"); +} + +function extractMarkdownLinks(markdown: string) { + const links: string[] = []; + const regex = /\[[^\]]+\]\(([^)]+)\)/g; + let match: RegExpExecArray | null; + while ((match = regex.exec(markdown)) !== null) { + const link = match[1]?.trim(); + if (link) links.push(link); + } + return links; +} + +function pushFinding( + findings: CompanySkillAuditFinding[], + code: string, + severity: CompanySkillAuditFinding["severity"], + message: string, + filePath: string | null, +) { + findings.push({ code, severity, message, path: filePath }); +} + +async function auditInstalledSkillBytes(skill: CompanySkill): Promise { + const skillDir = normalizeSkillDirectory(skill); + const scannedAt = new Date().toISOString(); + const originHash = asString(getSkillMeta(skill).originHash); + if (!skillDir) { + return { + skillId: skill.id, + installedHash: null, + originHash, + verdict: "fail", + codes: ["origin_unavailable"], + findings: [{ + code: "origin_unavailable", + severity: "error", + message: "Skill files are not available on disk for audit.", + path: null, + }], + scannedAt, + scanVersion: SKILL_AUDIT_SCAN_VERSION, + }; + } + + const { files, findings } = await collectSkillFileBytes(skillDir); + const actualPaths = files.map((file) => file.path).sort((left, right) => left.localeCompare(right)); + const expectedPaths = skill.fileInventory.map((entry) => normalizePortablePath(entry.path)).sort((left, right) => left.localeCompare(right)); + const installedHash = buildInventoryContentHash(files.map((file) => ({ + path: file.path, + sha256: sha256Buffer(file.bytes), + }))); + + if (!actualPaths.includes("SKILL.md")) { + pushFinding(findings, "missing_skill_md", "error", "Skill inventory does not contain SKILL.md.", "SKILL.md"); + } + + const actualSet = new Set(actualPaths); + const expectedSet = new Set(expectedPaths); + for (const expected of expectedPaths) { + if (!actualSet.has(expected)) { + if (expected === "SKILL.md") continue; + pushFinding(findings, "inventory_mismatch", "error", "Expected inventory file is missing on disk.", expected); + } + } + for (const actual of actualPaths) { + if (!expectedSet.has(actual)) { + pushFinding(findings, "inventory_mismatch", "error", "Installed file is not present in recorded inventory.", actual); + } + } + + const fileMap = new Map(files.map((file) => [file.path, file])); + const skillFile = fileMap.get("SKILL.md"); + if (skillFile) { + const markdown = skillFile.bytes.toString("utf8"); + const parsed = parseFrontmatterMarkdown(markdown); + if (!markdown.startsWith("---\n") || !asString(parsed.frontmatter.name)) { + pushFinding(findings, "invalid_frontmatter", "error", "SKILL.md must contain valid frontmatter with a name.", "SKILL.md"); + } + } + + const remoteExecPattern = /\b(?:curl|wget)\b[\s\S]{0,160}\|\s*(?:sh|bash)|\b(?:bash|sh)\s+-c\b|\beval\b|\bpython\s+-c\b|\bnode\s+-e\b/i; + const secretExfilPattern = /\b(?:cat|printenv|env|grep)\b[\s\S]{0,160}(?:\.aws\/credentials|\.ssh\/|\.npmrc|id_rsa|OPENAI_API_KEY|ANTHROPIC_API_KEY|API_KEY|TOKEN|SECRET)[\s\S]{0,160}\b(?:curl|wget|nc|netcat|scp)\b/i; + const networkPattern = /\b(?:curl|wget|fetch|httpie|nc|netcat|scp|ssh)\b|https?:\/\//i; + const secretReferencePattern = /\b(?:process\.env|printenv|\$[A-Z][A-Z0-9_]{2,}|API_KEY|TOKEN|SECRET|PASSWORD|\.env)\b/i; + + for (const file of files) { + if (file.sizeBytes > MAX_CATALOG_FILE_BYTES) { + pushFinding(findings, "oversized_file", "error", `Skill file exceeds ${MAX_CATALOG_FILE_BYTES} bytes.`, file.path); + } + if (file.kind !== "asset" && contentLooksBinary(file.bytes)) { + pushFinding(findings, "non_text_file", "error", "Non-asset skill files must be UTF-8 text.", file.path); + continue; + } + if (file.kind === "asset" || file.kind === "script" || file.kind === "other") { + pushFinding(findings, `${file.kind}_trust`, "warning", `Skill includes a ${file.kind} file.`, file.path); + } + if (file.kind === "asset") continue; + + const text = file.bytes.toString("utf8"); + if (remoteExecPattern.test(text)) { + pushFinding(findings, "remote_fetch_exec", "error", "Remote-fetch or dynamic execution pattern is not allowed.", file.path); + } + if (secretExfilPattern.test(text)) { + pushFinding(findings, "secret_exfiltration", "error", "Secret exfiltration pattern is not allowed.", file.path); + } + if (networkPattern.test(text)) { + pushFinding(findings, "network_reference", "warning", "Skill content references network-capable commands or URLs.", file.path); + } + if (secretReferencePattern.test(text)) { + pushFinding(findings, "secret_reference", "warning", "Skill content references environment variables or secret-like values.", file.path); + } + if (isMarkdownPath(file.path)) { + for (const link of extractMarkdownLinks(text)) { + if (/^(?:https?:|mailto:|#)/i.test(link)) continue; + const linkTarget = normalizePortablePath(path.posix.join(path.posix.dirname(file.path), link.split("#")[0] ?? "")); + if (linkTarget && !actualSet.has(linkTarget)) { + pushFinding(findings, "broken_internal_link", "warning", `Markdown link target is missing: ${link}`, file.path); + } + } + } + } + + if (originHash && installedHash !== originHash) { + pushFinding(findings, "local_modifications", "warning", "Installed catalog bytes differ from the pinned origin hash.", null); + } + + findings.sort((left, right) => `${left.severity}:${left.code}:${left.path ?? ""}`.localeCompare(`${right.severity}:${right.code}:${right.path ?? ""}`)); + const verdict: CompanySkillAuditVerdict = findings.some((finding) => finding.severity === "error") + ? "fail" + : findings.length > 0 ? "warning" : "pass"; + return { + skillId: skill.id, + installedHash, + originHash, + verdict, + codes: Array.from(new Set(findings.map((finding) => finding.code))).sort(), + findings, + scannedAt, + scanVersion: SKILL_AUDIT_SCAN_VERSION, + }; +} + function inferLanguageFromPath(filePath: string) { const fileName = path.posix.basename(filePath).toLowerCase(); if (fileName === "skill.md" || fileName.endsWith(".md")) return "markdown"; @@ -1447,6 +1847,13 @@ function enrichSkill(skill: CompanySkill, attachedAgentCount: number, usedByAgen function toCompanySkillListItem(skill: CompanySkillListRow, attachedAgentCount: number): CompanySkillListItem { const source = deriveSkillSourceInfo(skill); + const metadata = getSkillMeta(skill); + const catalogKind = skill.sourceType === "catalog" && (metadata.catalogKind === "bundled" || metadata.catalogKind === "optional") + ? metadata.catalogKind + : null; + const originHash = skill.sourceType === "catalog" ? asString(metadata.originHash) : null; + const packageName = skill.sourceType === "catalog" ? asString(metadata.packageName) : null; + const packageVersion = skill.sourceType === "catalog" ? asString(metadata.packageVersion) : null; return { id: skill.id, companyId: skill.companyId, @@ -1468,28 +1875,16 @@ function toCompanySkillListItem(skill: CompanySkillListRow, attachedAgentCount: sourceLabel: source.sourceLabel, sourceBadge: source.sourceBadge, sourcePath: source.sourcePath, + catalogKind, + originHash, + packageName, + packageVersion, }; } export function companySkillService(db: Db) { const agents = agentService(db); const projects = projectService(db); - const secretsSvc = secretService(db); - - async function resolveSkillAuthToken( - companyId: string, - skill: { metadata: Record | null }, - ): Promise { - const meta = skill.metadata; - if (!meta) return undefined; - const secretId = typeof meta.sourceAuthSecretId === "string" ? meta.sourceAuthSecretId.trim() : ""; - if (!secretId) return undefined; - try { - return await secretsSvc.resolveSecretValue(companyId, secretId, "latest"); - } catch { - return undefined; - } - } async function ensureBundledSkills(companyId: string) { for (const skillsRoot of resolveBundledSkillsRoot()) { @@ -1517,7 +1912,7 @@ export function companySkillService(db: Db) { return []; } - async function pruneMissingLocalPathSkills(companyId: string) { + async function reconcileLocalPathSkillSources(companyId: string) { const rows = await db .select({ id: companySkills.id, @@ -1525,18 +1920,48 @@ export function companySkillService(db: Db) { slug: companySkills.slug, sourceType: companySkills.sourceType, sourceLocator: companySkills.sourceLocator, + metadata: companySkills.metadata, }) .from(companySkills) .where(eq(companySkills.companyId, companyId)); const skills = rows.map((row) => ({ ...row, sourceType: row.sourceType as CompanySkillSourceType, + metadata: isPlainRecord(row.metadata) ? row.metadata : null, })); const missingIds = new Set(await findMissingLocalSkillIds(skills)); - if (missingIds.size === 0) return; for (const skill of skills) { - if (!missingIds.has(skill.id)) continue; + if (skill.sourceType !== "local_path") continue; + + if (!missingIds.has(skill.id)) { + if (getMissingSourceMarker(skill.metadata)) { + await db + .update(companySkills) + .set({ + metadata: withoutMissingSourceMarker(skill.metadata), + updatedAt: new Date(), + }) + .where(eq(companySkills.id, skill.id)); + } + continue; + } + + const usedByAgents = await usage(companyId, skill.key); + if (usedByAgents.length > 0) { + const metadata = withMissingSourceMarker( + skill.metadata, + buildMissingLocalSourceMarker(skill), + ); + if (JSON.stringify(metadata) !== JSON.stringify(skill.metadata ?? {})) { + await db + .update(companySkills) + .set({ metadata, updatedAt: new Date() }) + .where(eq(companySkills.id, skill.id)); + } + continue; + } + await db .delete(companySkills) .where(eq(companySkills.id, skill.id)); @@ -1561,7 +1986,7 @@ export function companySkillService(db: Db) { throw notFound("Company not found"); } await ensureBundledSkills(companyId); - await pruneMissingLocalPathSkills(companyId); + await reconcileLocalPathSkillSources(companyId); })(); skillInventoryRefreshPromises.set(companyId, refreshPromise); @@ -1648,6 +2073,54 @@ export function companySkillService(db: Db) { return row ? toCompanySkill(row) : null; } + async function updateSkillMetadata( + skill: CompanySkill, + metadataPatch: Record, + ): Promise { + const metadata = { + ...(isPlainRecord(skill.metadata) ? skill.metadata : {}), + ...metadataPatch, + }; + const row = await db + .update(companySkills) + .set({ metadata, updatedAt: new Date() }) + .where(eq(companySkills.id, skill.id)) + .returning() + .then((rows) => rows[0] ?? null); + if (!row) throw notFound("Skill not found"); + return toCompanySkill(row); + } + + async function persistAuditMetadata(skill: CompanySkill, audit: CompanySkillAuditResult): Promise { + const userModifiedAt = audit.originHash && audit.installedHash !== audit.originHash + ? asString(getSkillMeta(skill).userModifiedAt) ?? audit.scannedAt + : null; + const updateHoldReason: CompanySkillUpdateHoldReason | null = audit.verdict === "fail" + ? "audit_hard_stop" + : userModifiedAt ? "local_modifications" : null; + return updateSkillMetadata(skill, { + installedHash: audit.installedHash, + userModifiedAt, + updateHoldReason, + auditVerdict: audit.verdict, + auditCodes: audit.codes, + auditScannedAt: audit.scannedAt, + auditScanVersion: audit.scanVersion, + }); + } + + async function auditSkill(companyId: string, skillId: string): Promise { + await ensureSkillInventoryCurrent(companyId); + const skill = await getById(companyId, skillId); + if (!skill) return null; + if (skill.sourceType !== "catalog" && skill.sourceType !== "local_path") { + throw unprocessable("Only local-path and catalog-managed company skills support audit."); + } + const audit = await auditInstalledSkillBytes(skill); + await persistAuditMetadata(skill, audit); + return audit; + } + async function usage(companyId: string, key: string): Promise { const skills = await listReferenceTargets(companyId); const agentRows = await agents.list(companyId); @@ -1679,6 +2152,64 @@ export function companySkillService(db: Db) { await ensureSkillInventoryCurrent(companyId); const skill = await getById(companyId, skillId); if (!skill) return null; + const audit = skill.sourceType === "catalog" || skill.sourceType === "local_path" + ? await auditInstalledSkillBytes(skill) + : null; + const metadata = getSkillMeta(skill); + const statusMeta = { + installedHash: audit?.installedHash ?? asString(metadata.installedHash), + originHash: audit?.originHash ?? asString(metadata.originHash), + userModifiedAt: audit && audit.originHash && audit.installedHash !== audit.originHash + ? asString(metadata.userModifiedAt) ?? audit.scannedAt + : audit && audit.originHash + ? null + : asString(metadata.userModifiedAt), + updateHoldReason: (audit?.verdict === "fail" + ? "audit_hard_stop" + : audit && audit.originHash && audit.installedHash !== audit.originHash + ? "local_modifications" + : audit && audit.originHash + ? null + : asString(metadata.updateHoldReason)) as CompanySkillUpdateHoldReason | null, + auditVerdict: audit?.verdict ?? (asString(metadata.auditVerdict) as CompanySkillAuditVerdict | null), + auditCodes: audit?.codes ?? (Array.isArray(metadata.auditCodes) ? metadata.auditCodes.map(String) : []), + }; + + if (skill.sourceType === "catalog") { + const catalogId = asString(metadata.catalogId); + if (!catalogId) { + return { + supported: false, + reason: "This catalog skill does not have enough metadata to track updates.", + trackingRef: null, + currentRef: skill.sourceRef ?? statusMeta.originHash, + latestRef: null, + hasUpdate: false, + ...statusMeta, + }; + } + const catalogSkill = resolveCatalogSkillIfPresent(catalogId); + if (!catalogSkill) { + return { + supported: false, + reason: "Catalog entry is no longer available in the shipped manifest.", + trackingRef: catalogId, + currentRef: skill.sourceRef ?? statusMeta.originHash, + latestRef: null, + hasUpdate: false, + ...statusMeta, + }; + } + return { + supported: true, + reason: null, + trackingRef: catalogSkill.id, + currentRef: skill.sourceRef ?? statusMeta.originHash, + latestRef: catalogSkill.contentHash, + hasUpdate: catalogSkill.contentHash !== (skill.sourceRef ?? statusMeta.originHash), + ...statusMeta, + }; + } if (skill.sourceType !== "github" && skill.sourceType !== "skills_sh") { return { @@ -1688,10 +2219,10 @@ export function companySkillService(db: Db) { currentRef: skill.sourceRef ?? null, latestRef: null, hasUpdate: false, + ...statusMeta, }; } - const metadata = getSkillMeta(skill); const owner = asString(metadata.owner); const repo = asString(metadata.repo); const trackingRef = asString(metadata.trackingRef) ?? asString(metadata.ref); @@ -1703,22 +2234,13 @@ export function companySkillService(db: Db) { currentRef: skill.sourceRef ?? null, latestRef: null, hasUpdate: false, + ...statusMeta, }; } const hostname = asString(metadata.hostname) || "github.com"; - const authToken = await resolveSkillAuthToken(companyId, skill); - const parsed: ParsedGitSource = { - cloneUrl: `https://${hostname}/${owner}/${repo}.git`, - hostname, - owner, - repo, - ref: trackingRef, - basePath: "", - filePath: null, - explicitRef: true, - }; - const { pinnedSha: latestRef } = await resolveGitRef(parsed, authToken); + const apiBase = gitHubApiBase(hostname); + const latestRef = await resolveGitHubCommitSha(owner, repo, trackingRef, apiBase); return { supported: true, reason: null, @@ -1726,6 +2248,7 @@ export function companySkillService(db: Db) { currentRef: skill.sourceRef ?? null, latestRef, hasUpdate: latestRef !== (skill.sourceRef ?? null), + ...statusMeta, }; } @@ -1758,25 +2281,12 @@ export function companySkillService(db: Db) { const repo = asString(metadata.repo); const hostname = asString(metadata.hostname) || "github.com"; const ref = skill.sourceRef ?? asString(metadata.ref) ?? "main"; - const trackingRef = asString(metadata.trackingRef); const repoSkillDir = normalizeGitHubSkillDirectory(asString(metadata.repoSkillDir), skill.slug); if (!owner || !repo) { throw unprocessable("Skill source metadata is incomplete."); } - const authToken = await resolveSkillAuthToken(companyId, skill); const repoPath = normalizePortablePath(path.posix.join(repoSkillDir, normalizedPath)); - const parsedSource: ParsedGitSource = { - cloneUrl: `https://${hostname}/${owner}/${repo}.git`, - hostname, - owner, - repo, - ref, - basePath: repoSkillDir, - filePath: null, - explicitRef: true, - }; - const snapshot: RepoSnapshot = await openRepoSnapshot(parsedSource, trackingRef ?? null, ref, authToken); - content = await snapshot.readFile(repoPath); + content = await fetchText(resolveRawGitHubUrl(hostname, owner, repo, ref, repoPath)); } else if (skill.sourceType === "url") { if (normalizedPath !== "SKILL.md") { throw notFound("This skill source only exposes SKILL.md"); @@ -1880,7 +2390,7 @@ export function companySkillService(db: Db) { return detail; } - async function installUpdate(companyId: string, skillId: string): Promise { + async function installUpdate(companyId: string, skillId: string, options: { force?: boolean } = {}): Promise { await ensureSkillInventoryCurrent(companyId); const skill = await getById(companyId, skillId); if (!skill) return null; @@ -1889,21 +2399,216 @@ export function companySkillService(db: Db) { if (!status?.supported) { throw unprocessable(status?.reason ?? "This skill does not support updates."); } + if (skill.sourceType === "catalog" || skill.sourceType === "local_path") { + const audit = await auditInstalledSkillBytes(skill); + await persistAuditMetadata(skill, audit); + if (audit.verdict === "fail") { + throw unprocessable("Skill update is blocked by hard-stop audit findings.", { + updateHoldReason: "audit_hard_stop", + audit, + }); + } + if (audit.originHash && audit.installedHash !== audit.originHash && !options.force) { + throw unprocessable("Skill update is held because local modifications were detected; rerun with --force to discard them.", { + updateHoldReason: "local_modifications", + audit, + }); + } + } + + if (skill.sourceType === "catalog") { + const catalogId = asString(getSkillMeta(skill).catalogId); + if (!catalogId) { + throw unprocessable("Catalog skill metadata is incomplete."); + } + const catalogSkill = resolveCatalogSkillIfPresent(catalogId); + if (!catalogSkill) { + throw unprocessable("Catalog entry is no longer available in the shipped manifest.", { + updateHoldReason: "origin_unavailable", + }); + } + assertCatalogSkillInstallable(catalogSkill); + const originSnapshotLocator = await materializeCatalogOriginSnapshot(companyId, catalogSkill, skill.slug); + const snapshotSkill = { + ...skill, + sourceLocator: originSnapshotLocator, + sourceRef: catalogSkill.contentHash, + fileInventory: catalogSkill.files.map((entry) => ({ path: entry.path, kind: entry.kind })), + metadata: { + ...(isPlainRecord(skill.metadata) ? skill.metadata : {}), + originHash: catalogSkill.contentHash, + }, + }; + const candidateAudit = await auditInstalledSkillBytes(snapshotSkill); + if (candidateAudit.verdict === "fail") { + throw unprocessable("Catalog update is blocked by hard-stop audit findings.", { + updateHoldReason: "audit_hard_stop", + audit: candidateAudit, + }); + } + const materializedDir = path.resolve( + resolveManagedSkillsRoot(companyId), + "__catalog__", + buildSkillRuntimeName(catalogSkill.key, skill.slug), + ); + await copySkillDirectory(originSnapshotLocator, materializedDir); + const markdown = (await readCatalogSkillFile(catalogSkill.id, catalogSkill.entrypoint)).content; + const nextMetadata = buildCatalogSkillMetadata(catalogSkill, skill, originSnapshotLocator); + const nextValues = { + name: catalogSkill.name, + description: catalogSkill.description, + markdown, + sourceLocator: materializedDir, + sourceRef: catalogSkill.contentHash, + trustLevel: catalogSkill.trustLevel, + compatibility: catalogSkill.compatibility, + fileInventory: serializeFileInventory(catalogSkill.files.map((entry) => ({ + path: entry.path, + kind: entry.kind, + }))), + metadata: { + ...nextMetadata, + installedHash: catalogSkill.contentHash, + userModifiedAt: null, + updateHoldReason: null, + auditVerdict: "pass", + auditCodes: [], + auditScannedAt: new Date().toISOString(), + auditScanVersion: SKILL_AUDIT_SCAN_VERSION, + }, + updatedAt: new Date(), + }; + const row = await db + .update(companySkills) + .set(nextValues) + .where(and(eq(companySkills.id, skill.id), eq(companySkills.companyId, companyId))) + .returning() + .then((rows) => rows[0] ?? null); + if (!row) throw notFound("Skill not found"); + const updated = toCompanySkill(row); + const postAudit = await auditInstalledSkillBytes(updated); + if (postAudit.verdict === "fail") { + await persistAuditMetadata(updated, postAudit); + throw unprocessable("Catalog update produced hard-stop audit findings.", { + updateHoldReason: "audit_hard_stop", + audit: postAudit, + }); + } + return persistAuditMetadata(updated, postAudit); + } + if (!skill.sourceLocator) { throw unprocessable("Skill source locator is missing."); } - const authToken = await resolveSkillAuthToken(companyId, skill); - const result = await readUrlSkillImports(companyId, skill.sourceLocator, skill.slug, authToken); + const result = await readUrlSkillImports(companyId, skill.sourceLocator, skill.slug); const matching = result.skills.find((entry) => entry.key === skill.key) ?? result.skills[0] ?? null; if (!matching) { throw unprocessable(`Skill ${skill.key} could not be re-imported from its source.`); } - const imported = await upsertImportedSkills(companyId, [matching]); return imported[0] ?? null; } + async function resetSkill(companyId: string, skillId: string, options: { force?: boolean } = {}): Promise { + await ensureSkillInventoryCurrent(companyId); + const skill = await getById(companyId, skillId); + if (!skill) return null; + if (skill.sourceType !== "catalog") { + throw unprocessable("Only catalog-managed company skills support reset."); + } + + const metadata = getSkillMeta(skill); + const originHash = asString(metadata.originHash); + const snapshotLocator = asString(metadata.originSnapshotLocator); + const targetDir = normalizeSkillDirectory(skill); + if (!originHash || !targetDir) { + throw unprocessable("Catalog skill origin metadata is incomplete.", { + updateHoldReason: "origin_unavailable", + }); + } + + let sourceDir = snapshotLocator && (await statPath(path.join(snapshotLocator, "SKILL.md")))?.isFile() + ? snapshotLocator + : null; + if (!sourceDir) { + const catalogId = asString(metadata.catalogId); + const catalogSkill = catalogId ? resolveCatalogSkillIfPresent(catalogId) : null; + if (catalogSkill?.contentHash === originHash) { + sourceDir = await materializeCatalogOriginSnapshot(companyId, catalogSkill, skill.slug); + } + } + if (!sourceDir) { + throw conflict("Pinned catalog origin bytes are unavailable; run skills update explicitly instead.", { + updateHoldReason: "origin_unavailable", + }); + } + + const originAudit = await auditInstalledSkillBytes({ + ...skill, + sourceLocator: sourceDir, + metadata: { + ...(isPlainRecord(skill.metadata) ? skill.metadata : {}), + originHash, + }, + }); + if (originAudit.installedHash !== originHash || originAudit.verdict === "fail") { + throw unprocessable("Pinned catalog origin failed audit and cannot be restored.", { + updateHoldReason: originAudit.verdict === "fail" ? "audit_hard_stop" : "origin_unavailable", + audit: originAudit, + }); + } + + const preAudit = await auditInstalledSkillBytes(skill); + await persistAuditMetadata(skill, preAudit); + if (preAudit.installedHash !== originHash && !options.force) { + throw unprocessable("Skill reset would discard local modifications; rerun with --force after confirming reset.", { + updateHoldReason: "local_modifications", + audit: preAudit, + }); + } + + await copySkillDirectory(sourceDir, targetDir); + const markdown = await fs.readFile(path.join(targetDir, "SKILL.md"), "utf8"); + const inventory = await collectLocalSkillInventory(targetDir); + const trustLevel = deriveTrustLevel(inventory); + const row = await db + .update(companySkills) + .set({ + markdown, + sourceRef: originHash, + trustLevel, + compatibility: "compatible", + fileInventory: serializeFileInventory(inventory), + metadata: { + ...(isPlainRecord(skill.metadata) ? skill.metadata : {}), + originSnapshotLocator: sourceDir, + installedHash: originHash, + userModifiedAt: null, + updateHoldReason: null, + auditVerdict: "pass", + auditCodes: [], + auditScannedAt: new Date().toISOString(), + auditScanVersion: SKILL_AUDIT_SCAN_VERSION, + }, + updatedAt: new Date(), + }) + .where(and(eq(companySkills.id, skill.id), eq(companySkills.companyId, companyId))) + .returning() + .then((rows) => rows[0] ?? null); + if (!row) throw notFound("Skill not found"); + const reset = toCompanySkill(row); + const postAudit = await auditInstalledSkillBytes(reset); + if (postAudit.installedHash !== originHash || postAudit.verdict === "fail") { + await persistAuditMetadata(reset, postAudit); + throw unprocessable("Catalog reset did not restore a passing pinned origin.", { + updateHoldReason: postAudit.verdict === "fail" ? "audit_hard_stop" : "origin_unavailable", + audit: postAudit, + }); + } + return persistAuditMetadata(reset, postAudit); + } + async function scanProjectWorkspaces( companyId: string, input: CompanySkillProjectScanRequest = {}, @@ -2069,28 +2774,6 @@ export function companySkillService(db: Db) { } } - const sourceLocators = new Set(); - for (const skill of acceptedSkills) { - if (skill.sourceType !== "github" && skill.sourceType !== "skills_sh") continue; - const locator = skill.sourceLocator ?? ""; - if (locator) sourceLocators.add(locator); - } - for (const sourceLocator of sourceLocators) { - try { - const result = await readUrlSkillImports(companyId, sourceLocator, null); - for (const nextSkill of result.skills) { - if (acceptedSkills.some((s) => s.slug === nextSkill.slug)) continue; - const persisted = (await upsertImportedSkills(companyId, [nextSkill]))[0]; - if (persisted) { - imported.push(persisted); - upsertAcceptedSkill(persisted); - } - } - } catch { - warnings.push(`Could not re-scan source ${sourceLocator} — skipping.`); - } - } - return { scannedProjects: scannedProjectIds.size, scannedWorkspaces: scanTargets.length, @@ -2129,18 +2812,292 @@ export function companySkillService(db: Db) { return skillDir; } + async function createDirectoryReplacement(targetDir: string) { + const parentDir = path.dirname(targetDir); + const baseName = path.basename(targetDir); + await fs.mkdir(parentDir, { recursive: true }); + const stagingDir = path.join(parentDir, `.${baseName}.tmp-${randomUUID()}`); + const previousDir = path.join(parentDir, `.${baseName}.old-${randomUUID()}`); + await fs.rm(stagingDir, { recursive: true, force: true }); + await fs.mkdir(stagingDir, { recursive: true }); + + return { + stagingDir, + async commit() { + let hasPrevious = false; + try { + await fs.rename(targetDir, previousDir); + hasPrevious = true; + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + } + + try { + await fs.rename(stagingDir, targetDir); + } catch (error) { + if (hasPrevious) { + await fs.rename(previousDir, targetDir).catch(() => undefined); + } + throw error; + } + + if (hasPrevious) { + await fs.rm(previousDir, { recursive: true, force: true }); + } + }, + async cleanup() { + await fs.rm(stagingDir, { recursive: true, force: true }); + }, + }; + } + + async function materializeCatalogManifestSkillFiles( + companyId: string, + catalogSkill: CatalogSkill, + slug: string, + ) { + const catalogRoot = path.resolve(resolveManagedSkillsRoot(companyId), "__catalog__"); + const skillDir = path.resolve(catalogRoot, buildSkillRuntimeName(catalogSkill.key, slug)); + const replacement = await createDirectoryReplacement(skillDir); + try { + for (const entry of catalogSkill.files) { + const targetPath = path.resolve(replacement.stagingDir, entry.path); + if (targetPath !== replacement.stagingDir && !targetPath.startsWith(`${replacement.stagingDir}${path.sep}`)) { + throw unprocessable(`Catalog file path is invalid: ${entry.path}`); + } + await fs.mkdir(path.dirname(targetPath), { recursive: true }); + await copyCatalogSkillFile(catalogSkill.id, entry.path, targetPath); + } + await replacement.commit(); + } catch (error) { + await replacement.cleanup(); + throw error; + } + + return skillDir; + } + + async function materializeCatalogOriginSnapshot( + companyId: string, + catalogSkill: CatalogSkill, + slug: string, + ) { + const originsRoot = path.resolve(resolveManagedSkillsRoot(companyId), "__catalog_origins__"); + const snapshotDir = path.resolve( + originsRoot, + buildSkillRuntimeName(catalogSkill.key, slug), + catalogSkill.contentHash.replace(/^sha256:/, ""), + ); + const replacement = await createDirectoryReplacement(snapshotDir); + try { + for (const entry of catalogSkill.files) { + const targetPath = path.resolve(replacement.stagingDir, entry.path); + if (targetPath !== replacement.stagingDir && !targetPath.startsWith(`${replacement.stagingDir}${path.sep}`)) { + throw unprocessable(`Catalog file path is invalid: ${entry.path}`); + } + await fs.mkdir(path.dirname(targetPath), { recursive: true }); + await copyCatalogSkillFile(catalogSkill.id, entry.path, targetPath); + } + await replacement.commit(); + } catch (error) { + await replacement.cleanup(); + throw error; + } + + return snapshotDir; + } + + async function copySkillDirectory(sourceDir: string, targetDir: string) { + const { files } = await collectSkillFileBytes(sourceDir); + const replacement = await createDirectoryReplacement(targetDir); + try { + for (const file of files) { + const targetPath = path.resolve(replacement.stagingDir, file.path); + if (targetPath !== replacement.stagingDir && !targetPath.startsWith(`${replacement.stagingDir}${path.sep}`)) { + throw unprocessable(`Skill file path is invalid: ${file.path}`); + } + await fs.mkdir(path.dirname(targetPath), { recursive: true }); + await fs.writeFile(targetPath, file.bytes); + } + await replacement.commit(); + } catch (error) { + await replacement.cleanup(); + throw error; + } + } + + function buildCatalogSkillMetadata( + catalogSkill: CatalogSkill, + existing: CompanySkill | null, + originSnapshotLocator: string, + ) { + const packageMetadata = getCatalogPackageMetadata(); + const existingMetadata = existing && isPlainRecord(existing.metadata) ? existing.metadata : {}; + return { + ...existingMetadata, + skillKey: catalogSkill.key, + sourceKind: "catalog", + catalogId: catalogSkill.id, + catalogKey: catalogSkill.key, + catalogKind: catalogSkill.kind, + catalogCategory: catalogSkill.category, + catalogPath: catalogSkill.path, + packageName: packageMetadata.packageName, + packageVersion: packageMetadata.packageVersion, + originHash: catalogSkill.contentHash, + originVersion: packageMetadata.packageVersion, + originSnapshotLocator, + userModifiedAt: existingMetadata.userModifiedAt ?? null, + updateHoldReason: existingMetadata.updateHoldReason ?? null, + }; + } + + function assertCatalogSkillInstallable(catalogSkill: CatalogSkill) { + if (catalogSkill.compatibility !== "compatible") { + throw unprocessable(`Catalog skill ${catalogSkill.id} is not compatible.`); + } + if (catalogSkill.trustLevel === "scripts_executables") { + throw unprocessable( + "Catalog skill contains executable scripts and cannot be force-installed until security review semantics allow it.", + ); + } + } + + async function installFromCatalog( + companyId: string, + input: CompanySkillInstallCatalogRequest, + ): Promise { + await ensureSkillInventoryCurrent(companyId); + const catalogSkill = getCatalogSkillOrThrow(input.catalogSkillId); + assertCatalogSkillInstallable(catalogSkill); + + const slug = normalizeSkillSlug(input.slug ?? catalogSkill.slug); + if (!slug) { + throw unprocessable("Catalog skill slug is invalid."); + } + + const existingSkills = await listFull(companyId); + const existingByKey = existingSkills.find((skill) => skill.key === catalogSkill.key) ?? null; + const slugConflict = existingSkills.find((skill) => skill.slug === slug && skill.id !== existingByKey?.id) ?? null; + if (slugConflict) { + throw conflict(`Skill slug "${slug}" is already used by ${slugConflict.key}.`); + } + + if (existingByKey) { + const metadata = getSkillMeta(existingByKey); + const existingCatalogId = asString(metadata.catalogId); + const sameCatalog = existingByKey.sourceType === "catalog" && existingCatalogId === catalogSkill.id; + const catalogManaged = existingByKey.sourceType === "catalog"; + if (!sameCatalog && (!catalogManaged || !input.force)) { + throw conflict( + `Skill key "${catalogSkill.key}" is already used by ${existingByKey.sourceLocator ?? existingByKey.slug}.`, + ); + } + if ( + sameCatalog + && existingByKey.slug === slug + && asString(metadata.originHash) === catalogSkill.contentHash + ) { + const audit = await auditInstalledSkillBytes(existingByKey); + const audited = await persistAuditMetadata(existingByKey, audit); + if (audit.installedHash === catalogSkill.contentHash && audit.verdict !== "fail") { + return { + action: "unchanged", + skill: audited, + catalogSkill, + warnings: audit.findings.map((finding) => finding.message), + }; + } + if (!input.force) { + const holdReason = audit.verdict === "fail" ? "audit_hard_stop" : "local_modifications"; + const message = audit.verdict === "fail" + ? "Catalog skill has hard-stop audit findings; rerun with --force to replace it." + : "Catalog skill has local modifications; rerun with --force to replace it."; + throw unprocessable(message, { + updateHoldReason: holdReason, + audit, + }); + } + } + } + + const materializedDir = await materializeCatalogManifestSkillFiles(companyId, catalogSkill, slug); + const originSnapshotLocator = await materializeCatalogOriginSnapshot(companyId, catalogSkill, slug); + const markdown = (await readCatalogSkillFile(catalogSkill.id, catalogSkill.entrypoint)).content; + const metadata = buildCatalogSkillMetadata(catalogSkill, existingByKey, originSnapshotLocator); + const values = { + companyId, + key: catalogSkill.key, + slug, + name: catalogSkill.name, + description: catalogSkill.description, + markdown, + sourceType: "catalog", + sourceLocator: materializedDir, + sourceRef: catalogSkill.contentHash, + trustLevel: catalogSkill.trustLevel, + compatibility: catalogSkill.compatibility, + fileInventory: serializeFileInventory(catalogSkill.files.map((entry) => ({ + path: entry.path, + kind: entry.kind, + }))), + metadata, + updatedAt: new Date(), + }; + + const row = existingByKey + ? await db + .update(companySkills) + .set(values) + .where(eq(companySkills.id, existingByKey.id)) + .returning() + .then((rows) => rows[0] ?? null) + : await db + .insert(companySkills) + .values(values) + .returning() + .then((rows) => rows[0] ?? null); + + if (!row) throw notFound("Failed to persist company skill"); + const installed = toCompanySkill(row); + const postAudit = await auditInstalledSkillBytes(installed); + if (postAudit.verdict === "fail") { + await persistAuditMetadata(installed, postAudit); + throw unprocessable("Catalog install produced hard-stop audit findings.", { + updateHoldReason: "audit_hard_stop", + audit: postAudit, + }); + } + const audited = await persistAuditMetadata(installed, postAudit); + return { + action: existingByKey ? "updated" : "created", + skill: audited, + catalogSkill, + warnings: postAudit.findings.map((finding) => finding.message), + }; + } + async function materializeRuntimeSkillFiles(companyId: string, skill: CompanySkill) { const runtimeRoot = path.resolve(resolveManagedSkillsRoot(companyId), "__runtime__"); const skillDir = path.resolve(runtimeRoot, buildSkillRuntimeName(skill.key, skill.slug)); await fs.rm(skillDir, { recursive: true, force: true }); await fs.mkdir(skillDir, { recursive: true }); + let wroteSkillFile = false; for (const entry of skill.fileInventory) { - const detail = await readFile(companyId, skill.id, entry.path).catch(() => null); - if (!detail) continue; + const normalizedPath = normalizePortablePath(entry.path); + const detail = await readFile(companyId, skill.id, normalizedPath).catch(() => null); + const content = detail?.content ?? (normalizedPath === "SKILL.md" ? skill.markdown : null); + if (content === null) continue; const targetPath = path.resolve(skillDir, entry.path); await fs.mkdir(path.dirname(targetPath), { recursive: true }); - await fs.writeFile(targetPath, detail.content, "utf8"); + await fs.writeFile(targetPath, content, "utf8"); + if (normalizedPath === "SKILL.md") wroteSkillFile = true; + } + + if (!wroteSkillFile) { + await fs.rm(skillDir, { recursive: true, force: true }); + throw unprocessable("Company skill could not be materialized because its stored SKILL.md copy is missing."); } return skillDir; @@ -2151,6 +3108,29 @@ export function companySkillService(db: Db) { return path.resolve(runtimeRoot, buildSkillRuntimeName(skill.key, skill.slug)); } + async function resolveRuntimeSkillSource( + companyId: string, + skill: CompanySkill, + options: RuntimeSkillEntryOptions, + ): Promise { + const source = await resolveExistingSkillDirectory(normalizeSkillDirectory(skill)); + if (source) return { status: "available", source }; + + if (options.materializeMissing === false) { + const materializedPath = resolveRuntimeSkillMaterializedPath(companyId, skill); + const materializedSource = await resolveExistingSkillDirectory(materializedPath); + if (materializedSource) return { status: "available", source: materializedSource }; + return { + status: "missing", + source: materializedPath, + detail: buildMissingRuntimeSourceDetail(skill), + }; + } + + const materializedSource = await materializeRuntimeSkillFiles(companyId, skill).catch(() => null); + return materializedSource ? { status: "available", source: materializedSource } : null; + } + async function listRuntimeSkillEntries( companyId: string, options: RuntimeSkillEntryOptions = {}, @@ -2160,19 +3140,16 @@ export function companySkillService(db: Db) { const out: PaperclipSkillEntry[] = []; for (const skill of skills) { const sourceKind = asString(getSkillMeta(skill).sourceKind); - let source = normalizeSkillDirectory(skill); - if (!source) { - source = options.materializeMissing === false - ? resolveRuntimeSkillMaterializedPath(companyId, skill) - : await materializeRuntimeSkillFiles(companyId, skill).catch(() => null); - } - if (!source) continue; + const sourceResolution = await resolveRuntimeSkillSource(companyId, skill, options); + if (!sourceResolution) continue; const required = sourceKind === "paperclip_bundled"; out.push({ key: skill.key, runtimeName: buildSkillRuntimeName(skill.key, skill.slug), - source, + source: sourceResolution.source, + sourceStatus: sourceResolution.status, + missingDetail: sourceResolution.status === "missing" ? sourceResolution.detail : null, required, requiredReason: required ? "Bundled Paperclip skills are always available for local adapters." @@ -2314,29 +3291,20 @@ export function companySkillService(db: Db) { const incomingOwner = asString(incomingMeta.owner); const incomingRepo = asString(incomingMeta.repo); const incomingKind = asString(incomingMeta.sourceKind); - // Bundled skills are sourced from the server image and re-upserted by - // ensureBundledSkills only. Never let a non-bundled import overwrite a - // bundled row, regardless of which org/repo it claims to be from. if ( existing && existingMeta.sourceKind === "paperclip_bundled" - && incomingKind !== "paperclip_bundled" + && incomingKind === "github" + && incomingOwner === "paperclipai" + && incomingRepo === "paperclip" ) { out.push(existing); continue; } - // Preserve sourceAuthSecretId across re-imports of the same skill. Skip - // bundled rows: they should never carry a PAT reference, and preserving - // one across a bundled re-upsert would re-attach stale data. const metadata = { ...(skill.metadata ?? {}), skillKey: skill.key, - ...(existing?.metadata - && existingMeta.sourceKind !== "paperclip_bundled" - && typeof (existing.metadata as Record).sourceAuthSecretId === "string" - ? { sourceAuthSecretId: (existing.metadata as Record).sourceAuthSecretId } - : {}), }; const values = { companyId, @@ -2372,7 +3340,7 @@ export function companySkillService(db: Db) { return out; } - async function importFromSource(companyId: string, source: string, authToken?: string): Promise { + async function importFromSource(companyId: string, source: string): Promise { await ensureSkillInventoryCurrent(companyId); const parsed = parseSkillImportSourceInput(source); const local = !/^https?:\/\//i.test(parsed.resolvedSource); @@ -2382,7 +3350,7 @@ export function companySkillService(db: Db) { .filter((skill) => !parsed.requestedSkillSlug || skill.slug === parsed.requestedSkillSlug), warnings: parsed.warnings, } - : await readUrlSkillImports(companyId, parsed.resolvedSource, parsed.requestedSkillSlug, authToken) + : await readUrlSkillImports(companyId, parsed.resolvedSource, parsed.requestedSkillSlug) .then((result) => ({ skills: result.skills, warnings: [...parsed.warnings, ...result.warnings], @@ -2409,35 +3377,6 @@ export function companySkillService(db: Db) { } } const imported = await upsertImportedSkills(companyId, filteredSkills); - - if (authToken && imported.length > 0) { - for (const skill of imported) { - const skillMeta = skill.metadata as Record | null; - if (skillMeta?.sourceKind === "paperclip_bundled") continue; - const secretName = `skill-pat:${skill.id}`; - let secretId: string; - const existing = await secretsSvc.getByName(companyId, secretName); - if (existing) { - await secretsSvc.rotate(existing.id, { value: authToken }); - secretId = existing.id; - } else { - const created = await secretsSvc.create(companyId, { - name: secretName, - provider: "local_encrypted", - value: authToken, - description: `PAT for skill ${skill.slug}`, - }); - secretId = created.id; - } - const meta = (skill.metadata ?? {}) as Record; - meta.sourceAuthSecretId = secretId; - await db - .update(companySkills) - .set({ metadata: meta, updatedAt: new Date() }) - .where(and(eq(companySkills.id, skill.id), eq(companySkills.companyId, companyId))); - } - } - return { imported, warnings }; } @@ -2477,85 +3416,9 @@ export function companySkillService(db: Db) { // Clean up materialized runtime files await fs.rm(resolveRuntimeSkillMaterializedPath(companyId, skill), { recursive: true, force: true }); - const meta = skill.metadata as Record | null; - const secretId = typeof meta?.sourceAuthSecretId === "string" ? meta.sourceAuthSecretId : null; - if (secretId) { - // Skip cleanup if another skill in the same company still references this - // secret. The deleted row is already gone from the table, so any result - // here is a sibling skill we shouldn't orphan. - const otherSkillRefs = await db - .select({ id: companySkills.id }) - .from(companySkills) - .where(and( - eq(companySkills.companyId, companyId), - sql`${companySkills.metadata} ->> 'sourceAuthSecretId' = ${secretId}`, - )) - .limit(1); - if (otherSkillRefs.length === 0) { - try { - await secretsSvc.remove(secretId); - } catch { - // Best-effort: don't fail the skill deletion if secret cleanup fails - // (typically blocked by an agent env binding still referencing it). - } - } - } - return skill; } - async function updateSkillAuth( - companyId: string, - skillId: string, - authToken: string | null, - ): Promise { - const skill = await getById(companyId, skillId); - if (!skill) return null; - - const meta = (skill.metadata ?? {}) as Record; - if (meta.sourceKind === "paperclip_bundled") { - throw unprocessable("Cannot configure auth for bundled paperclip skills"); - } - const existingSecretId = typeof meta.sourceAuthSecretId === "string" ? meta.sourceAuthSecretId : null; - - if (authToken) { - const secretName = `skill-pat:${skill.id}`; - let secretId: string; - const existingSecret = existingSecretId - ? await secretsSvc.getById(existingSecretId) - : await secretsSvc.getByName(companyId, secretName); - if (existingSecret) { - await secretsSvc.rotate(existingSecret.id, { value: authToken }); - secretId = existingSecret.id; - } else { - const created = await secretsSvc.create(companyId, { - name: secretName, - provider: "local_encrypted", - value: authToken, - description: `PAT for skill ${skill.slug}`, - }); - secretId = created.id; - } - meta.sourceAuthSecretId = secretId; - } else { - if (existingSecretId) { - try { - await secretsSvc.remove(existingSecretId); - } catch { - // Best-effort: don't fail the metadata update if secret deletion fails - } - } - delete meta.sourceAuthSecretId; - } - - const [updated] = await db - .update(companySkills) - .set({ metadata: meta, updatedAt: new Date() }) - .where(and(eq(companySkills.id, skillId), eq(companySkills.companyId, companyId))) - .returning(); - return updated ? toCompanySkill(updated) : null; - } - return { list, listFull, @@ -2572,10 +3435,12 @@ export function companySkillService(db: Db) { createLocalSkill, deleteSkill, importFromSource, - updateSkillAuth, + installFromCatalog, scanProjectWorkspaces, importPackageFiles, + auditSkill, installUpdate, + resetSkill, listRuntimeSkillEntries, }; } diff --git a/server/src/services/git-source.ts b/server/src/services/git-source.ts deleted file mode 100644 index c67cadcd..00000000 --- a/server/src/services/git-source.ts +++ /dev/null @@ -1,282 +0,0 @@ -import path from "path"; -import git from "isomorphic-git"; -import http from "isomorphic-git/http/node"; -import { Volume, createFsFromVolume } from "memfs"; - -import { unprocessable } from "../errors.js"; - -export type ParsedGitSource = { - cloneUrl: string; - hostname: string; - owner: string; - repo: string; - ref: string | null; - basePath: string; - filePath: string | null; - explicitRef: boolean; -}; - -export type RefResolution = { - pinnedSha: string; - trackingRef: string | null; -}; - -export type RepoSnapshot = { - sha: string; - listFiles(): Promise; - readFile(repoPath: string): Promise; - readFileOptional(repoPath: string): Promise; - readBinary(repoPath: string): Promise; -}; - -const SHA_REGEX = /^[0-9a-f]{40}$/i; - -export function buildCloneUrl(hostname: string, owner: string, repo: string): string { - return `https://${hostname}/${owner}/${repo}.git`; -} - -export function parseGitSourceUrl(rawUrl: string): ParsedGitSource { - let url: URL; - try { - url = new URL(rawUrl); - } catch { - throw unprocessable("Invalid git source URL"); - } - if (url.protocol !== "https:") { - throw unprocessable("Source URL must use HTTPS"); - } - const segments = url.pathname.split("/").filter(Boolean); - if (segments.length < 2) { - throw unprocessable("Source URL must include an owner and repository"); - } - const owner = segments[0]!; - const repo = segments[1]!.replace(/\.git$/i, ""); - - // Query-string shape: /{owner}/{repo}?ref=...&path=... - // Used by company portability URLs. Takes precedence over path-based parsing - // so a URL with both shapes (rare) prefers the explicit query params. - const queryRef = url.searchParams.get("ref")?.trim() ?? null; - const queryPath = url.searchParams.get("path")?.trim() ?? null; - if (queryRef || queryPath) { - const normalizedPath = (queryPath ?? "").replace(/\\/g, "/").replace(/^\/+|\/+$/g, ""); - return { - cloneUrl: buildCloneUrl(url.hostname, owner, repo), - hostname: url.hostname, - owner, - repo, - ref: queryRef || null, - basePath: normalizedPath, - filePath: null, - explicitRef: Boolean(queryRef), - }; - } - - let ref: string | null = null; - let basePath = ""; - let filePath: string | null = null; - let explicitRef = false; - let tail: string[] = []; - - // Recognise common host-specific URL shapes so users can paste a tree/blob link. - if (segments[2] === "tree" || segments[2] === "blob") { - // github.com style - ref = segments[3] ?? null; - tail = segments.slice(4); - explicitRef = ref !== null; - } else if (segments[2] === "src" && (segments[3] === "branch" || segments[3] === "commit" || segments[3] === "tag")) { - // gitea / forgejo style - ref = segments[4] ?? null; - tail = segments.slice(5); - explicitRef = ref !== null; - } else if (segments[2] === "-" && (segments[3] === "tree" || segments[3] === "blob")) { - // gitlab style: /{owner}/{repo}/-/tree/{ref}/{path} - ref = segments[4] ?? null; - tail = segments.slice(5); - explicitRef = ref !== null; - } else if (segments[2] === "src" && segments.length >= 4) { - // bitbucket style: /{owner}/{repo}/src/{ref}/{path} - ref = segments[3] ?? null; - tail = segments.slice(4); - explicitRef = ref !== null; - } - - if (segments[2] === "blob" || (segments[2] === "-" && segments[3] === "blob")) { - const joined = tail.join("/"); - filePath = joined || null; - basePath = filePath ? path.posix.dirname(filePath) : ""; - if (basePath === ".") basePath = ""; - } else if (tail.length > 0) { - const joined = tail.join("/"); - // Heuristic: if the last segment looks like a file (has an extension), treat as file - const last = tail[tail.length - 1]!; - if (/\.[A-Za-z0-9]+$/.test(last)) { - filePath = joined; - basePath = path.posix.dirname(joined); - if (basePath === ".") basePath = ""; - } else { - basePath = joined; - } - } - - return { - cloneUrl: buildCloneUrl(url.hostname, owner, repo), - hostname: url.hostname, - owner, - repo, - ref, - basePath, - filePath, - explicitRef, - }; -} - -function buildAuthCallback(authToken: string | undefined) { - if (!authToken) return undefined; - // Universal pattern: token-as-username works for GitHub PATs (classic and fine-grained), - // GitLab project/personal access tokens, Gitea/Forgejo tokens, and Bitbucket app passwords - // when used over the git smart-HTTP protocol. - return () => ({ username: authToken, password: "x-oauth-basic" }); -} - -async function withGitErrors(label: string, fn: () => Promise): Promise { - try { - return await fn(); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (/HTTP Error: 401/i.test(message)) { - throw unprocessable(`${label}: authentication required or token rejected`); - } - if (/HTTP Error: 403/i.test(message)) { - throw unprocessable(`${label}: access forbidden`); - } - if (/HTTP Error: 404/i.test(message) || /repository not found/i.test(message)) { - throw unprocessable(`${label}: repository not found`); - } - if (/ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ETIMEDOUT/i.test(message)) { - throw unprocessable(`${label}: could not connect to host`); - } - throw unprocessable(`${label}: ${message}`); - } -} - -export async function resolveGitRef( - parsed: ParsedGitSource, - authToken?: string, -): Promise { - const onAuth = buildAuthCallback(authToken); - - if (parsed.ref && SHA_REGEX.test(parsed.ref.trim())) { - return { - pinnedSha: parsed.ref.trim().toLowerCase(), - trackingRef: parsed.explicitRef ? parsed.ref.trim() : null, - }; - } - - const refs = await withGitErrors(`Resolve refs for ${parsed.cloneUrl}`, () => - git.listServerRefs({ - http, - url: parsed.cloneUrl, - onAuth, - symrefs: true, - protocolVersion: 2, - }), - ); - - const findExact = (fullRef: string) => refs.find((r) => r.ref === fullRef); - - if (!parsed.ref) { - const head = refs.find((r) => r.ref === "HEAD"); - if (!head?.oid) { - throw unprocessable(`Could not determine default branch for ${parsed.cloneUrl}`); - } - const target = head.target?.replace(/^refs\/heads\//, "") ?? null; - return { pinnedSha: head.oid, trackingRef: target }; - } - - const wanted = parsed.ref.replace(/^refs\/(heads|tags)\//, ""); - const branch = findExact(`refs/heads/${wanted}`); - if (branch?.oid) return { pinnedSha: branch.oid, trackingRef: wanted }; - - // Prefer the peeled (annotated) tag oid when present, else the tag object oid. - const peeled = findExact(`refs/tags/${wanted}^{}`); - if (peeled?.oid) return { pinnedSha: peeled.oid, trackingRef: wanted }; - const tag = findExact(`refs/tags/${wanted}`); - if (tag?.oid) return { pinnedSha: tag.oid, trackingRef: wanted }; - - throw unprocessable(`Ref '${parsed.ref}' not found in ${parsed.cloneUrl}`); -} - -export async function openRepoSnapshot( - parsed: ParsedGitSource, - trackingRef: string | null, - expectedSha: string, - authToken?: string, -): Promise { - const volume = new Volume(); - const fs = createFsFromVolume(volume) as unknown as Parameters[0]["fs"]; - const dir = "/repo"; - const onAuth = buildAuthCallback(authToken); - - await withGitErrors(`Clone ${parsed.cloneUrl}`, async () => { - await git.clone({ - fs, - http, - dir, - url: parsed.cloneUrl, - ref: trackingRef ?? expectedSha, - singleBranch: true, - depth: 1, - noCheckout: true, - onAuth, - }); - }); - - // Re-resolve to the actual commit cloned. If upstream moved between resolveGitRef and - // clone, we trust what we cloned (snapshot is self-consistent). - const sha = await git.resolveRef({ fs, dir, ref: "HEAD" }); - - async function listFiles(): Promise { - const out: string[] = []; - await git.walk({ - fs, - dir, - trees: [git.TREE({ ref: sha })], - map: async (filepath, entries) => { - if (filepath === ".") return; - const entry = entries?.[0]; - if (!entry) return; - const type = await entry.type(); - if (type === "blob") { - out.push(filepath); - } - }, - }); - return out; - } - - async function readBinary(repoPath: string): Promise { - const normalized = repoPath.replace(/^\/+/, ""); - const { blob } = await git.readBlob({ fs, dir, oid: sha, filepath: normalized }); - return blob; - } - - async function readFile(repoPath: string): Promise { - const blob = await readBinary(repoPath); - return new TextDecoder("utf-8").decode(blob); - } - - async function readFileOptional(repoPath: string): Promise { - try { - return await readFile(repoPath); - } catch (err) { - // isomorphic-git throws NotFoundError when the path is missing from the tree. - const name = (err as { code?: string; name?: string } | null)?.code - ?? (err as { name?: string } | null)?.name - ?? ""; - if (/NotFound/i.test(name)) return null; - throw err; - } - } - - return { sha, listFiles, readFile, readFileOptional, readBinary }; -} diff --git a/server/src/services/github-fetch.ts b/server/src/services/github-fetch.ts new file mode 100644 index 00000000..787ae0ef --- /dev/null +++ b/server/src/services/github-fetch.ts @@ -0,0 +1,25 @@ +import { unprocessable } from "../errors.js"; + +function isGitHubDotCom(hostname: string) { + const h = hostname.toLowerCase(); + return h === "github.com" || h === "www.github.com"; +} + +export function gitHubApiBase(hostname: string) { + return isGitHubDotCom(hostname) ? "https://api.github.com" : `https://${hostname}/api/v3`; +} + +export function resolveRawGitHubUrl(hostname: string, owner: string, repo: string, ref: string, filePath: string) { + const p = filePath.replace(/^\/+/, ""); + return isGitHubDotCom(hostname) + ? `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${p}` + : `https://${hostname}/raw/${owner}/${repo}/${ref}/${p}`; +} + +export async function ghFetch(url: string, init?: RequestInit): Promise { + try { + return await fetch(url, init); + } catch { + throw unprocessable(`Could not connect to ${new URL(url).hostname} — ensure the URL points to a GitHub or GitHub Enterprise instance`); + } +} diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 8c34e992..76ba6307 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -29,6 +29,8 @@ import { activityLog, approvals, companySkills as companySkillsTable, + documentAnnotationComments, + documentAnnotationThreads, documentRevisions, issueDocuments, heartbeatRunEvents, @@ -87,6 +89,7 @@ import { logActivity, publishPluginDomainEvent, type LogActivityInput } from "./ import { buildWorkspaceReadyComment, cleanupExecutionWorkspaceArtifacts, + ensurePersistedExecutionWorkspaceAvailable, ensureRuntimeServicesForRun, persistAdapterManagedRuntimeServices, realizeExecutionWorkspace, @@ -594,6 +597,8 @@ export function mergeExecutionWorkspaceMetadataForPersistence(input: { createdByRuntime: boolean; configSnapshot: Record | null; shouldReuseExisting: boolean; + baseRef: string | null | undefined; + baseRefSha: string | null | undefined; }) { const base = { ...(input.existingMetadata ?? {}), @@ -601,6 +606,17 @@ export function mergeExecutionWorkspaceMetadataForPersistence(input: { createdByRuntime: input.createdByRuntime, } as Record; + const existingSnapshot = parseObject(base.baseRefSnapshot); + if ( + typeof existingSnapshot.resolvedSha !== "string" + && input.baseRefSha + ) { + base.baseRefSnapshot = { + baseRef: input.baseRef ?? null, + resolvedSha: input.baseRefSha, + }; + } + if (input.shouldReuseExisting || !input.configSnapshot) { return base; } @@ -624,6 +640,8 @@ export function buildRealizedExecutionWorkspaceFromPersisted(input: { } const strategy = input.workspace.strategyType === "git_worktree" ? "git_worktree" : "project_primary"; + const baseRefSnapshot = parseObject(input.workspace.metadata?.baseRefSnapshot); + const baseRefSha = typeof baseRefSnapshot.resolvedSha === "string" ? baseRefSnapshot.resolvedSha : null; return { baseCwd: input.base.baseCwd, source: input.workspace.mode === "shared_workspace" ? "project_primary" : "task_session", @@ -637,6 +655,7 @@ export function buildRealizedExecutionWorkspaceFromPersisted(input: { worktreePath: strategy === "git_worktree" ? (readNonEmptyString(input.workspace.providerRef) ?? cwd) : null, warnings: [], created: false, + baseRefSha, }; } @@ -1964,6 +1983,7 @@ async function buildPaperclipWakePayload(input: { }) { const executionStage = parseObject(input.contextSnapshot.executionStage); const commentIds = extractWakeCommentIds(input.contextSnapshot); + const annotationCommentId = readNonEmptyString(input.contextSnapshot.annotationCommentId); const issueId = readNonEmptyString(input.contextSnapshot.issueId); const continuationSummary = input.continuationSummary ?? null; const issueSummary = @@ -2054,6 +2074,57 @@ async function buildPaperclipWakePayload(input: { }); } + const annotationDeltas = annotationCommentId + ? await input.db + .select({ + id: documentAnnotationComments.id, + issueId: documentAnnotationComments.issueId, + threadId: documentAnnotationComments.threadId, + body: documentAnnotationComments.body, + authorType: documentAnnotationComments.authorType, + authorAgentId: documentAnnotationComments.authorAgentId, + authorUserId: documentAnnotationComments.authorUserId, + createdAt: documentAnnotationComments.createdAt, + documentKey: documentAnnotationThreads.documentKey, + status: documentAnnotationThreads.status, + anchorState: documentAnnotationThreads.anchorState, + anchorConfidence: documentAnnotationThreads.anchorConfidence, + currentRevisionNumber: documentAnnotationThreads.currentRevisionNumber, + selectedText: documentAnnotationThreads.selectedText, + prefixText: documentAnnotationThreads.prefixText, + suffixText: documentAnnotationThreads.suffixText, + }) + .from(documentAnnotationComments) + .innerJoin(documentAnnotationThreads, eq(documentAnnotationComments.threadId, documentAnnotationThreads.id)) + .where(and( + eq(documentAnnotationComments.companyId, input.companyId), + eq(documentAnnotationComments.id, annotationCommentId), + )) + .then((rows) => rows.map((row) => ({ + id: row.id, + issueId: row.issueId, + threadId: row.threadId, + documentKey: row.documentKey, + revisionNumber: row.currentRevisionNumber, + quote: row.selectedText, + prefix: row.prefixText, + suffix: row.suffixText, + threadStatus: row.status, + anchorState: row.anchorState, + anchorConfidence: row.anchorConfidence, + body: row.body.length > MAX_INLINE_WAKE_COMMENT_BODY_CHARS + ? row.body.slice(0, MAX_INLINE_WAKE_COMMENT_BODY_CHARS) + : row.body, + bodyTruncated: row.body.length > MAX_INLINE_WAKE_COMMENT_BODY_CHARS, + createdAt: row.createdAt.toISOString(), + author: row.authorAgentId + ? { type: "agent", id: row.authorAgentId } + : row.authorUserId + ? { type: "user", id: row.authorUserId } + : { type: row.authorType, id: null }, + }))) + : []; + return { reason: readNonEmptyString(input.contextSnapshot.wakeReason), issue: issueSummary @@ -2111,6 +2182,7 @@ async function buildPaperclipWakePayload(input: { commentIds, latestCommentId: commentIds[commentIds.length - 1] ?? null, comments, + annotationDeltas, commentWindow: { requestedCount: commentIds.length, includedCount: comments.length, @@ -4063,7 +4135,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {}) continuationAttempt: decision.nextAttempt, updatedAt: new Date(), }) - .where(eq(heartbeatRuns.id, continuationRun.id)); + .where(eq(heartbeatRuns.id, run.id)); } } @@ -7229,7 +7301,34 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {}) repoRef: resolvedWorkspace.repoRef, } satisfies ExecutionWorkspaceInput; const reusedExecutionWorkspace = shouldReuseExisting && existingExecutionWorkspace - ? buildRealizedExecutionWorkspaceFromPersisted({ + ? await ensurePersistedExecutionWorkspaceAvailable({ + base: executionWorkspaceBase, + workspace: { + mode: existingExecutionWorkspace.mode, + strategyType: existingExecutionWorkspace.strategyType, + cwd: existingExecutionWorkspace.cwd, + providerRef: existingExecutionWorkspace.providerRef, + projectId: existingExecutionWorkspace.projectId, + projectWorkspaceId: existingExecutionWorkspace.projectWorkspaceId, + repoUrl: existingExecutionWorkspace.repoUrl, + baseRef: existingExecutionWorkspace.baseRef, + branchName: existingExecutionWorkspace.branchName, + metadata: existingExecutionWorkspace.metadata as Record | null, + config: { + provisionCommand: + existingExecutionWorkspace.config?.provisionCommand + ?? projectExecutionWorkspacePolicy?.workspaceStrategy?.provisionCommand + ?? null, + }, + }, + issue: issueRef, + agent: { + id: agent.id, + name: agent.name, + companyId: agent.companyId, + }, + recorder: workspaceOperationRecorder, + }) ?? buildRealizedExecutionWorkspaceFromPersisted({ base: executionWorkspaceBase, workspace: existingExecutionWorkspace, }) @@ -7254,6 +7353,8 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {}) createdByRuntime: executionWorkspace.created, configSnapshot, shouldReuseExisting, + baseRef: executionWorkspace.repoRef, + baseRefSha: executionWorkspace.baseRefSha ?? null, }); try { persistedExecutionWorkspace = shouldReuseExisting && existingExecutionWorkspace diff --git a/server/src/services/portable-path.ts b/server/src/services/portable-path.ts new file mode 100644 index 00000000..8e4159a4 --- /dev/null +++ b/server/src/services/portable-path.ts @@ -0,0 +1,12 @@ +export function normalizePortablePath(input: string) { + const parts: string[] = []; + for (const segment of input.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "").split("/")) { + if (!segment || segment === ".") continue; + if (segment === "..") { + if (parts.length > 0) parts.pop(); + continue; + } + parts.push(segment); + } + return parts.join("/"); +} diff --git a/server/src/services/skills-catalog.ts b/server/src/services/skills-catalog.ts new file mode 100644 index 00000000..26a4900f --- /dev/null +++ b/server/src/services/skills-catalog.ts @@ -0,0 +1,201 @@ +import { existsSync, readFileSync, statSync } from "node:fs"; +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import type { + CatalogSkill, + CatalogSkillFileDetail, + CatalogSkillListQuery, +} from "@paperclipai/shared"; +import { HttpError, conflict, notFound } from "../errors.js"; +import { normalizePortablePath } from "./portable-path.js"; + +interface CatalogManifestFile { + packageName: string; + packageVersion: string; + skills: CatalogSkill[]; +} + +const serviceDir = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(serviceDir, "../../.."); +const catalogPackageRoot = path.join(repoRoot, "packages/skills-catalog"); +const catalogManifestPath = path.join(catalogPackageRoot, "generated/catalog.json"); +let cachedCatalogManifest: { + manifest: CatalogManifestFile; + mtimeMs: number; + size: number; +} | null = null; + +function loadCatalogManifest(): CatalogManifestFile { + if (!existsSync(catalogManifestPath)) { + throw new Error( + `Skills catalog manifest not found at ${catalogManifestPath}. Run pnpm --filter @paperclipai/skills-catalog build:manifest.`, + ); + } + return JSON.parse(readFileSync(catalogManifestPath, "utf8")) as CatalogManifestFile; +} + +function getCatalogManifest() { + if (!existsSync(catalogManifestPath)) { + throw new Error( + `Skills catalog manifest not found at ${catalogManifestPath}. Run pnpm --filter @paperclipai/skills-catalog build:manifest.`, + ); + } + const stats = statSync(catalogManifestPath); + if ( + cachedCatalogManifest && + cachedCatalogManifest.mtimeMs === stats.mtimeMs && + cachedCatalogManifest.size === stats.size + ) { + return cachedCatalogManifest.manifest; + } + + const manifest = loadCatalogManifest(); + cachedCatalogManifest = { + manifest, + mtimeMs: stats.mtimeMs, + size: stats.size, + }; + return manifest; +} + +function getCatalogSkills() { + const catalogManifest = getCatalogManifest(); + return catalogManifest.skills.map((skill) => ({ + ...skill, + packageName: catalogManifest.packageName, + packageVersion: catalogManifest.packageVersion, + })); +} + +function isMarkdownPath(filePath: string) { + const fileName = path.posix.basename(filePath).toLowerCase(); + return fileName === "skill.md" || fileName.endsWith(".md"); +} + +function inferLanguageFromPath(filePath: string) { + const fileName = path.posix.basename(filePath).toLowerCase(); + if (fileName === "skill.md" || fileName.endsWith(".md")) return "markdown"; + if (fileName.endsWith(".ts")) return "typescript"; + if (fileName.endsWith(".tsx")) return "tsx"; + if (fileName.endsWith(".js")) return "javascript"; + if (fileName.endsWith(".jsx")) return "jsx"; + if (fileName.endsWith(".json")) return "json"; + if (fileName.endsWith(".yml") || fileName.endsWith(".yaml")) return "yaml"; + if (fileName.endsWith(".sh")) return "bash"; + if (fileName.endsWith(".py")) return "python"; + if (fileName.endsWith(".html")) return "html"; + if (fileName.endsWith(".css")) return "css"; + return null; +} + +function resolveCatalogPackageRoot() { + return catalogPackageRoot; +} + +function searchText(skill: CatalogSkill) { + return [ + skill.id, + skill.key, + skill.slug, + skill.name, + skill.description, + skill.category, + skill.kind, + ...skill.recommendedForRoles, + ...skill.tags, + ].join("\n").toLowerCase(); +} + +export function listCatalogSkills(query: CatalogSkillListQuery = {}): CatalogSkill[] { + const normalizedQuery = query.q?.trim().toLowerCase() ?? ""; + return getCatalogSkills() + .filter((skill) => !query.kind || skill.kind === query.kind) + .filter((skill) => !query.category || skill.category === query.category) + .filter((skill) => !normalizedQuery || searchText(skill).includes(normalizedQuery)) + .sort((left, right) => left.name.localeCompare(right.name) || left.key.localeCompare(right.key)); +} + +export function resolveCatalogSkillReference(reference: string): { skill: CatalogSkill | null; ambiguous: boolean } { + const trimmed = reference.trim(); + if (!trimmed) return { skill: null, ambiguous: false }; + const catalogSkills = getCatalogSkills(); + + const exact = catalogSkills.find((skill) => skill.id === trimmed || skill.key === trimmed); + if (exact) return { skill: exact, ambiguous: false }; + + const slugMatches = catalogSkills.filter((skill) => skill.slug === trimmed); + if (slugMatches.length === 1) return { skill: slugMatches[0]!, ambiguous: false }; + if (slugMatches.length > 1) return { skill: null, ambiguous: true }; + return { skill: null, ambiguous: false }; +} + +export function getCatalogSkillOrThrow(reference: string): CatalogSkill { + const result = resolveCatalogSkillReference(reference); + if (result.ambiguous) { + throw conflict(`Catalog skill slug "${reference}" is ambiguous. Use an id or key.`); + } + if (!result.skill) { + throw notFound("Catalog skill not found"); + } + return result.skill; +} + +export async function readCatalogSkillFile( + reference: string, + relativePath = "SKILL.md", +): Promise { + const skill = getCatalogSkillOrThrow(reference); + const normalizedPath = normalizePortablePath(relativePath || "SKILL.md"); + const fileEntry = skill.files.find((entry) => entry.path === normalizedPath); + if (!fileEntry) { + throw notFound("Catalog skill file not found"); + } + + const packageRoot = resolveCatalogPackageRoot(); + const absolutePath = path.resolve(packageRoot, skill.path, normalizedPath); + const skillRoot = path.resolve(packageRoot, skill.path); + if (absolutePath !== skillRoot && !absolutePath.startsWith(`${skillRoot}${path.sep}`)) { + throw notFound("Catalog skill file not found"); + } + + if (fileEntry.kind === "asset") { + throw new HttpError(415, "Catalog asset previews are not supported."); + } + + const content = await fs.readFile(absolutePath, "utf8"); + return { + catalogSkillId: skill.id, + path: normalizedPath, + kind: fileEntry.kind, + content, + language: inferLanguageFromPath(normalizedPath), + markdown: isMarkdownPath(normalizedPath), + }; +} + +export async function copyCatalogSkillFile(reference: string, relativePath: string, targetPath: string): Promise { + const skill = getCatalogSkillOrThrow(reference); + const normalizedPath = normalizePortablePath(relativePath || "SKILL.md"); + const fileEntry = skill.files.find((entry) => entry.path === normalizedPath); + if (!fileEntry) { + throw notFound("Catalog skill file not found"); + } + + const packageRoot = resolveCatalogPackageRoot(); + const absolutePath = path.resolve(packageRoot, skill.path, normalizedPath); + const skillRoot = path.resolve(packageRoot, skill.path); + if (absolutePath !== skillRoot && !absolutePath.startsWith(`${skillRoot}${path.sep}`)) { + throw notFound("Catalog skill file not found"); + } + + await fs.copyFile(absolutePath, targetPath); +} + +export function getCatalogPackageMetadata() { + const catalogManifest = getCatalogManifest(); + return { + packageName: catalogManifest.packageName, + packageVersion: catalogManifest.packageVersion, + }; +} diff --git a/server/src/services/workspace-runtime.ts b/server/src/services/workspace-runtime.ts index b601f495..5d22c743 100644 --- a/server/src/services/workspace-runtime.ts +++ b/server/src/services/workspace-runtime.ts @@ -67,6 +67,7 @@ export interface RealizedExecutionWorkspace extends ExecutionWorkspaceInput { worktreePath: string | null; warnings: string[]; created: boolean; + baseRefSha?: string | null; } export interface RuntimeServiceRef { @@ -524,11 +525,110 @@ async function runGit(args: string[], cwd: string): Promise { return proc.stdout.trim(); } +function formatShortSha(value: string | null | undefined) { + return value ? value.slice(0, 12) : "unknown"; +} + function gitErrorIncludes(error: unknown, needle: string) { const message = error instanceof Error ? error.message : String(error); return message.toLowerCase().includes(needle.toLowerCase()); } +function parseRemoteTrackingRef(ref: string): { remote: string; branch: string } | null { + const trimmed = ref.trim(); + const refsRemotesPrefix = "refs/remotes/"; + const normalized = trimmed.startsWith(refsRemotesPrefix) + ? trimmed.slice(refsRemotesPrefix.length) + : trimmed; + const slashIndex = normalized.indexOf("/"); + if (slashIndex <= 0 || slashIndex === normalized.length - 1) return null; + const remote = normalized.slice(0, slashIndex); + const branch = normalized.slice(slashIndex + 1); + if (!/^[A-Za-z0-9._-]+$/.test(remote)) return null; + return { remote, branch }; +} + +async function refreshRemoteTrackingBaseRef(repoRoot: string, baseRef: string): Promise { + const remoteTracking = parseRemoteTrackingRef(baseRef); + if (!remoteTracking) return []; + + const remoteExists = await runGit(["remote", "get-url", remoteTracking.remote], repoRoot) + .then(() => true) + .catch(() => false); + if (!remoteExists) return []; + + try { + await runGit([ + "fetch", + "--prune", + remoteTracking.remote, + `+refs/heads/${remoteTracking.branch}:refs/remotes/${remoteTracking.remote}/${remoteTracking.branch}`, + ], repoRoot); + return []; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return [`Could not refresh base ref ${baseRef} before preparing the execution workspace: ${message}`]; + } +} + +async function resolveBaseRefSha(repoRoot: string, baseRef: string): Promise { + return await runGit(["rev-parse", "--verify", `${baseRef}^{commit}`], repoRoot).catch(() => null); +} + +function readRecordedBaseRefSha(metadata: Record | null | undefined): string | null { + const snapshot = parseObject(metadata?.baseRefSnapshot); + const resolvedSha = snapshot.resolvedSha; + return typeof resolvedSha === "string" && resolvedSha.trim().length > 0 ? resolvedSha.trim() : null; +} + +export async function inspectExecutionWorkspaceBaseDrift(input: { + repoRoot: string; + worktreePath: string; + branchName: string | null; + baseRef: string | null; + recordedBaseRefSha?: string | null; + skipRefresh?: boolean; +}): Promise<{ + warnings: string[]; + currentBaseRefSha: string | null; + branchBaseRefSha: string | null; +}> { + const baseRef = input.baseRef?.trim(); + if (!baseRef) { + return { warnings: [], currentBaseRefSha: null, branchBaseRefSha: null }; + } + + const warnings = input.skipRefresh ? [] : await refreshRemoteTrackingBaseRef(input.repoRoot, baseRef); + const currentBaseRefSha = await resolveBaseRefSha(input.repoRoot, baseRef); + if (!currentBaseRefSha) { + warnings.push(`Could not resolve base ref ${baseRef} while checking execution workspace freshness.`); + return { warnings, currentBaseRefSha: null, branchBaseRefSha: null }; + } + + const branchBaseRefSha = await runGit(["merge-base", "HEAD", baseRef], input.worktreePath).catch(() => null); + if (!branchBaseRefSha) { + warnings.push(`Could not compare execution workspace ${input.branchName ?? "branch"} against base ref ${baseRef}.`); + return { warnings, currentBaseRefSha, branchBaseRefSha: null }; + } + + if (branchBaseRefSha !== currentBaseRefSha) { + const behindCountRaw = await runGit(["rev-list", "--count", `HEAD..${baseRef}`], input.worktreePath).catch(() => ""); + const behindCount = Number.parseInt(behindCountRaw, 10); + const behindText = Number.isFinite(behindCount) && behindCount > 0 + ? `${behindCount} commit${behindCount === 1 ? "" : "s"}` + : "newer commits"; + const recordedText = input.recordedBaseRefSha + ? `recorded base ${formatShortSha(input.recordedBaseRefSha)}` + : `merge-base ${formatShortSha(branchBaseRefSha)}`; + warnings.push( + `Execution workspace branch ${input.branchName ? `"${input.branchName}"` : "HEAD"} is behind ${baseRef} by ${behindText}: ${recordedText}, current base ${formatShortSha(currentBaseRefSha)}. Refresh or rebase the workspace before relying on recent base-branch fixes.`, + ); + } + + return { warnings, currentBaseRefSha, branchBaseRefSha }; +} + + type GitWorktreeListEntry = { worktree: string; branch: string | null; @@ -591,22 +691,31 @@ async function isGitCheckout(cwd: string): Promise { } async function detectDefaultBranch(repoRoot: string): Promise { + const originMasterRef = "origin/master"; + await refreshRemoteTrackingBaseRef(repoRoot, originMasterRef); + if (await resolveBaseRefSha(repoRoot, originMasterRef)) { + return originMasterRef; + } + // Try the explicit remote HEAD first (set by git clone or git remote set-head) try { const remoteHead = await runGit( ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], repoRoot, ); - const branch = remoteHead?.startsWith("origin/") ? remoteHead.slice("origin/".length) : remoteHead; - if (branch) return branch; + if (remoteHead) { + await refreshRemoteTrackingBaseRef(repoRoot, remoteHead); + if (await resolveBaseRefSha(repoRoot, remoteHead)) return remoteHead; + } } catch { // Not set — fall through to heuristic } // Fallback: check for common default branch names on the remote - for (const candidate of ["main", "master"]) { + for (const candidate of ["origin/master", "origin/main", "main", "master"]) { try { - await runGit(["rev-parse", "--verify", `refs/remotes/origin/${candidate}`], repoRoot); + await refreshRemoteTrackingBaseRef(repoRoot, candidate); + await runGit(["rev-parse", "--verify", `${candidate}^{commit}`], repoRoot); return candidate; } catch { // Not found — try next @@ -1003,6 +1112,7 @@ export async function realizeExecutionWorkspace(input: { worktreePath: null, warnings: [], created: false, + baseRefSha: null, }; } @@ -1026,10 +1136,20 @@ export async function realizeExecutionWorkspace(input: { const baseRef = configuredBaseRef ?? await detectDefaultBranch(repoRoot) ?? "HEAD"; + const baseRefreshWarnings = await refreshRemoteTrackingBaseRef(repoRoot, baseRef); + const currentBaseRefSha = await resolveBaseRefSha(repoRoot, baseRef); await fs.mkdir(worktreeParentDir, { recursive: true }); async function reuseExistingWorktree(reusablePath: string) { + const baseDrift = await inspectExecutionWorkspaceBaseDrift({ + repoRoot, + worktreePath: reusablePath, + branchName, + baseRef, + recordedBaseRefSha: null, + skipRefresh: true, + }); if (input.recorder) { await input.recorder.recordOperation({ phase: "worktree_prepare", @@ -1039,6 +1159,8 @@ export async function realizeExecutionWorkspace(input: { worktreePath: reusablePath, branchName, baseRef, + currentBaseRefSha: baseDrift.currentBaseRefSha, + branchBaseRefSha: baseDrift.branchBaseRefSha, created: false, reused: true, }, @@ -1066,8 +1188,9 @@ export async function realizeExecutionWorkspace(input: { cwd: reusablePath, branchName, worktreePath: reusablePath, - warnings: [], + warnings: [...baseRefreshWarnings, ...baseDrift.warnings], created: false, + baseRefSha: baseDrift.branchBaseRefSha ?? baseDrift.currentBaseRefSha, }; } @@ -1109,6 +1232,7 @@ export async function realizeExecutionWorkspace(input: { worktreePath, branchName, baseRef, + baseRefSha: currentBaseRefSha, created: true, }, successMessage: `Created git worktree at ${worktreePath}\n`, @@ -1128,6 +1252,7 @@ export async function realizeExecutionWorkspace(input: { worktreePath, branchName, baseRef, + baseRefSha: currentBaseRefSha, created: false, reusedExistingBranch: true, }, @@ -1163,8 +1288,9 @@ export async function realizeExecutionWorkspace(input: { cwd: worktreePath, branchName, worktreePath, - warnings: [], + warnings: baseRefreshWarnings, created: true, + baseRefSha: currentBaseRefSha, }; } @@ -1180,6 +1306,7 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: { repoUrl: string | null | undefined; baseRef: string | null | undefined; branchName: string | null | undefined; + metadata?: Record | null; config?: { provisionCommand?: string | null; } | null; @@ -1205,15 +1332,26 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: { worktreePath: strategy === "git_worktree" ? (input.workspace.providerRef ?? cwd) : null, warnings: [], created: false, + baseRefSha: readRecordedBaseRefSha(input.workspace.metadata), }; const provisionCommand = asString(input.workspace.config?.provisionCommand, "").trim(); if (strategy !== "git_worktree") { return realized; } + const repoRoot = await runGit(["rev-parse", "--show-toplevel"], input.base.baseCwd); + const recordedBaseRefSha = readRecordedBaseRefSha(input.workspace.metadata); if (await directoryExists(cwd)) { + const baseDrift = await inspectExecutionWorkspaceBaseDrift({ + repoRoot, + worktreePath: realized.worktreePath ?? cwd, + branchName: realized.branchName, + baseRef: input.workspace.baseRef ?? input.base.repoRef ?? null, + recordedBaseRefSha, + }); + realized.warnings = baseDrift.warnings; + realized.baseRefSha = recordedBaseRefSha ?? baseDrift.branchBaseRefSha ?? baseDrift.currentBaseRefSha; if (provisionCommand) { - const repoRoot = await runGit(["rev-parse", "--show-toplevel"], input.base.baseCwd); await provisionExecutionWorktree({ strategy: { type: "git_worktree", @@ -1232,7 +1370,6 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: { return realized; } - const repoRoot = await runGit(["rev-parse", "--show-toplevel"], input.base.baseCwd); const worktreePath = realized.worktreePath ?? cwd; const branchName = asString(input.workspace.branchName, "").trim(); if (!branchName) { @@ -1241,6 +1378,9 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: { await fs.mkdir(path.dirname(worktreePath), { recursive: true }); await runGit(["worktree", "prune"], repoRoot).catch(() => {}); + const restoreBaseRef = input.workspace.baseRef ?? input.base.repoRef ?? null; + const restoreRefreshWarnings = restoreBaseRef ? await refreshRemoteTrackingBaseRef(repoRoot, restoreBaseRef) : []; + const restoreCurrentBaseRefSha = restoreBaseRef ? await resolveBaseRefSha(repoRoot, restoreBaseRef) : null; let created = false; try { @@ -1253,6 +1393,7 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: { worktreePath, branchName, baseRef: input.workspace.baseRef ?? input.base.repoRef ?? null, + currentBaseRefSha: restoreCurrentBaseRefSha, created: false, restored: true, }, @@ -1268,6 +1409,7 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: { throw error; } const baseRef = input.workspace.baseRef ?? await detectDefaultBranch(repoRoot) ?? "HEAD"; + const recreatedBaseRefSha = await resolveBaseRefSha(repoRoot, baseRef); await recordGitOperation(input.recorder, { phase: "worktree_prepare", args: ["worktree", "add", "-b", branchName, worktreePath, baseRef], @@ -1277,6 +1419,7 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: { worktreePath, branchName, baseRef, + baseRefSha: recreatedBaseRefSha, created: true, restored: true, }, @@ -1286,6 +1429,15 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: { created = true; } + const baseDrift = await inspectExecutionWorkspaceBaseDrift({ + repoRoot, + worktreePath, + branchName, + baseRef: input.workspace.baseRef ?? input.base.repoRef ?? null, + recordedBaseRefSha, + skipRefresh: true, + }); + await provisionExecutionWorktree({ strategy: { type: "git_worktree", @@ -1305,7 +1457,12 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: { ...realized, cwd: worktreePath, worktreePath, + warnings: [...restoreRefreshWarnings, ...baseDrift.warnings], created, + baseRefSha: + recordedBaseRefSha + ?? (created ? restoreCurrentBaseRefSha : baseDrift.branchBaseRefSha) + ?? baseDrift.currentBaseRefSha, }; }