From 5964dab891ad07aa951ae7159bbbd3e1a66748b8 Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 28 Aug 2026 00:42:35 +0000 Subject: [PATCH] feat(db): hierarchy record class schema + witnesses (contract 1, M4-1a) (#1459) --- packages/db/drizzle/0018_clean_cobalt_man.sql | 63 + packages/db/drizzle/meta/0018_snapshot.json | 5034 +++++++++++++++++ packages/db/drizzle/meta/_journal.json | 9 +- .../db/src/hierarchy-schema.witness.test.ts | 501 ++ .../db/src/hierarchy-writer-coverage.test.ts | 2307 ++++++++ packages/db/src/schema.ts | 104 + 6 files changed, 8017 insertions(+), 1 deletion(-) create mode 100644 packages/db/drizzle/0018_clean_cobalt_man.sql create mode 100644 packages/db/drizzle/meta/0018_snapshot.json create mode 100644 packages/db/src/hierarchy-schema.witness.test.ts create mode 100644 packages/db/src/hierarchy-writer-coverage.test.ts diff --git a/packages/db/drizzle/0018_clean_cobalt_man.sql b/packages/db/drizzle/0018_clean_cobalt_man.sql new file mode 100644 index 00000000..3b1221d6 --- /dev/null +++ b/packages/db/drizzle/0018_clean_cobalt_man.sql @@ -0,0 +1,63 @@ +CREATE TABLE "companies" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text NOT NULL, + "slug" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "companies_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "estates" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text NOT NULL, + "slug" text NOT NULL, + "company_id" uuid NOT NULL, + CONSTRAINT "estates_company_slug_uniq" UNIQUE("company_id","slug") +); +--> statement-breakpoint +CREATE TABLE "hierarchy_grants" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" text, + "team_id" uuid, + "company_id" uuid, + "estate_id" uuid, + "platform_project_id" uuid, + "role" text NOT NULL, + "granted_by" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "hierarchy_grants_subject_target_role_uniq" UNIQUE NULLS NOT DISTINCT("user_id","team_id","company_id","estate_id","platform_project_id","role"), + CONSTRAINT "hierarchy_grants_subject_check" CHECK (num_nonnulls(user_id, team_id) = 1), + CONSTRAINT "hierarchy_grants_target_check" CHECK (num_nonnulls(company_id, estate_id, platform_project_id) = 1) +); +--> statement-breakpoint +CREATE TABLE "platform_projects" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text NOT NULL, + "slug" text NOT NULL, + "estate_id" uuid NOT NULL, + CONSTRAINT "platform_projects_estate_slug_uniq" UNIQUE("estate_id","slug") +); +--> statement-breakpoint +CREATE TABLE "workspaces" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text NOT NULL, + "slug" text NOT NULL, + "platform_project_id" uuid NOT NULL, + CONSTRAINT "workspaces_platform_project_slug_uniq" UNIQUE("platform_project_id","slug") +); +--> statement-breakpoint +ALTER TABLE "estates" ADD CONSTRAINT "estates_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "hierarchy_grants" ADD CONSTRAINT "hierarchy_grants_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "hierarchy_grants" ADD CONSTRAINT "hierarchy_grants_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."teams"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "hierarchy_grants" ADD CONSTRAINT "hierarchy_grants_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "hierarchy_grants" ADD CONSTRAINT "hierarchy_grants_estate_id_estates_id_fk" FOREIGN KEY ("estate_id") REFERENCES "public"."estates"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "hierarchy_grants" ADD CONSTRAINT "hierarchy_grants_platform_project_id_platform_projects_id_fk" FOREIGN KEY ("platform_project_id") REFERENCES "public"."platform_projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "hierarchy_grants" ADD CONSTRAINT "hierarchy_grants_granted_by_users_id_fk" FOREIGN KEY ("granted_by") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "platform_projects" ADD CONSTRAINT "platform_projects_estate_id_estates_id_fk" FOREIGN KEY ("estate_id") REFERENCES "public"."estates"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workspaces" ADD CONSTRAINT "workspaces_platform_project_id_platform_projects_id_fk" FOREIGN KEY ("platform_project_id") REFERENCES "public"."platform_projects"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "hierarchy_grants_company_id_idx" ON "hierarchy_grants" USING btree ("company_id");--> statement-breakpoint +CREATE INDEX "hierarchy_grants_estate_id_idx" ON "hierarchy_grants" USING btree ("estate_id");--> statement-breakpoint +CREATE INDEX "hierarchy_grants_platform_project_id_idx" ON "hierarchy_grants" USING btree ("platform_project_id");--> statement-breakpoint +CREATE INDEX "hierarchy_grants_user_id_idx" ON "hierarchy_grants" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "hierarchy_grants_team_id_idx" ON "hierarchy_grants" USING btree ("team_id");--> statement-breakpoint +CREATE INDEX "hierarchy_grants_granted_by_idx" ON "hierarchy_grants" USING btree ("granted_by"); \ No newline at end of file diff --git a/packages/db/drizzle/meta/0018_snapshot.json b/packages/db/drizzle/meta/0018_snapshot.json new file mode 100644 index 00000000..25c62eac --- /dev/null +++ b/packages/db/drizzle/meta/0018_snapshot.json @@ -0,0 +1,5034 @@ +{ + "id": "12c270b3-4f57-4f30-bcfd-6adddca4263a", + "prevId": "845a2f87-ad97-49a1-a0b0-0a9c39f732b1", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "accounts_provider_account_idx": { + "name": "accounts_provider_account_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "accounts_user_id_idx": { + "name": "accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.admin_tokens": { + "name": "admin_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "admin_tokens_user_id_idx": { + "name": "admin_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "admin_tokens_hash_idx": { + "name": "admin_tokens_hash_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "admin_tokens_user_id_users_id_fk": { + "name": "admin_tokens_user_id_users_id_fk", + "tableFrom": "admin_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_logs": { + "name": "agent_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'hot'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "summarized_at": { + "name": "summarized_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_logs_session_tier_idx": { + "name": "agent_logs_session_tier_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_logs_user_id_idx": { + "name": "agent_logs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_logs_tier_created_at_idx": { + "name": "agent_logs_tier_created_at_idx", + "columns": [ + { + "expression": "tier", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_logs_user_id_users_id_fk": { + "name": "agent_logs_user_id_users_id_fk", + "tableFrom": "agent_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_tools": { + "name": "allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "skills": { + "name": "skills", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "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_project_id_idx": { + "name": "agents_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_owner_id_idx": { + "name": "agents_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_is_system_idx": { + "name": "agents_is_system_idx", + "columns": [ + { + "expression": "is_system", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_project_id_projects_id_fk": { + "name": "agents_project_id_projects_id_fk", + "tableFrom": "agents", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "agents_owner_id_users_id_fk": { + "name": "agents_owner_id_users_id_fk", + "tableFrom": "agents", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.appreciations": { + "name": "appreciations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "from_user": { + "name": "from_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_user": { + "name": "to_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backlog": { + "name": "backlog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "backlog_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "depends_on": { + "name": "depends_on", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "claim_owner": { + "name": "claim_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_ttl_seconds": { + "name": "claim_ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acceptance": { + "name": "acceptance", + "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": { + "backlog_status_priority_idx": { + "name": "backlog_status_priority_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backlog_status_claimed_at_idx": { + "name": "backlog_status_claimed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backlog_idempotency_key_idx": { + "name": "backlog_idempotency_key_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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 + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "companies_slug_unique": { + "name": "companies_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connector_lease_audit_log": { + "name": "connector_lease_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logical_agent_id": { + "name": "logical_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lease_id": { + "name": "lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_epoch": { + "name": "lease_epoch", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "connector_lease_audit_binding_occurred_idx": { + "name": "connector_lease_audit_binding_occurred_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "logical_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connector_lease_audit_correlation_idx": { + "name": "connector_lease_audit_correlation_idx", + "columns": [ + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversations": { + "name": "conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "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": { + "conversations_user_archived_idx": { + "name": "conversations_user_archived_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "conversations_project_id_idx": { + "name": "conversations_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "conversations_agent_id_idx": { + "name": "conversations_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "conversations_user_id_users_id_fk": { + "name": "conversations_user_id_users_id_fk", + "tableFrom": "conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "conversations_project_id_projects_id_fk": { + "name": "conversations_project_id_projects_id_fk", + "tableFrom": "conversations", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "conversations_agent_id_agents_id_fk": { + "name": "conversations_agent_id_agents_id_fk", + "tableFrom": "conversations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.estates": { + "name": "estates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "estates_company_id_companies_id_fk": { + "name": "estates_company_id_companies_id_fk", + "tableFrom": "estates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "estates_company_slug_uniq": { + "name": "estates_company_slug_uniq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "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()" + } + }, + "indexes": { + "events_type_idx": { + "name": "events_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "events_date_idx": { + "name": "events_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_audit_log": { + "name": "federation_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "verb": { + "name": "verb", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "denied_reason": { + "name": "denied_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "query_hash": { + "name": "query_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bytes_out": { + "name": "bytes_out", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_audit_log_peer_created_at_idx": { + "name": "federation_audit_log_peer_created_at_idx", + "columns": [ + { + "expression": "peer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_audit_log_subject_created_at_idx": { + "name": "federation_audit_log_subject_created_at_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_audit_log_created_at_idx": { + "name": "federation_audit_log_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "federation_audit_log_peer_id_federation_peers_id_fk": { + "name": "federation_audit_log_peer_id_federation_peers_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "federation_audit_log_subject_user_id_users_id_fk": { + "name": "federation_audit_log_subject_user_id_users_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "users", + "columnsFrom": [ + "subject_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "federation_audit_log_grant_id_federation_grants_id_fk": { + "name": "federation_audit_log_grant_id_federation_grants_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "federation_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_enrollment_tokens": { + "name": "federation_enrollment_tokens", + "schema": "", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "federation_enrollment_tokens_grant_id_federation_grants_id_fk": { + "name": "federation_enrollment_tokens_grant_id_federation_grants_id_fk", + "tableFrom": "federation_enrollment_tokens", + "tableTo": "federation_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_enrollment_tokens_peer_id_federation_peers_id_fk": { + "name": "federation_enrollment_tokens_peer_id_federation_peers_id_fk", + "tableFrom": "federation_enrollment_tokens", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_grants": { + "name": "federation_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "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()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_reason": { + "name": "revoked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_grants_subject_status_idx": { + "name": "federation_grants_subject_status_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_grants_peer_status_idx": { + "name": "federation_grants_peer_status_idx", + "columns": [ + { + "expression": "peer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "federation_grants_subject_user_id_users_id_fk": { + "name": "federation_grants_subject_user_id_users_id_fk", + "tableFrom": "federation_grants", + "tableTo": "users", + "columnsFrom": [ + "subject_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_grants_peer_id_federation_peers_id_fk": { + "name": "federation_grants_peer_id_federation_peers_id_fk", + "tableFrom": "federation_grants", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_peers": { + "name": "federation_peers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "common_name": { + "name": "common_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_pem": { + "name": "cert_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_serial": { + "name": "cert_serial", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_not_after": { + "name": "cert_not_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "client_key_pem": { + "name": "client_key_pem", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "peer_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "endpoint_url": { + "name": "endpoint_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_peers_cert_serial_idx": { + "name": "federation_peers_cert_serial_idx", + "columns": [ + { + "expression": "cert_serial", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_peers_state_idx": { + "name": "federation_peers_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "federation_peers_common_name_unique": { + "name": "federation_peers_common_name_unique", + "nullsNotDistinct": false, + "columns": [ + "common_name" + ] + }, + "federation_peers_cert_serial_unique": { + "name": "federation_peers_cert_serial_unique", + "nullsNotDistinct": false, + "columns": [ + "cert_serial" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.hierarchy_grants": { + "name": "hierarchy_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "estate_id": { + "name": "estate_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform_project_id": { + "name": "platform_project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "hierarchy_grants_company_id_idx": { + "name": "hierarchy_grants_company_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "hierarchy_grants_estate_id_idx": { + "name": "hierarchy_grants_estate_id_idx", + "columns": [ + { + "expression": "estate_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "hierarchy_grants_platform_project_id_idx": { + "name": "hierarchy_grants_platform_project_id_idx", + "columns": [ + { + "expression": "platform_project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "hierarchy_grants_user_id_idx": { + "name": "hierarchy_grants_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "hierarchy_grants_team_id_idx": { + "name": "hierarchy_grants_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "hierarchy_grants_granted_by_idx": { + "name": "hierarchy_grants_granted_by_idx", + "columns": [ + { + "expression": "granted_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "hierarchy_grants_user_id_users_id_fk": { + "name": "hierarchy_grants_user_id_users_id_fk", + "tableFrom": "hierarchy_grants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "hierarchy_grants_team_id_teams_id_fk": { + "name": "hierarchy_grants_team_id_teams_id_fk", + "tableFrom": "hierarchy_grants", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "hierarchy_grants_company_id_companies_id_fk": { + "name": "hierarchy_grants_company_id_companies_id_fk", + "tableFrom": "hierarchy_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "hierarchy_grants_estate_id_estates_id_fk": { + "name": "hierarchy_grants_estate_id_estates_id_fk", + "tableFrom": "hierarchy_grants", + "tableTo": "estates", + "columnsFrom": [ + "estate_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "hierarchy_grants_platform_project_id_platform_projects_id_fk": { + "name": "hierarchy_grants_platform_project_id_platform_projects_id_fk", + "tableFrom": "hierarchy_grants", + "tableTo": "platform_projects", + "columnsFrom": [ + "platform_project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "hierarchy_grants_granted_by_users_id_fk": { + "name": "hierarchy_grants_granted_by_users_id_fk", + "tableFrom": "hierarchy_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "hierarchy_grants_subject_target_role_uniq": { + "name": "hierarchy_grants_subject_target_role_uniq", + "nullsNotDistinct": true, + "columns": [ + "user_id", + "team_id", + "company_id", + "estate_id", + "platform_project_id", + "role" + ] + } + }, + "policies": {}, + "checkConstraints": { + "hierarchy_grants_subject_check": { + "name": "hierarchy_grants_subject_check", + "value": "num_nonnulls(user_id, team_id) = 1" + }, + "hierarchy_grants_target_check": { + "name": "hierarchy_grants_target_check", + "value": "num_nonnulls(company_id, estate_id, platform_project_id) = 1" + } + }, + "isRLSEnabled": false + }, + "public.insights": { + "name": "insights", + "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 + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "relevance_score": { + "name": "relevance_score", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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()" + }, + "decayed_at": { + "name": "decayed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "insights_user_id_idx": { + "name": "insights_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "insights_category_idx": { + "name": "insights_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "insights_relevance_idx": { + "name": "insights_relevance_idx", + "columns": [ + { + "expression": "relevance_score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "insights_user_id_users_id_fk": { + "name": "insights_user_id_users_id_fk", + "tableFrom": "insights", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interaction_checkpoints": { + "name": "interaction_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_digest": { + "name": "content_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "compaction_epoch": { + "name": "compaction_epoch", + "type": "integer", + "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": { + "interaction_checkpoints_session_idempotency_idx": { + "name": "interaction_checkpoints_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interaction_checkpoints_session_epoch_idx": { + "name": "interaction_checkpoints_session_epoch_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "compaction_epoch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interaction_checkpoints_session_id_interaction_sessions_id_fk": { + "name": "interaction_checkpoints_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_checkpoints", + "tableTo": "interaction_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interaction_handoffs": { + "name": "interaction_handoffs", + "schema": "", + "columns": { + "handoff_id": { + "name": "handoff_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "interaction_handoff_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "interaction_handoffs_session_status_idx": { + "name": "interaction_handoffs_session_status_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interaction_handoffs_session_id_interaction_sessions_id_fk": { + "name": "interaction_handoffs_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_handoffs", + "tableTo": "interaction_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interaction_inbox": { + "name": "interaction_inbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_digest": { + "name": "content_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "interaction_inbox_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "interaction_inbox_session_idempotency_idx": { + "name": "interaction_inbox_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interaction_inbox_session_status_created_idx": { + "name": "interaction_inbox_session_status_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interaction_inbox_session_id_interaction_sessions_id_fk": { + "name": "interaction_inbox_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_inbox", + "tableTo": "interaction_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interaction_outbox": { + "name": "interaction_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_digest": { + "name": "content_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "interaction_outbox_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "interaction_outbox_session_idempotency_idx": { + "name": "interaction_outbox_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interaction_outbox_session_status_created_idx": { + "name": "interaction_outbox_session_status_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interaction_outbox_session_id_interaction_sessions_id_fk": { + "name": "interaction_outbox_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_outbox", + "tableTo": "interaction_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interaction_sessions": { + "name": "interaction_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runtime_session_id": { + "name": "runtime_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "interaction_sessions_owner_id_users_id_fk": { + "name": "interaction_sessions_owner_id_users_id_fk", + "tableFrom": "interaction_sessions", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.logical_agent_connector_leases": { + "name": "logical_agent_connector_leases", + "schema": "", + "columns": { + "lease_id": { + "name": "lease_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logical_agent_id": { + "name": "logical_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "lease_epoch": { + "name": "lease_epoch", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "acquired_at": { + "name": "acquired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "logical_agent_connector_lease_binding_idx": { + "name": "logical_agent_connector_lease_binding_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "logical_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "logical_agent_connector_lease_expiry_idx": { + "name": "logical_agent_connector_lease_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "logical_agent_connector_lease_connector_idx": { + "name": "logical_agent_connector_lease_connector_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "messages_conversation_id_idx": { + "name": "messages_conversation_id_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_conversation_id_conversations_id_fk": { + "name": "messages_conversation_id_conversations_id_fk", + "tableFrom": "messages", + "tableTo": "conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mission_tasks": { + "name": "mission_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not-started'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr": { + "name": "pr", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mission_tasks_mission_id_idx": { + "name": "mission_tasks_mission_id_idx", + "columns": [ + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_task_id_idx": { + "name": "mission_tasks_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_user_id_idx": { + "name": "mission_tasks_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_status_idx": { + "name": "mission_tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mission_tasks_mission_id_missions_id_fk": { + "name": "mission_tasks_mission_id_missions_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mission_tasks_task_id_tasks_id_fk": { + "name": "mission_tasks_task_id_tasks_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mission_tasks_user_id_users_id_fk": { + "name": "mission_tasks_user_id_users_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.missions": { + "name": "missions", + "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": "'planning'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "milestones": { + "name": "milestones", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "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": { + "missions_project_id_idx": { + "name": "missions_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "missions_user_id_idx": { + "name": "missions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "missions_project_id_projects_id_fk": { + "name": "missions_project_id_projects_id_fk", + "tableFrom": "missions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "missions_user_id_users_id_fk": { + "name": "missions_user_id_users_id_fk", + "tableFrom": "missions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.platform_projects": { + "name": "platform_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "estate_id": { + "name": "estate_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "platform_projects_estate_id_estates_id_fk": { + "name": "platform_projects_estate_id_estates_id_fk", + "tableFrom": "platform_projects", + "tableTo": "estates", + "columnsFrom": [ + "estate_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "platform_projects_estate_slug_uniq": { + "name": "platform_projects_estate_slug_uniq", + "nullsNotDistinct": false, + "columns": [ + "estate_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preferences": { + "name": "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 + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mutable": { + "name": "mutable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "preferences_user_id_idx": { + "name": "preferences_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "preferences_user_key_idx": { + "name": "preferences_user_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "preferences_user_id_users_id_fk": { + "name": "preferences_user_id_users_id_fk", + "tableFrom": "preferences", + "tableTo": "users", + "columnsFrom": [ + "user_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()" + }, + "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'" + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "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": {}, + "foreignKeys": { + "projects_owner_id_users_id_fk": { + "name": "projects_owner_id_users_id_fk", + "tableFrom": "projects", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "projects_team_id_teams_id_fk": { + "name": "projects_team_id_teams_id_fk", + "tableFrom": "projects", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_credentials": { + "name": "provider_credentials", + "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 + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_type": { + "name": "credential_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_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": { + "provider_credentials_user_provider_idx": { + "name": "provider_credentials_user_provider_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_credentials_user_id_idx": { + "name": "provider_credentials_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_credentials_user_id_users_id_fk": { + "name": "provider_credentials_user_id_users_id_fk", + "tableFrom": "provider_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routing_rules": { + "name": "routing_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routing_rules_scope_priority_idx": { + "name": "routing_rules_scope_priority_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routing_rules_user_id_idx": { + "name": "routing_rules_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routing_rules_enabled_idx": { + "name": "routing_rules_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routing_rules_user_id_users_id_fk": { + "name": "routing_rules_user_id_users_id_fk", + "tableFrom": "routing_rules", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "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 + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_expires_at_idx": { + "name": "sessions_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skills": { + "name": "skills", + "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 + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'custom'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "installed_by": { + "name": "installed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skills_enabled_idx": { + "name": "skills_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skills_installed_by_users_id_fk": { + "name": "skills_installed_by_users_id_fk", + "tableFrom": "skills", + "tableTo": "users", + "columnsFrom": [ + "installed_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "skills_name_unique": { + "name": "skills_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summarization_jobs": { + "name": "summarization_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "logs_processed": { + "name": "logs_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "insights_created": { + "name": "insights_created", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summarization_jobs_status_idx": { + "name": "summarization_jobs_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.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "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": "'not-started'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee": { + "name": "assignee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "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": { + "tasks_project_id_idx": { + "name": "tasks_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_mission_id_idx": { + "name": "tasks_mission_id_idx", + "columns": [ + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_project_id_projects_id_fk": { + "name": "tasks_project_id_projects_id_fk", + "tableFrom": "tasks", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_mission_id_missions_id_fk": { + "name": "tasks_mission_id_missions_id_fk", + "tableFrom": "tasks", + "tableTo": "missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_members_team_user_idx": { + "name": "team_members_team_user_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_invited_by_users_id_fk": { + "name": "team_members_invited_by_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "invited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manager_id": { + "name": "manager_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "teams_manager_id_users_id_fk": { + "name": "teams_manager_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "manager_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_slug_unique": { + "name": "teams_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "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": "'open'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "source": { + "name": "source", + "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": { + "tickets_status_idx": { + "name": "tickets_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.users": { + "name": "users", + "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 + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verifications": { + "name": "verifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspaces": { + "name": "workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_project_id": { + "name": "platform_project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "workspaces_platform_project_id_platform_projects_id_fk": { + "name": "workspaces_platform_project_id_platform_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "platform_projects", + "columnsFrom": [ + "platform_project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspaces_platform_project_slug_uniq": { + "name": "workspaces_platform_project_slug_uniq", + "nullsNotDistinct": false, + "columns": [ + "platform_project_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.backlog_status": { + "name": "backlog_status", + "schema": "public", + "values": [ + "ready", + "claimed", + "blocked", + "done" + ] + }, + "public.grant_status": { + "name": "grant_status", + "schema": "public", + "values": [ + "pending", + "active", + "revoked", + "expired" + ] + }, + "public.interaction_handoff_status": { + "name": "interaction_handoff_status", + "schema": "public", + "values": [ + "pending", + "accepted" + ] + }, + "public.interaction_inbox_status": { + "name": "interaction_inbox_status", + "schema": "public", + "values": [ + "pending", + "processing", + "processed" + ] + }, + "public.interaction_outbox_status": { + "name": "interaction_outbox_status", + "schema": "public", + "values": [ + "pending", + "processing", + "delivered" + ] + }, + "public.peer_state": { + "name": "peer_state", + "schema": "public", + "values": [ + "pending", + "active", + "suspended", + "revoked" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 711a6337..b47130a1 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -127,6 +127,13 @@ "when": 1787609223282, "tag": "0017_accounts_issuer", "breakpoints": true + }, + { + "idx": 18, + "version": "7", + "when": 1787862158838, + "tag": "0018_clean_cobalt_man", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/packages/db/src/hierarchy-schema.witness.test.ts b/packages/db/src/hierarchy-schema.witness.test.ts new file mode 100644 index 00000000..ff226e84 --- /dev/null +++ b/packages/db/src/hierarchy-schema.witness.test.ts @@ -0,0 +1,501 @@ +/** + * Hierarchy schema witnesses — contract 1 (docs/requirements/hierarchy-schema.md) §6. + * + * Witnesses §6.1 (chain construction, slug scoping, grant CHECKs, grant + * uniqueness, NOT NULLs), §6.2 (column allowlist), the database-level parts of + * §6.6 (RESTRICT/cascade deletion behavior), and §6.7's catalog half (no + * foreign keys from outside the class into class tables). + * + * Two legs run the same witness body: + * - PGlite (WASM Postgres): always runs, so the witnesses execute locally + * with no database configured. + * - Real PostgreSQL (§6.8): runs when DATABASE_URL is set — in CI that is + * the ci-postgres service, migrated by the pipeline before `pnpm test`. + * This leg is the contract's binding witness; the PGlite leg is the local + * development signal. + */ +import { randomUUID } from 'node:crypto'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { sql } from 'drizzle-orm'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { createDb } from './client.js'; +import { createPgliteDb } from './client-pglite.js'; +import { runPgliteMigrations } from './migrate.js'; +import { + companies, + estates, + hierarchyGrants, + platformProjects, + workspaces, + teams, + users, +} from './schema.js'; + +type AnyDb = { + db: { + insert: (t: unknown) => { values: (v: unknown) => Promise }; + delete: (t: unknown) => { where?: unknown } & PromiseLike; + execute: (q: unknown) => Promise<{ rows?: unknown[] } | unknown[]>; + }; + close: () => Promise; +}; + +/** Column allowlist — the exact declared sets of §2/§3. Nothing else. */ +const COLUMN_ALLOWLIST: Record = { + companies: ['id', 'name', 'slug', 'created_at', 'updated_at'], + estates: ['id', 'name', 'slug', 'company_id'], + platform_projects: ['id', 'name', 'slug', 'estate_id'], + workspaces: ['id', 'name', 'slug', 'platform_project_id'], + hierarchy_grants: [ + 'id', + 'user_id', + 'team_id', + 'company_id', + 'estate_id', + 'platform_project_id', + 'role', + 'granted_by', + 'created_at', + ], +}; + +const NODE_TABLES = ['companies', 'estates', 'platform_projects', 'workspaces']; +const CLASS_TABLES = [...NODE_TABLES, 'hierarchy_grants']; + +/** + * Drizzle wraps constraint failures ("Failed query: ...") with the driver + * error attached as `cause`. Match the pattern anywhere along the cause chain. + */ +async function expectViolation(p: Promise, re: RegExp, label = ''): Promise { + let err: unknown; + try { + await p; + } catch (e) { + err = e; + } + expect(err, label || 'expected the statement to be refused').toBeDefined(); + const messages: string[] = []; + let cur: unknown = err; + while (cur instanceof Error) { + messages.push(cur.message); + cur = (cur as { cause?: unknown }).cause; + } + expect(messages.join(' | '), label).toMatch(re); +} + +function rows(res: { rows?: unknown[] } | unknown[]): Record[] { + return (Array.isArray(res) ? res : (res.rows ?? [])) as Record[]; +} + +/** Unique per-run prefix so real-PG runs never collide and clean up safely. */ +const T = `hier-w-${randomUUID().slice(0, 8)}`; + +function witnessSuite(getHandle: () => AnyDb): void { + const db = () => getHandle().db as unknown as ReturnType['db']; + + const userA = `${T}-user-a`; + const userB = `${T}-user-b`; + let teamId: string; + let companyId: string; + let company2Id: string; + let estateId: string; + let estate2Id: string; + let ppId: string; + let workspaceId: string; + + beforeAll(async () => { + await db() + .insert(users) + .values([ + { id: userA, name: 'Witness A', email: `${userA}@example.com` }, + { id: userB, name: 'Witness B', email: `${userB}@example.com` }, + ]); + teamId = randomUUID(); + await db() + .insert(teams) + .values({ + id: teamId, + name: `${T}-team`, + slug: `${T}-team`, + ownerId: userA, + managerId: userA, + }); + }); + + afterAll(async () => { + // Bottom-up, fail-closed order; grants cascade with their targets. + const d = db(); + await d.execute(sql`DELETE FROM hierarchy_grants WHERE granted_by LIKE ${T + '%'}`); + await d.execute(sql`DELETE FROM workspaces WHERE slug LIKE ${T + '%'}`); + await d.execute(sql`DELETE FROM platform_projects WHERE slug LIKE ${T + '%'}`); + await d.execute(sql`DELETE FROM estates WHERE slug LIKE ${T + '%'}`); + await d.execute(sql`DELETE FROM companies WHERE slug LIKE ${T + '%'}`); + await d.execute(sql`DELETE FROM teams WHERE slug LIKE ${T + '%'}`); + await d.execute(sql`DELETE FROM users WHERE id LIKE ${T + '%'}`); + }); + + // ── §6.1 chain construction ──────────────────────────────────────────────── + + it('accepts a full valid chain: company → estate → platform-project → workspace', async () => { + companyId = randomUUID(); + estateId = randomUUID(); + ppId = randomUUID(); + workspaceId = randomUUID(); + await db() + .insert(companies) + .values({ id: companyId, name: 'Acme', slug: `${T}-acme` }); + await db() + .insert(estates) + .values({ id: estateId, name: 'Estate 1', slug: `${T}-e1`, companyId }); + await db() + .insert(platformProjects) + .values({ id: ppId, name: 'PP 1', slug: `${T}-pp1`, estateId }); + await db() + .insert(workspaces) + .values({ id: workspaceId, name: 'WS 1', slug: `${T}-ws1`, platformProjectId: ppId }); + }); + + it('accepts two siblings under one parent (the §2.5 control)', async () => { + estate2Id = randomUUID(); + await db() + .insert(estates) + .values({ id: estate2Id, name: 'Estate 2', slug: `${T}-e2`, companyId }); + }); + + it('refuses inserts with a null parent FK', async () => { + await expectViolation( + db().execute( + sql`INSERT INTO estates (id, name, slug, company_id) VALUES (${randomUUID()}, 'x', ${T + '-null-e'}, NULL)`, + ), + /null value|not-null/i, + ); + await expectViolation( + db().execute( + sql`INSERT INTO platform_projects (id, name, slug, estate_id) VALUES (${randomUUID()}, 'x', ${T + '-null-p'}, NULL)`, + ), + /null value|not-null/i, + ); + await expectViolation( + db().execute( + sql`INSERT INTO workspaces (id, name, slug, platform_project_id) VALUES (${randomUUID()}, 'x', ${T + '-null-w'}, NULL)`, + ), + /null value|not-null/i, + ); + }); + + it('refuses inserts with a dangling parent FK', async () => { + await expectViolation( + db() + .insert(estates) + .values({ id: randomUUID(), name: 'x', slug: `${T}-dangle`, companyId: randomUUID() }), + /foreign key/i, + ); + }); + + it('catalog: each child table has exactly one parent-FK column and no parentage edge table exists', async () => { + const res = rows( + await db().execute(sql` + SELECT tc.table_name, kcu.column_name, ccu.table_name AS ref_table + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema + JOIN information_schema.constraint_column_usage ccu + ON tc.constraint_name = ccu.constraint_name AND tc.table_schema = ccu.table_schema + WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = 'public' + `), + ); + const nodeSet = new Set(NODE_TABLES); + // Exactly one parent FK per child node table. + for (const [child, parent] of [ + ['estates', 'companies'], + ['platform_projects', 'estates'], + ['workspaces', 'platform_projects'], + ] as const) { + const parentFks = res.filter( + (r) => r['table_name'] === child && nodeSet.has(String(r['ref_table'])), + ); + expect(parentFks.map((r) => `${r['column_name']}->${r['ref_table']}`)).toEqual([ + `${{ estates: 'company_id', platform_projects: 'estate_id', workspaces: 'platform_project_id' }[child]}->${parent}`, + ]); + } + // No table outside the class references a node table (also §6.7's catalog + // half for companies/estates/platform_projects/workspaces), and the only + // multi-FK referencer is hierarchy_grants (grant attachment, not + // parentage). + const referencers = new Map(); + for (const r of res) { + if (nodeSet.has(String(r['ref_table']))) { + const t = String(r['table_name']); + referencers.set(t, (referencers.get(t) ?? 0) + 1); + } + } + for (const [table, count] of referencers) { + expect(CLASS_TABLES, `unexpected referencer of a node table: ${table}`).toContain(table); + if (count > 1) expect(table).toBe('hierarchy_grants'); + } + // No FK anywhere references hierarchy_grants. + expect(res.filter((r) => r['ref_table'] === 'hierarchy_grants')).toEqual([]); + }); + + // ── §6.1 slug scoping ────────────────────────────────────────────────────── + + it('refuses a duplicate slug under the same parent, accepts it under another parent', async () => { + company2Id = randomUUID(); + await db() + .insert(companies) + .values({ id: company2Id, name: 'Beta', slug: `${T}-beta` }); + await expectViolation( + db() + .insert(estates) + .values({ id: randomUUID(), name: 'dup', slug: `${T}-e1`, companyId }), + /duplicate key|unique/i, + ); + // Same slug, different company — accepted. + await db() + .insert(estates) + .values({ id: randomUUID(), name: 'ok', slug: `${T}-e1`, companyId: company2Id }); + // companies.slug is unique per deployment. + await expectViolation( + db() + .insert(companies) + .values({ id: randomUUID(), name: 'dup', slug: `${T}-acme` }), + /duplicate key|unique/i, + ); + }); + + it('scopes platform_projects and workspaces slugs per parent (refuse same-parent duplicate, accept cross-parent)', async () => { + // Dedicated parent estate so this test leaves estate2 a leaf (the §3.4 + // cascade witness depends on that). + const estate3Id = randomUUID(); + await db() + .insert(estates) + .values({ id: estate3Id, name: 'Estate 3', slug: `${T}-e3`, companyId }); + // platform_projects: (estate_id, slug) unique. + await expectViolation( + db() + .insert(platformProjects) + .values({ id: randomUUID(), name: 'dup', slug: `${T}-pp1`, estateId }), + /duplicate key|unique/i, + ); + const pp2Id = randomUUID(); + await db() + .insert(platformProjects) + .values({ id: pp2Id, name: 'ok', slug: `${T}-pp1`, estateId: estate3Id }); + // workspaces: (platform_project_id, slug) unique. + await expectViolation( + db() + .insert(workspaces) + .values({ id: randomUUID(), name: 'dup', slug: `${T}-ws1`, platformProjectId: ppId }), + /duplicate key|unique/i, + ); + await db() + .insert(workspaces) + .values({ id: randomUUID(), name: 'ok', slug: `${T}-ws1`, platformProjectId: pp2Id }); + }); + + // ── §6.2 column allowlist ────────────────────────────────────────────────── + + it('column allowlist: each class table has exactly its declared columns (no payload, no owner_id)', async () => { + for (const [table, allow] of Object.entries(COLUMN_ALLOWLIST)) { + const res = rows( + await db().execute( + sql`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ${table}`, + ), + ); + const actual = res.map((r) => String(r['column_name'])).sort(); + expect(actual, `column set of ${table}`).toEqual([...allow].sort()); + } + }); + + // ── §6.1 grant CHECKs ────────────────────────────────────────────────────── + + it('accepts one valid grant per subject×target form', async () => { + // All six forms; also the base rows for the §6.1 uniqueness witness below. + const forms = [ + { userId: userA, companyId }, + { userId: userA, estateId }, + { userId: userA, platformProjectId: ppId }, + { teamId, companyId }, + { teamId, estateId }, + { teamId, platformProjectId: ppId }, + ]; + for (const form of forms) { + await db() + .insert(hierarchyGrants) + .values({ ...form, role: 'owner', grantedBy: userA }); + } + }); + + it('refuses a grant with zero or two subjects (exactly-one-of CHECK)', async () => { + await expectViolation( + db().insert(hierarchyGrants).values({ companyId, role: 'viewer', grantedBy: userA }), + /check constraint/i, + ); + await expectViolation( + db() + .insert(hierarchyGrants) + .values({ userId: userA, teamId, companyId, role: 'viewer', grantedBy: userA }), + /check constraint/i, + ); + }); + + it('refuses a grant with zero or two targets (exactly-one-of CHECK)', async () => { + await expectViolation( + db().insert(hierarchyGrants).values({ userId: userA, role: 'viewer', grantedBy: userA }), + /check constraint/i, + ); + await expectViolation( + db() + .insert(hierarchyGrants) + .values({ userId: userA, companyId, estateId, role: 'viewer', grantedBy: userA }), + /check constraint/i, + ); + }); + + // ── §6.1 grant uniqueness (NULLS NOT DISTINCT) ───────────────────────────── + + it('refuses a duplicate (subject, target, role) for each of the six forms', async () => { + const forms = [ + { userId: userA, companyId }, + { userId: userA, estateId }, + { userId: userA, platformProjectId: ppId }, + { teamId, companyId }, + { teamId, estateId }, + { teamId, platformProjectId: ppId }, + ]; + for (const form of forms) { + await expectViolation( + db() + .insert(hierarchyGrants) + .values({ ...form, role: 'owner', grantedBy: userB }), + /duplicate key|unique/i, + `duplicate form ${JSON.stringify(form)} must be refused`, + ); + } + // Control: same subject and target with a different role is a new grant. + await db() + .insert(hierarchyGrants) + .values({ userId: userA, companyId, role: `${T}-other-role`, grantedBy: userA }); + }); + + // ── §6.1 NOT NULLs ───────────────────────────────────────────────────────── + + it('refuses null role, granted_by, and null name/slug columns', async () => { + await expectViolation( + db().execute( + sql`INSERT INTO hierarchy_grants (user_id, company_id, role, granted_by) VALUES (${userA}, ${companyId}, NULL, ${userA})`, + ), + /null value|not-null/i, + ); + await expectViolation( + db().execute( + sql`INSERT INTO hierarchy_grants (user_id, company_id, role, granted_by) VALUES (${userA}, ${companyId}, 'x', NULL)`, + ), + /null value|not-null/i, + ); + await expectViolation( + db().execute(sql`INSERT INTO companies (name, slug) VALUES (NULL, ${T + '-nn'})`), + /null value|not-null/i, + ); + await expectViolation( + db().execute(sql`INSERT INTO companies (name, slug) VALUES ('x', NULL)`), + /null value|not-null/i, + ); + await expectViolation( + db().execute( + sql`INSERT INTO estates (name, slug, company_id) VALUES ('x', NULL, ${companyId})`, + ), + /null value|not-null/i, + ); + }); + + // ── §6.6 deletion (database-level witnesses) ─────────────────────────────── + + it('refuses deleting a node with children (fail-closed bottom-up)', async () => { + await expectViolation( + db().execute(sql`DELETE FROM companies WHERE id = ${companyId}`), + /foreign key/i, + ); + await expectViolation( + db().execute(sql`DELETE FROM estates WHERE id = ${estateId}`), + /foreign key/i, + ); + await expectViolation( + db().execute(sql`DELETE FROM platform_projects WHERE id = ${ppId}`), + /foreign key/i, + ); + }); + + it('cascades a deleted leaf node’s grants and nothing else', async () => { + // estate2 is a leaf (no platform-projects). Attach one grant to it. + await db() + .insert(hierarchyGrants) + .values({ userId: userB, estateId: estate2Id, role: 'viewer', grantedBy: userA }); + const grantCount = async () => + Number( + rows( + await db().execute( + sql`SELECT count(*)::int AS n FROM hierarchy_grants WHERE granted_by LIKE ${T + '%'}`, + ), + )[0]!['n'], + ); + const before = await grantCount(); + await db().execute(sql`DELETE FROM estates WHERE id = ${estate2Id}`); + // Exactly the one grant on the deleted estate is gone. + expect(await grantCount()).toBe(before - 1); + }); + + it('refuses deleting a user or team that is a grant subject or granted_by referent (RESTRICT)', async () => { + await expectViolation(db().execute(sql`DELETE FROM users WHERE id = ${userA}`), /foreign key/i); + // userB is only a subject (its estate2 grant cascaded away above, but it + // still holds no grants — re-create one to witness subject RESTRICT). + await db() + .insert(hierarchyGrants) + .values({ userId: userB, companyId: company2Id, role: 'viewer', grantedBy: userA }); + await expectViolation(db().execute(sql`DELETE FROM users WHERE id = ${userB}`), /foreign key/i); + await expectViolation( + db().execute(sql`DELETE FROM teams WHERE id = ${teamId}`), + /foreign key/i, + ); + }); +} + +// ── Leg 1: PGlite (always runs — local witness signal) ─────────────────────── + +describe('hierarchy schema witnesses — PGlite', () => { + let dir: string; + let handle: ReturnType; + + beforeAll(async () => { + dir = mkdtempSync(join(tmpdir(), 'hier-witness-')); + handle = createPgliteDb(dir); + await runPgliteMigrations(handle); + }); + + afterAll(async () => { + await handle.close(); + rmSync(dir, { recursive: true, force: true }); + }); + + witnessSuite(() => handle as unknown as AnyDb); +}); + +// ── Leg 2: real PostgreSQL (§6.8 — binding witness, ci-postgres in CI) ─────── + +const hasPostgres = Boolean(process.env['DATABASE_URL']); + +describe.skipIf(!hasPostgres)('hierarchy schema witnesses — real PostgreSQL', () => { + let handle: ReturnType; + + beforeAll(() => { + handle = createDb(process.env['DATABASE_URL']!); + }); + + afterAll(async () => { + await handle.close(); + }); + + witnessSuite(() => handle as unknown as AnyDb); +}); diff --git a/packages/db/src/hierarchy-writer-coverage.test.ts b/packages/db/src/hierarchy-writer-coverage.test.ts new file mode 100644 index 00000000..dc3bf0b2 --- /dev/null +++ b/packages/db/src/hierarchy-writer-coverage.test.ts @@ -0,0 +1,2307 @@ +/** + * Hierarchy writer-coverage assertion — contract 1 + * (docs/requirements/hierarchy-schema.md) §6.3(b). + * + * A static CI assertion over all production sources (apps/, packages/, + * plugins/) with three prongs, each bound to a closed, explicitly enumerated + * allowlist: + * + * (i) Symbol prong — write references (insert/update/delete) to the + * class-table schema symbols occur only in allowlisted modules. + * Schema symbols are tracked through named imports (aliased or not), + * namespace imports, and re-export conduits. Conduits carry an + * EXPORT MAP (per-module named exports and namespace exports), + * computed to a fixpoint, so a rename at the export site + * (`export { companies as c } from`), an export of a locally bound + * alias (`import { companies }; export { companies as co }`), and a + * binding derived from a namespace (`export const co = ns.companies`) + * all propagate symbol identity to the consumer. Declaration exports + * tolerate a type annotation and prior declarators + * (`export const co: typeof companies = companies`, + * `export const dummy = 0, co = companies`). The DEFAULT-export + * slot is an export name like any other (`export { companies as + * default } from`, `export default companies`), so a bare default + * import of a conduit resolves against the map too; namespace-member + * write targets match the source's EXPORTED names — renames included + * (`M.co` after `export { companies as co } from`) — not just the + * original class symbols. Destructuring a NON-class property from a + * schema namespace over-approximates: the binding is treated as a + * schema namespace itself (fail-closed), and single-alias derivation + * from a namespace (`const t = ns.companies`) is folded in to a + * fixed depth. + * (ii) Literal prong — a class-table name inside a string or template + * span that also carries SQL context fails outside the allowlist. + * Spans are produced by a real lexer, so comments cannot hide code + * and strings cannot hide comments. Schema definitions and generated + * migrations are excluded from this prong only (per contract). + * (iii) Raw-execution prong — content-independent. A file is RAW-CAPABLE + * when it imports a database driver or the createDb/createPgliteDb + * factories — directly, via namespace, by destructuring a factory + * symbol out of a schema namespace, or from a capability CONDUIT. + * Conduits carry their own exported-name maps computed to a + * fixpoint, so a factory renamed at any hop (`export { createDb as + * mk } from`, `export default createDb`) still marks every module + * down the chain, and a tracked dynamic import of a conduit is an + * import edge like a static one. In a raw-capable file + * outside the allowlist and register, EVERY `.execute(`, `.query(`, + * and `.unsafe(` call fails, on any receiver, with any argument — + * there is no tagged-template exemption (§6.3(b): "regardless of what + * the SQL string contains or how it is constructed"). In files + * without detected capability (the DI residual), a conventional + * receiver backstop still fires on db/client-shaped receivers. + * `sql.raw` is tracked through import aliasing and namespaces. + * + * Runtime code-construction primitives (eval, new Function — spelled + * directly or as a literal computed member like `globalThis['eval']`) fail + * anywhere — allowlist and register included: constructed code defeats every + * static prong, so there is no enumerated disposition path for it. + * Unanalyzable import routes fail everywhere except their own enumerated + * disposition registers below: a dynamic import whose specifier is not a + * single string literal, and createRequire (tracked through import aliasing + * — `createRequire as x` — and destructuring from the module namespace, + * whether that namespace came from a static `import * as` or a tracked + * dynamic import; a + * route that hides the NAME entirely, e.g. re-exporting createRequire from a + * helper, falls under the value-flow residual). A LITERAL dynamic import is + * an import edge like any static import (contract rev 9): its bindings + * (destructured or namespace) feed prong (i) exactly as static import + * bindings do — and to keep that claim sound, a literal dynamic import of a + * schema/factory/driver source OUTSIDE the tracked + * `const X = await import('…')` binding shape (`.then` chains, deferred + * awaits, Promise.all) is itself a violation: bindings the analyzer cannot + * track are not allowed to exist for capability-bearing modules. + * + * KNOWN RESIDUALS (deliberate, reviewed trade-offs — not claims of closure): + * - In a file with NO detected raw capability (no driver/factory import on + * any tracked route), prong (iii) falls back to a conventional-receiver + * backstop (db/client/pool/tx/…). A dependency-injected handle bound to + * an unconventional name in such a file is an OPEN evasion path. The + * counterfactual was measured: any-receiver flagging in capability-free + * files false-positives on real non-SQL .execute()/.query() sites + * (runtime-provider-registry, connector-lease, the log CLI), so the + * residual is accepted and reviews of DI provider modules carry it. + * - Computed member access with a NON-literal name (obj[verb]()) is not + * statically resolvable; literal computed access (obj['insert']()) is + * flagged, and the statically-resolvable DISGUISES of a literal key fail + * closed — access or call, in any position: a text-only template-literal + * key (obj[`insert`](), ns[`companies`] — template text never reaches + * the lexer's code output), a quoted OR template key carrying expression + * dressing (`ns['companies' as const]`, `ns['companies'!]`, + * `ns[`companies` as const]` — the dressed rule's key class includes the + * backtick, so the lexed empty backtick pair matches), and a quoted key + * built from string escapes (`\u`/`\x`/octal). (A template key WITH + * interpolation is a non-literal computed member, above.) Invoking a + * write/exec verb via `.apply`/`.call`/`.bind` fails closed the same + * way, and ANY appearance of the `Reflect` identifier fails closed + * outright — verb detection through Reflect is not boundable (argument + * windows stop at newlines and nested parens, the method can be + * bracket-spelled, the object aliased), and the tree has zero + * occurrences outside test files. These + * code-shape rules match ordinary syntax over ordinary method names, so + * they carry their own enumerated disposition (CODE_SHAPE_REGISTER, + * empty today): a reviewed legitimate hit is registered, never resolved + * by weakening the shape. Constructing the member at runtime is adjacent + * to eval and is expected to be caught in review. + * - The DB_FACTORY_IMPORTERS enumeration counts the import edges + * hasSymbolImportEdge can see (named import, literal dynamic package + * import with symbol use, namespace member use). Destructuring a factory + * out of a schema namespace (`const { createDb } = dbns`) confers + * capability for prong (iii) but is NOT visible to the enumeration, so a + * file on that route joins the importer set only via review; prong (iii) + * still flags any execution in it. The enumeration is a measured set, + * not a soundness claim. + * - General VALUE FLOW is not modeled. The analyzer tracks import edges, + * re-export chains, and single-step alias/destructure derivations from a + * namespace binding (to depth 2) — not arbitrary assignment chains, + * function returns, or method extraction. Demonstrated escapes in this + * class: `const u = this.client.unsafe; u.call(this.client, s)` (the + * verb never appears as a member call), an alias chain three or more + * steps deep, a helper function that returns a schema symbol, and an + * export whose expression COMPUTES the value (a ternary, a call result) + * rather than naming a binding or member. + * Closing it requires data-flow analysis (a type-checker-backed rewrite, + * tracked for M4-1b consideration); the counterfactual — flagging every + * bare identifier call — false-positives on essentially all callback + * code. Reviews of modules touching db handles carry this residual. + * - The scan perimeter is //src for the three roots; production + * TS outside a src/ directory (e.g. packages/mosaic/framework/**) is not + * scanned (verified free of db/driver/execute references at review time). + * Files excluded from the scan — test files and out-of-src modules — are + * also invisible as import-graph CONDUITS: test files are emitted to + * dist, so a production module could launder a symbol or capability + * through a re-export in one. Importing a test module from production + * code is anomalous and review-visible; the blind spot is accepted as a + * residual, not closed. + * + * The writer allowlist names hierarchy command/repository modules ONLY. It is + * empty today: the hierarchy command family (M4-1b) has not landed, so no + * production module may write the class tables. The infrastructure register + * holds legitimate non-hierarchy raw execution; registered modules are exempt + * from prong (iii) only — prongs (i) and (ii) apply to them with no + * exemption, and no registered module may appear on the writer allowlist. + * + * Register modules whose exports let a CALLER reach SQL execution carry their + * own closed importer enumerations (the laundering path §6.3(b) closes): + * the migration runner, migrate-tier, and the createDb/createPgliteDb + * factories. The remaining registered modules execute only fixed statements + * or collection-CRUD over the storage `(id, data)` shape — which cannot + * address class-table columns — and export no caller-supplied-SQL surface; + * that composition property is review-enforced (§5.1) on any change to them. + * + * A false positive is resolved in the same PR by adding the module to the one + * enumerated list its role permits — never by weakening the assertion. + * + * Test files (*.spec.*, *.test.*, __tests__/) are not scanned: they are not + * production mutation paths, and the contract's own §6 witnesses must write + * class tables directly to witness database constraints. The known evasion + * forms from the M4-1a review are kept below as permanent controls: the + * analyzer must flag every one of them, so a regression that reopens an + * escape fails this suite. + */ +import { readdirSync, readFileSync, statSync, existsSync } from 'node:fs'; +import { dirname, join, relative, resolve, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const REPO_ROOT = resolve(fileURLToPath(new URL('.', import.meta.url)), '..', '..', '..'); + +/** Drizzle schema symbols of the five class tables (packages/db/src/schema.ts). */ +const CLASS_SYMBOLS = ['companies', 'estates', 'platformProjects', 'workspaces', 'hierarchyGrants']; + +/** SQL table names of the five class tables. */ +const CLASS_TABLES = [ + 'companies', + 'estates', + 'platform_projects', + 'workspaces', + 'hierarchy_grants', +]; + +/** + * Writer allowlist (§6.3b): hierarchy command/repository modules only. + * EMPTY until the hierarchy command family lands (M4-1b). Adding a module + * here is a contract-conformance decision reviewed under §5.1 — the module + * must be part of the Gateway hierarchy command path, and it must not export + * a function that executes caller-supplied SQL. + */ +const WRITER_ALLOWLIST: string[] = []; + +/** + * Infrastructure register: closed enumeration of legitimate non-hierarchy raw + * execution. Exempt from prong (iii) ONLY; prongs (i)/(ii) still apply, and + * none of these may ever join the writer allowlist. + */ +const INFRA_REGISTER: string[] = [ + 'packages/db/src/client.ts', // connection factory (imports postgres driver) + 'packages/db/src/client-pglite.ts', // PGlite factory (imports the pglite driver) + 'packages/db/src/migrate.ts', // migration runner (hash-ledger DDL execution) + 'packages/db/src/backlog.ts', // backlog domain module: parameterized sql`` over backlog tables only + 'packages/memory/src/insights.ts', // analytics raw query over memory tables + 'packages/storage/src/tier-detection.ts', // driver import for tier probing (fixed statements) + 'packages/storage/src/adapters/pglite.ts', // storage adapter (collection CRUD over (id, data)) + 'packages/storage/src/adapters/postgres.ts', // storage adapter (extension bootstrap) + 'packages/storage/src/migrate-tier.ts', // storage tier migration + 'packages/storage/src/cli.ts', // storage CLI health probe + 'apps/gateway/src/admin/admin-health.controller.ts', // SELECT 1 health probe +]; + +/** + * Closed importer enumerations for registered modules whose exports execute + * SQL or hand out an executing handle. An import edge is a static value + * import naming the symbol (from the module path or the package barrel), or + * a literal dynamic import of the package in a file using the symbol. + * `import type` is erased and is not an edge; parameter injection (the + * gateway schema-check module) has no edge. Being enumerated confers nothing + * else: importers stay subject to every prong and gain no writer standing. + */ +const MIGRATION_RUNNER_SYMBOLS = ['runMigrations', 'runPgliteMigrations', 'getMigrationStatus']; +const MIGRATION_RUNNER_IMPORTERS: string[] = [ + 'apps/gateway/src/database/database.module.ts', + 'packages/storage/src/adapters/postgres.ts', + 'packages/mosaic/src/commands/fleet-backlog.ts', + 'packages/mosaic/src/commands/gateway/verify.ts', +]; +const MIGRATE_TIER_SYMBOLS = [ + 'runMigrateTier', + 'checkTargetPreconditions', + 'PostgresMigrationTarget', + 'DrizzleMigrationSource', +]; +const MIGRATE_TIER_IMPORTERS: string[] = [ + 'packages/storage/src/cli.ts', // storage CLI command surface + 'packages/storage/src/index.ts', // package barrel re-export (public API) +]; +/** + * Enumerated disposition for non-literal dynamic imports (§6.3b review F8): + * files here may use a computed import specifier; every other prong still + * applies to them in full. Each entry needs a justification. + */ +const DYNAMIC_IMPORT_REGISTER: string[] = [ + 'plugins/macp/src/index.ts', // loads the ACP runtime SDK from a configured sdkRoot; no db access +]; +/** + * Enumerated disposition for createRequire (an unanalyzable CJS import + * route). Every entry uses it for package.json version reads or module + * resolution only (measured); every other prong still applies in full. + */ +const CREATE_REQUIRE_REGISTER: string[] = [ + 'packages/mosaic/src/cli.ts', // package.json version read + 'packages/mosaic/src/commands/gateway/daemon.ts', // module path resolution + 'packages/mosaic/src/commands/launch.ts', // package.json version read + resolution + 'packages/mosaic/src/commands/lease-activation-probe.ts', // injectable module resolver default + 'plugins/macp/src/index.ts', // OpenCode SDK resolution +]; +/** Importers of the db-handle factories (measured set; a new importer must be reviewed in). */ +const DB_FACTORY_SYMBOLS = ['createDb', 'createPgliteDb']; +const DB_FACTORY_IMPORTERS: string[] = [ + 'apps/gateway/src/database/database.module.ts', + 'packages/brain/src/cli.ts', + 'packages/log/src/cli.ts', + 'packages/memory/src/adapters/pgvector.ts', + 'packages/memory/src/cli.ts', + 'packages/mosaic/src/commands/fleet-backlog.ts', + 'packages/storage/src/adapters/postgres.ts', + 'packages/storage/src/cli.ts', + 'packages/storage/src/migrate-tier.ts', +]; +/** + * Enumerated disposition for the fail-closed CODE-SHAPE rules (text-only + * template keys, dressed/escape-built quoted keys, verb + * `.apply`/`.call`/`.bind`, `Reflect.*` verb indirection). Those shapes use + * ordinary syntax over ordinary method names (`query`, `delete`), so a + * legitimate hit is possible — e.g. a non-SQL `.query.bind(this)` on a + * log-shaped service. Such a hit is registered here with a justification, + * reviewed under §5.1, and is never resolved by weakening the shape. Empty + * today: the production tree has zero occurrences of any of these shapes + * (calibrated by the full-tree test). Exemption covers the shape rules ONLY — + * every prong still applies in full. eval/new Function stays unconditional: + * constructed code has no disposition path. + */ +const CODE_SHAPE_REGISTER: string[] = []; + +const SCAN_ROOTS = ['apps', 'packages', 'plugins']; +const EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']); +const DRIVER_SPECIFIERS = ['postgres', 'pg', '@electric-sql/pglite']; + +function isTestPath(rel: string): boolean { + return ( + /\.(spec|test)\.[cm]?tsx?$/.test(rel) || + rel.split(sep).includes('__tests__') || + rel.endsWith('.d.ts') + ); +} + +function collectSources(): string[] { + const files: string[] = []; + for (const root of SCAN_ROOTS) { + const rootDir = join(REPO_ROOT, root); + if (!existsSync(rootDir)) continue; + for (const pkg of readdirSync(rootDir)) { + const srcDir = join(rootDir, pkg, 'src'); + if (!existsSync(srcDir) || !statSync(srcDir).isDirectory()) continue; + const walk = (dir: string): void => { + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + const st = statSync(full); + if (st.isDirectory()) { + if (entry === 'node_modules' || entry === 'dist') continue; + walk(full); + } else if (EXTENSIONS.has(full.slice(full.lastIndexOf('.')))) { + const rel = relative(REPO_ROOT, full).split(sep).join('/'); + if (!isTestPath(rel)) files.push(rel); + } + } + }; + walk(srcDir); + } + } + return files.sort(); +} + +// --------------------------------------------------------------------------- +// Lexer: single pass producing (a) comment-free source with strings intact +// (for import/export and call-site regexes) and (b) the string/template text +// spans (for the literal prong). String-aware, so `//` inside a string is not +// a comment and a quote inside a comment does not open a string. Template +// expressions re-enter code mode (nesting supported); regex literals are +// recognized with the standard prev-token heuristic so their contents cannot +// open a phantom string. +// --------------------------------------------------------------------------- +interface Lexed { + code: string; + spans: string[]; +} + +function lexSource(src: string): Lexed { + let code = ''; + const spans: string[] = []; + let i = 0; + const n = src.length; + // Template nesting: each entry is the accumulated text of one template. + const tplStack: string[] = []; + // Brace depth inside the current ${ } expression, one entry per nesting level. + const exprDepth: number[] = []; + let lastSig = ''; // last significant code char (regex-vs-division heuristic) + let lastWord = ''; // last identifier/keyword emitted to code + + const emit = (ch: string): void => { + code += ch; + if (!/\s/.test(ch)) { + lastSig = ch; + if (/[A-Za-z0-9_$]/.test(ch)) lastWord += ch; + else lastWord = ''; + } + }; + + const regexCanStart = (): boolean => { + if (lastSig === '' || '([{,;=:!&|?+-*%^<>~'.includes(lastSig)) return true; + return ['return', 'typeof', 'case', 'in', 'of', 'new', 'delete', 'void', 'do', 'else'].includes( + lastWord, + ); + }; + + while (i < n) { + const ch = src[i]!; + const next = i + 1 < n ? src[i + 1]! : ''; + + if (tplStack.length > 0 && exprDepth.length < tplStack.length) { + // Inside template literal text. + const top = tplStack.length - 1; + if (ch === '\\') { + tplStack[top] += src.slice(i, i + 2); + i += 2; + continue; + } + if (ch === '`') { + spans.push(tplStack.pop()!); + emit('`'); + i += 1; + continue; + } + if (ch === '$' && next === '{') { + exprDepth.push(0); + emit('$'); + emit('{'); + i += 2; + continue; + } + tplStack[top] += ch; + i += 1; + continue; + } + + // Code mode (possibly inside a ${ } expression). + if (ch === '/' && next === '/') { + while (i < n && src[i] !== '\n') i += 1; + continue; + } + if (ch === '/' && next === '*') { + i += 2; + while (i < n && !(src[i] === '*' && src[i + 1] === '/')) i += 1; + i += 2; + emit(' '); + continue; + } + if (ch === "'" || ch === '"') { + let span = ''; + i += 1; + while (i < n && src[i] !== ch) { + if (src[i] === '\\') { + span += src.slice(i, i + 2); + i += 2; + } else { + span += src[i]; + i += 1; + } + } + i += 1; + spans.push(span); + // Keep quoted strings in code output so import specifiers stay parseable. + emit(ch); + code += span; + emit(ch); + continue; + } + if (ch === '`') { + tplStack.push(''); + emit('`'); + i += 1; + continue; + } + if (ch === '/' && regexCanStart()) { + // Regex literal: consume without interpreting quotes/backticks inside. + i += 1; + let inClass = false; + while (i < n) { + const rc = src[i]!; + if (rc === '\\') { + i += 2; + continue; + } + if (rc === '[') inClass = true; + else if (rc === ']') inClass = false; + else if (rc === '/' && !inClass) break; + else if (rc === '\n') break; // not a regex after all; bail safely + i += 1; + } + i += 1; + while (i < n && /[a-z]/.test(src[i]!)) i += 1; // flags + emit('/'); + continue; + } + if (exprDepth.length > 0) { + const top = exprDepth.length - 1; + if (ch === '{') exprDepth[top] = exprDepth[top]! + 1; + if (ch === '}') { + if (exprDepth[top] === 0) { + exprDepth.pop(); + emit('}'); + i += 1; + continue; + } + exprDepth[top] = exprDepth[top]! - 1; + } + } + emit(ch); + i += 1; + } + while (tplStack.length > 0) spans.push(tplStack.pop()!); + return { code, spans }; +} + +// --------------------------------------------------------------------------- +// Import graph helpers +// --------------------------------------------------------------------------- +function resolveSpecifier(fromRel: string, spec: string, fileSet: Set): string | null { + if (!spec.startsWith('.')) return null; + const base = join(dirname(fromRel), spec).split(sep).join('/'); + const noJs = base.replace(/\.(js|mjs|cjs)$/, ''); + for (const cand of [ + base, + noJs, + `${noJs}.ts`, + `${noJs}.tsx`, + `${noJs}.mts`, + `${noJs}.cts`, + `${noJs}/index.ts`, + ]) { + if (fileSet.has(cand)) return cand; + } + return null; +} + +const IMPORT_RE = + /import\s*(type\s+)?(?:(\w+)\s*,\s*)?(?:\{([^}]*)\}|\*\s*as\s+(\w+)|(\w+))?\s*from\s*['"]([^'"]+)['"]/g; +const EXPORT_FROM_RE = + /export\s*(type\s+)?(?:\{([^}]*)\}|\*(?:\s*as\s+\w+)?)\s*from\s*['"]([^'"]+)['"]/g; + +// Member-access shapes, tolerant of optional chaining and non-null +// assertions (ns?.companies, ns!.companies, ns?.['companies']) — a `?.` or +// `!` between receiver and member must not break a match. +const DOT = `\\s*(?:\\?\\.|!\\s*\\.|\\.)\\s*`; +const BRACKET_OPEN = `\\s*(?:\\?\\.|!)?\\s*\\[`; +const MEMBER_SEG = `(?:${DOT}\\w+|${BRACKET_OPEN}\\s*['"\`]\\w+['"\`]\\s*\\])`; +/** Terminal member access matching one of `alt`'s names, dot or bracket form. */ +const memberTail = (alt: string): string => + `(?:${DOT}(?:${alt})\\b|${BRACKET_OPEN}\\s*['"\`](?:${alt})['"\`]\\s*\\])`; +/** + * Reduce an exported expression to its core: strip parenthesization, + * trailing type assertions (`as …`, `satisfies …`), and trailing non-null + * assertions (`(companies)`, `companies as unknown as object`, + * `companies satisfies object`, `companies!`, `(companies as any)!`) so + * `export default ` passes see the binding under the dressing. + */ +function stripExprDressing(raw: string): string { + let expr = raw.trim(); + for (let prev = ''; prev !== expr; ) { + prev = expr; + expr = expr + .replace(/^\(\s*/, '') + .replace(/\s*\)$/, '') + .replace(/\s+as\s+[^()]+$/, '') + .replace(/\s+satisfies\s+[^()]+$/, '') + .replace(/\s*!+$/, '') + .trim(); + } + return expr; +} + +/** Call-open shape tolerant of the optional-call form: `f(…)` or `f?.(…)`. */ +const CALL_OPEN = `\\s*(?:\\?\\.)?\\s*\\(`; + +/** + * Declaration-head shapes: a declarator's initializer may sit behind a type + * annotation (`export const co: typeof companies = companies`) or behind + * prior declarators (`export const dummy = 0, co = companies`). TYPE_ANN + * admits `=>` inside the type text but stops at a bare `=` (the + * initializer); DECL_LIST skips prior declarators whose initializers are + * comma-free. Both are approximations of the declarator grammar — exotic + * prior initializers (an array or call containing a comma) fall to the + * value-flow residual. Both are also SINGLE-LINE shapes: a multiline type + * annotation (prettier keeps one only past the print width) falls to the + * value-flow residual too. + */ +const TYPE_ANN = `(?:\\s*:\\s*(?:[^=;\\n]|=>)*?)?`; +const DECL_LIST = `(?:[\\w$]+${TYPE_ANN}\\s*=\\s*[^,;\\n]*,\\s*)*`; + +interface FileFacts { + rel: string; + code: string; + spans: string[]; +} + +interface Violation { + file: string; + prong: string; + detail: string; +} + +/** + * Schema exports of one module in the conduit graph: `named` are exported + * identifiers bound to a class-table symbol (under WHATEVER exported name — + * renames propagate); `ns` are exported identifiers that are themselves + * namespaces over a schema source (`export * as x from …`). + */ +interface SchemaExports { + named: Set; + ns: Set; +} +type SchemaConduits = Map; + +/** Exported class-symbol names reachable through `spec` from `rel` (null = not a schema source). */ +function schemaExportsOf( + rel: string, + spec: string, + conduits: SchemaConduits, + fileSet: Set, +): SchemaExports | null { + if (spec === '@mosaicstack/db') return { named: new Set(CLASS_SYMBOLS), ns: new Set() }; + const r = resolveSpecifier(rel, spec, fileSet); + return r !== null ? (conduits.get(r) ?? null) : null; +} + +/** + * Compute the fixpoint map of "schema sources": module → the exported names + * under which a class-table symbol (or a schema namespace) is reachable from + * it. Seeds: the schema module and the db package barrel (the bare + * '@mosaicstack/db' specifier is handled in schemaExportsOf). Renames are + * propagated: `export { companies as c } from …` exports `c`, an + * `export { x as y }` of a local class binding exports `y`, and + * `export const y = ns.companies` over a schema namespace exports `y`. + */ +function computeSchemaConduits(files: FileFacts[], fileSet: Set): SchemaConduits { + const conduits: SchemaConduits = new Map([ + ['packages/db/src/schema.ts', { named: new Set(CLASS_SYMBOLS), ns: new Set() }], + ['packages/db/src/index.ts', { named: new Set(CLASS_SYMBOLS), ns: new Set() }], + ]); + let changed = true; + while (changed) { + changed = false; + for (const f of files) { + const mine: SchemaExports = conduits.get(f.rel) ?? { + named: new Set(), + ns: new Set(), + }; + const before = mine.named.size + mine.ns.size; + for (const m of f.code.matchAll(EXPORT_FROM_RE)) { + if (m[1]) continue; // export type — erased + const src = schemaExportsOf(f.rel, m[3]!, conduits, fileSet); + if (src === null || (src.named.size === 0 && src.ns.size === 0)) continue; + const starAs = /export\s*\*\s*as\s+(\w+)/.exec(m[0]); + if (starAs) { + mine.ns.add(starAs[1]!); // export * as x from schema source + } else if (m[2] === undefined) { + for (const n of src.named) mine.named.add(n); // export * from … + for (const n of src.ns) mine.ns.add(n); + } else { + for (const part of m[2].split(',')) { + const seg = part.trim(); + if (!seg || seg.startsWith('type ')) continue; + const asMatch = /^(\w+)\s+as\s+(\w+)$/.exec(seg); + const original = asMatch ? asMatch[1]! : seg; + const exported = asMatch ? asMatch[2]! : seg; + if (src.named.has(original)) mine.named.add(exported); + if (src.ns.has(original)) mine.ns.add(exported); + } + } + } + // Exports of local bindings: `export { x as y }` where x is a local + // class alias (or namespace), and `export const y = ns.`. + const aliases = classAliases(f, conduits, fileSet); + if (aliases.named.length > 0 || aliases.namespaces.length > 0) { + for (const m of f.code.matchAll(/export\s*\{([^}]*)\}(?!\s*from)/g)) { + for (const part of m[1]!.split(',')) { + const seg = part.trim(); + if (!seg || seg.startsWith('type ')) continue; + const asMatch = /^(\w+)\s+as\s+(\w+)$/.exec(seg); + const local = asMatch ? asMatch[1]! : seg; + const exported = asMatch ? asMatch[2]! : seg; + if (aliases.named.includes(local)) mine.named.add(exported); + if (aliases.namespaces.includes(local)) mine.ns.add(exported); + } + } + for (const ns of aliases.namespaces) { + for (const m of f.code.matchAll( + new RegExp( + `export\\s+(?:const|let|var)\\s+${DECL_LIST}(\\w+)${TYPE_ANN}\\s*=\\s*\\(*\\s*${ns}${MEMBER_SEG}*${memberTail(CLASS_SYMBOLS.join('|'))}`, + 'g', + ), + )) { + mine.named.add(m[1]!); + } + } + for (const local of aliases.named) { + for (const m of f.code.matchAll( + new RegExp( + `export\\s+(?:const|let|var)\\s+${DECL_LIST}(\\w+)${TYPE_ANN}\\s*=\\s*\\(*\\s*${local}\\b`, + 'g', + ), + )) { + mine.named.add(m[1]!); + } + } + // The default slot is an export name like any other, whatever the + // expression dressing: bare local, parenthesized, type-asserted + // (`as`/`satisfies`), non-null-asserted (`companies!`), or a + // namespace member chain in dot or bracket form, semicolon or not. + for (const m of f.code.matchAll(/export\s+default\s+([^;\n]+)/g)) { + const expr = stripExprDressing(m[1]!); + if (/^\w+$/.test(expr)) { + if (aliases.named.includes(expr)) mine.named.add('default'); + if (aliases.namespaces.includes(expr)) mine.ns.add('default'); + } else { + for (const ns of aliases.namespaces) { + if ( + new RegExp(`^${ns}${MEMBER_SEG}*${memberTail(CLASS_SYMBOLS.join('|'))}$`).test(expr) + ) { + mine.named.add('default'); + break; + } + } + } + } + } + if (mine.named.size + mine.ns.size > before) { + conduits.set(f.rel, mine); + changed = true; + } + } + } + return conduits; +} + +interface ClassAliases { + named: string[]; // local identifiers bound to class-table symbols + namespaces: string[]; // namespace identifiers over a schema source + // Member names under which a class symbol is reachable on SOME imported + // schema source (class symbols plus every renamed conduit export the file + // imports) — the alternation for namespace-member write targets. + memberSyms: Set; +} + +/** + * Parse an import named-binding list ("a, b as c") against the SOURCE's + * schema exports, adding locals bound to class symbols / schema namespaces. + */ +function importBindings( + namedList: string, + src: SchemaExports, + named: Set, + namespaces: Set, +): void { + for (const part of namedList.split(',')) { + const seg = part.trim(); + if (!seg || seg.startsWith('type ')) continue; + const asMatch = /^(\w+)\s+as\s+(\w+)$/.exec(seg); + const original = asMatch ? asMatch[1]! : seg; + const local = asMatch ? asMatch[2]! : seg; + if (src.named.has(original)) named.add(local); + if (src.ns.has(original)) namespaces.add(local); + } +} + +/** + * Parse a destructuring pattern ("a, b: c, d = x") over a schema source. + * A class-symbol property binds a named alias; ANY other destructured + * property is over-approximated as a schema namespace (it may be a nested + * namespace such as `const { schema } = ns` — fail-closed). + */ +function destructureBindings( + pattern: string, + src: SchemaExports, + named: Set, + namespaces: Set, +): void { + for (const part of pattern.split(',')) { + const seg = part.trim(); + if (!seg) continue; + const m = /^(\w+)\s*(?::\s*(\w+))?\s*(?:=[\s\S]*)?$/.exec(seg); + if (!m) continue; + const local = m[2] ?? m[1]!; + if (src.named.has(m[1]!)) named.add(local); + else namespaces.add(local); + } +} + +function classAliases(f: FileFacts, conduits: SchemaConduits, fileSet: Set): ClassAliases { + const named = new Set(); + const namespaces = new Set(); + const memberSyms = new Set(CLASS_SYMBOLS); + for (const m of f.code.matchAll(IMPORT_RE)) { + const [, typeOnly, defaultWith, namedList, nsName, defaultBare, spec] = m; + if (typeOnly) continue; + const src = schemaExportsOf(f.rel, spec!, conduits, fileSet); + if (src === null || (src.named.size === 0 && src.ns.size === 0)) continue; + for (const n of src.named) memberSyms.add(n); + if (nsName) namespaces.add(nsName); + if (namedList) importBindings(namedList, src, named, namespaces); + // A default import binds whatever the source exports as `default`. + const dflt = defaultWith ?? defaultBare; + if (dflt) { + if (src.named.has('default')) named.add(dflt); + if (src.ns.has('default')) namespaces.add(dflt); + } + } + // Literal dynamic imports of a schema source are import edges like any + // other (contract rev 9): both binding shapes feed prong (i). + for (const m of f.code.matchAll( + /(?:const|let|var)\s*(?:\{([^}]*)\}|(\w+))\s*=\s*await\s+import\s*\(\s*(['"])([^'"]+)\3\s*\)/g, + )) { + const [, pattern, nsName, , spec] = m; + const src = schemaExportsOf(f.rel, spec!, conduits, fileSet); + if (src === null || (src.named.size === 0 && src.ns.size === 0)) continue; + for (const n of src.named) memberSyms.add(n); + if (nsName) namespaces.add(nsName); + if (pattern) destructureBindings(pattern, src, named, namespaces); + } + // Aliases derived FROM a schema namespace, to a bounded depth (2 passes): + // const { companies } = ns; → named alias + // const { schema } = ns; → nested namespace (fail-closed) + // const t = ns.companies; → named alias + // const s2 = ns.schema; → namespace alias + const nsSrc: SchemaExports = { named: memberSyms, ns: new Set() }; + for (let pass = 0; pass < 2; pass += 1) { + for (const ns of [...namespaces]) { + for (const m of f.code.matchAll( + new RegExp(`(?:const|let|var)\\s*\\{([^}]*)\\}\\s*=\\s*(?:await\\s+)?${ns}\\b`, 'g'), + )) { + destructureBindings(m[1]!, nsSrc, named, namespaces); + } + for (const m of f.code.matchAll( + new RegExp( + `(?:const|let|var)\\s+${DECL_LIST}(\\w+)${TYPE_ANN}\\s*=\\s*\\(*\\s*${ns}${MEMBER_SEG}*(?:${DOT}(\\w+)\\b|${BRACKET_OPEN}\\s*['"\`](\\w+)['"\`]\\s*\\])`, + 'g', + ), + )) { + if (memberSyms.has((m[2] ?? m[3])!)) named.add(m[1]!); + else namespaces.add(m[1]!); + } + } + } + return { named: [...named], namespaces: [...namespaces], memberSyms }; +} + +/** + * Capability conduits: modules that re-export raw-execution capability, so a + * consumer can obtain it without a literal driver/factory specifier. + * `driver` — re-exports (star, named, or default) from a driver package or + * another driver conduit; importing ANYTHING from one confers driver + * capability (the conduit module itself is additionally flagged by prong + * (iii)'s literal check, so these routes fail at both ends). + * `factory` — carries a NAME MAP (module → the exported names under which a + * factory is reachable), computed to a fixpoint: `export … from` propagates + * the SOURCE's exported factory names through renames (star and star-as + * copy them all), and a local binding of a factory (named import, namespace + * member extraction, or tracked dynamic-import destructure) exported under + * any name — braces, declaration, or `export default` — adds that name. + * Importing ANYTHING from a factory conduit (statically or via a tracked + * dynamic import) confers factory capability — the conduit may rename the + * symbol at any hop, so there is no consumer-side name gate. + */ +function computeCapabilityConduits( + files: FileFacts[], + fileSet: Set, +): { driver: Set; factory: Set } { + const driver = new Set(); + // Factory conduits carry a NAME MAP (module → exported names under which a + // factory is reachable) so renames propagate hop by hop, exactly like the + // schema export map — a literal-name gate at any hop would launder. + const factoryNames = new Map>(); + const isDriverSpec = (rel: string, spec: string): boolean => { + if (DRIVER_SPECIFIERS.includes(spec)) return true; + const r = resolveSpecifier(rel, spec, fileSet); + return r !== null && driver.has(r); + }; + const factoryNamesOf = (rel: string, spec: string): Set | null => { + if (spec === '@mosaicstack/db') return new Set(DB_FACTORY_SYMBOLS); + const r = resolveSpecifier(rel, spec, fileSet); + return r !== null ? (factoryNames.get(r) ?? null) : null; + }; + let changed = true; + while (changed) { + changed = false; + for (const f of files) { + for (const m of f.code.matchAll(EXPORT_FROM_RE)) { + if (m[1]) continue; // export type — erased + const spec = m[3]!; + if (!driver.has(f.rel) && isDriverSpec(f.rel, spec)) { + driver.add(f.rel); + changed = true; + } + } + // `export { default as x } from 'postgres'` matches EXPORT_FROM_RE's + // named branch above; `export x from` is not valid syntax — covered. + const mine = factoryNames.get(f.rel) ?? new Set(); + const before = mine.size; + for (const m of f.code.matchAll(EXPORT_FROM_RE)) { + if (m[1]) continue; + const src = factoryNamesOf(f.rel, m[3]!); + if (src === null || src.size === 0) continue; + if (m[2] === undefined) { + // `export *` / `export * as x` — over-approximated to the source's + // names (membership is what confers capability on consumers). + for (const n of src) mine.add(n); + } else { + for (const part of m[2].split(',')) { + const seg = part.trim(); + if (!seg || seg.startsWith('type ')) continue; + const am = /^(\w+)\s+as\s+(\w+)$/.exec(seg); + if (src.has(am ? am[1]! : seg)) mine.add(am ? am[2]! : seg); + } + } + } + // Locals bound to a factory (named import — aliased or not — namespace + // member extraction, or tracked dynamic-import destructure), then + // exported under ANY name and by ANY form, braces or default included + // (`export const mk = mod.createDb`, `export default createDb`). + const locals = new Set(); + const nss = new Map>(); + for (const im of f.code.matchAll(IMPORT_RE)) { + const [, typeOnly, , namedList, nsName, , spec] = im; + if (typeOnly) continue; + const src = factoryNamesOf(f.rel, spec!); + if (src === null || src.size === 0) continue; + if (nsName) nss.set(nsName, src); + if (namedList) { + for (const part of namedList.split(',')) { + const am = /^(\w+)(?:\s+as\s+(\w+))?$/.exec(part.trim()); + if (am && src.has(am[1]!)) locals.add(am[2] ?? am[1]!); + } + } + } + for (const dm of f.code.matchAll( + /(?:const|let|var)\s*(?:\{([^}]*)\}|(\w+))\s*=\s*await\s+import\s*\(\s*(['"])([^'"]+)\3\s*\)/g, + )) { + const [, pattern, nsName, , spec] = dm; + const src = factoryNamesOf(f.rel, spec!); + if (src === null || src.size === 0) continue; + if (nsName) nss.set(nsName, src); + if (pattern) { + for (const part of pattern.split(',')) { + const pm = /^(\w+)\s*(?::\s*(\w+))?/.exec(part.trim()); + if (pm && src.has(pm[1]!)) locals.add(pm[2] ?? pm[1]!); + } + } + } + for (const [ns, src] of nss) { + for (const am of f.code.matchAll( + new RegExp( + `(?:const|let|var)\\s+${DECL_LIST}(\\w+)${TYPE_ANN}\\s*=\\s*\\(*\\s*${ns}(?:${DOT}(\\w+)\\b|${BRACKET_OPEN}\\s*['"\`](\\w+)['"\`]\\s*\\])`, + 'g', + ), + )) { + if (src.has((am[2] ?? am[3])!)) locals.add(am[1]!); + } + } + if (locals.size > 0) { + for (const m of f.code.matchAll(/export\s*\{([^}]*)\}(?!\s*from)/g)) { + for (const part of m[1]!.split(',')) { + const seg = part.trim(); + if (!seg || seg.startsWith('type ')) continue; + const am = /^(\w+)(?:\s+as\s+(\w+))?$/.exec(seg); + if (am && locals.has(am[1]!)) mine.add(am[2] ?? am[1]!); + } + } + for (const local of locals) { + if ( + new RegExp(`export\\s+(?:const|let|var|function)\\s+${DECL_LIST}${local}\\b`).test( + f.code, + ) + ) { + mine.add(local); + } + // A derived binding exported under a NEW name re-exports the + // capability under that name: `export const mk2 = (mk);` + for (const dm of f.code.matchAll( + new RegExp( + `export\\s+(?:const|let|var)\\s+${DECL_LIST}(\\w+)${TYPE_ANN}\\s*=\\s*\\(*\\s*${local}\\b`, + 'g', + ), + )) { + mine.add(dm[1]!); + } + } + } + // The default slot, under any expression dressing — a local factory + // binding (`export default (createDb)`) or a namespace factory member + // (`export default mod.createDb`, `export default mod['createDb']`) — + // mirrors the schema side's default-export pass. + for (const m of f.code.matchAll(/export\s+default\s+([^;\n]+)/g)) { + const expr = stripExprDressing(m[1]!); + if (/^\w+$/.test(expr)) { + if (locals.has(expr)) mine.add('default'); + } else { + for (const [ns, src] of nss) { + const t = new RegExp( + `^${ns}(?:${DOT}(\\w+)\\b|${BRACKET_OPEN}\\s*['"\`](\\w+)['"\`]\\s*\\])$`, + ).exec(expr); + if (t && src.has((t[1] ?? t[2])!)) { + mine.add('default'); + break; + } + } + } + } + if (mine.size > before) { + factoryNames.set(f.rel, mine); + changed = true; + } + } + } + return { driver, factory: new Set(factoryNames.keys()) }; +} + +/** Value-import edge naming one of `symbols` (named import from anywhere, or literal dynamic package import while using the symbol). */ +function hasSymbolImportEdge(code: string, symbols: string[], packageName: string): boolean { + const pkg = packageName.replace('/', '\\/'); + const staticEdge = new RegExp( + `import\\s*(?!type\\b)(?:\\w+\\s*,\\s*)?\\{[^}]*\\b(${symbols.join('|')})\\b[^}]*\\}\\s*from\\s*['"][^'"]+['"]`, + ).test(code); + const usesSymbol = new RegExp(`\\b(${symbols.join('|')})\\b`).test(code); + const dynamicEdge = usesSymbol && new RegExp(`import\\s*\\(\\s*['"]${pkg}['"]\\s*\\)`).test(code); + // Namespace form: `import * as ns from ''` + `ns.` usage + // (dot or bracket member, optional-chain/non-null tolerant). + let nsEdge = false; + for (const m of code.matchAll( + new RegExp(`import\\s*\\*\\s*as\\s+(\\w+)\\s*from\\s*['"]${pkg}['"]`, 'g'), + )) { + if (new RegExp(`\\b${m[1]}${memberTail(symbols.join('|'))}`).test(code)) nsEdge = true; + } + return staticEdge || dynamicEdge || nsEdge; +} + +// --------------------------------------------------------------------------- +// The analyzer — pure over (rel, source) so the evasion controls below can +// feed synthetic files through the exact production logic. +// --------------------------------------------------------------------------- +interface AnalysisCtx { + fileSet: Set; + conduits: SchemaConduits; // schema-symbol sources with exported names (prong i) + driverConduits: Set; // driver-capability re-exporters (prong iii) + factoryConduits: Set; // factory-capability re-exporters (prong iii) +} + +function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] { + const { fileSet, conduits, driverConduits, factoryConduits } = ctx; + const violations: Violation[] = []; + const rel = f.rel; + const code = f.code; + const inAllowlist = WRITER_ALLOWLIST.includes(rel); + const inRegister = INFRA_REGISTER.includes(rel); + const isSchemaDefinition = rel === 'packages/db/src/schema.ts'; + + // Runtime code construction: fails anywhere. Covers direct calls + // (optional-call form included), and literal computed access + // (window['eval'], globalThis['Function']). + if ( + new RegExp( + `\\beval${CALL_OPEN}|\\bnew\\s+Function\\s*\\(|\\[\\s*['"](eval|Function)['"]\\s*\\]`, + ).test(code) + ) { + violations.push({ file: rel, prong: 'code-construction', detail: 'eval/new Function' }); + } + // Fail-closed CODE-SHAPE rules: statically-resolvable disguises of a + // literal member key or verb invocation. They match ordinary syntax, so a + // reviewed legitimate hit is dispositioned through CODE_SHAPE_REGISTER — + // the shapes themselves are never weakened. + if (!CODE_SHAPE_REGISTER.includes(rel)) { + // A computed member — access or call — whose key is a text-only template + // literal is invisible to every member and verb matcher (the lexer routes + // template text to spans, so the key survives in code as two ADJACENT + // backticks — the discriminator against backticks inside quoted-string + // prose, whose text stays in code, and against interpolated keys, whose + // `${…}` expression stays in code between the backticks). Runtime code + // construction's sibling: fail-closed in any position (write target, + // export expression, receiver, call). A template key WITH interpolation + // is a non-literal computed member (documented residual above). + if (new RegExp(`\\[\\s*\`\`\\s*\\]`).test(code)) { + violations.push({ + file: rel, + prong: 'code-construction', + detail: 'template-literal computed member', + }); + } + // A quoted OR template computed key carrying expression dressing + // (`ns['companies' as const]`, `ns['companies' satisfies 'companies']`, + // `ns['companies'!]`, and the template forms `ns[\`companies\` as + // const]`, `ns[\`companies\`!]`) keeps its static value while breaking + // every `['name']` matcher — the bracket alternatives require the + // closing quote to touch the `]`, and a dressed TEMPLATE key also breaks + // the template rule above (dressing intervenes before the `]`). + // Tolerance inside the matchers cannot span the class (the dressing's + // type text may itself contain a `]`, e.g. `as Foo['x']`), so the SHAPE + // fails closed: a quote-close followed by `!`/`as`/`satisfies` inside a + // bracket. The key class includes the backtick: a dressed template key + // survives in lexed code as the empty adjacent-backtick pair, which the + // empty-key case of this regex matches (an interpolated key keeps its + // `\${…}` between the backticks and stays clean unless dressed — a + // dressed interpolated key fires too, an over-match dispositioned like + // any other shape hit). The `!(?!=)` guard keeps ordinary comparisons + // (`o['k'] !== x` — dressing AFTER the bracket) clean. + if ( + new RegExp(`\\[\\s*(['"\`])(?:(?!\\1)[^\\n])*\\1\\s*(?:!(?!=)|as\\s|satisfies\\s)`).test(code) + ) { + violations.push({ + file: rel, + prong: 'code-construction', + detail: 'dressed computed-member key', + }); + } + // A quoted key built from string escapes (a `\` + `u`/`x`/octal-digit + // sequence whose decoded text is a plain identifier, e.g. a key spelling + // `companies` with its first letter unicode-escaped) stays invisible to + // every `\w+`-keyed matcher. The key IS statically + // resolvable, so it is not under the non-literal residual: any bracketed + // quoted key containing an identifier-capable escape fails closed. + if (new RegExp(`\\[\\s*(['"])[^'"\\n]*\\\\[ux0-7][^'"\\n]*\\1\\s*\\]`).test(code)) { + violations.push({ + file: rel, + prong: 'code-construction', + detail: 'escape-built computed-member key', + }); + } + // Invoking a write/exec verb through Function.prototype indirection + // (`db.insert.apply(db, [companies])`, `d.execute.call(d, s)`, + // `db.insert.bind(db)`) hides the argument shape from every verb matcher + // while both member names stay statically visible — unlike the + // method-EXTRACTION residual, where the verb never appears as a member. + if ( + new RegExp( + `(?:${DOT}(?:insert|update|delete|execute|query|unsafe|raw)\\b|` + + `\\[\\s*['"](?:insert|update|delete|execute|query|unsafe|raw)['"]\\s*\\])` + + `${DOT}(?:apply|call|bind)${CALL_OPEN}`, + ).test(code) + ) { + violations.push({ + file: rel, + prong: 'code-construction', + detail: 'verb apply/call/bind indirection', + }); + } + // `Reflect` reaches the same members without member syntax: + // `Reflect.apply(db.insert, db, [companies])`, `Reflect.get(db, + // 'insert')`, `Reflect.getOwnPropertyDescriptor(db, 'execute')`. Verb + // detection through Reflect is not boundable: any argument window stops + // at a newline (prettier breaks a >100-col call one-arg-per-line, moving + // the verb past it) or at the first `)` (a nested-paren first argument + // closes it early), the method can be bracket-spelled + // (`Reflect['apply']`), and the object can be aliased + // (`const R = Reflect`). So the IDENTIFIER is the shape: any `Reflect` + // token in lexed code fails closed — member access, aliasing, or + // argument passing alike. The tree has zero occurrences outside test + // files (measured); a reviewed legitimate use is register-dispositioned. + // (`\b` keeps compound identifiers like `ReflectHelper` clean.) + if (new RegExp(`\\bReflect\\b`).test(code)) { + violations.push({ + file: rel, + prong: 'code-construction', + detail: 'Reflect indirection', + }); + } + } + // Dynamic import whose specifier is not a single string literal: the + // import graph becomes unanalyzable. Checked per call site, so a literal + // first fragment ('x' + y) does not slip past. A LITERAL import of a + // schema/factory source must additionally sit in the tracked binding shape + // (const X = await import(…)) — any other consumption of its promise + // (.then, deferred await, array wrapping) hides the binding from prong (i), + // so it fails closed here. + if (!DYNAMIC_IMPORT_REGISTER.includes(rel)) { + for (const m of code.matchAll(/\bimport\s*\(/g)) { + const idx = m.index ?? 0; + const tail = code.slice(idx + m[0].length); + const lit = /^\s*(['"])((?:[^'"\\]|\\.)*?)\1\s*[,)]/.exec(tail); + if (!lit) { + violations.push({ file: rel, prong: 'dynamic-import', detail: 'non-literal import()' }); + continue; + } + const spec = lit[2]!; + const schemaSrc = schemaExportsOf(rel, spec, conduits, fileSet); + const factoryLike = + spec === '@mosaicstack/db' || + (() => { + const r = resolveSpecifier(rel, spec, fileSet); + return r !== null && (factoryConduits.has(r) || driverConduits.has(r)); + })() || + DRIVER_SPECIFIERS.includes(spec); + if (schemaSrc !== null || factoryLike) { + const before = code.slice(Math.max(0, idx - 200), idx); + if (!/(?:const|let|var)\s*(?:\{[^}]*\}|\w+)\s*=\s*await\s*$/.test(before)) { + violations.push({ + file: rel, + prong: 'dynamic-import', + detail: `literal import('${spec}') outside the tracked binding shape`, + }); + } + } + } + } + // createRequire: an unanalyzable CJS import route. Tracked by name AND + // through import aliases (import { createRequire as x } / destructured + // from a dynamic module import). + if (!CREATE_REQUIRE_REGISTER.includes(rel)) { + const crNames = new Set(); + if (new RegExp(`\\bcreateRequire${CALL_OPEN}`).test(code)) crNames.add('createRequire'); + for (const m of code.matchAll(/import\s*\{([^}]*)\}\s*from\s*['"](?:node:)?module['"]/g)) { + const alias = /\bcreateRequire\s+as\s+(\w+)/.exec(m[1]!); + if (alias) crNames.add(alias[1]!); + } + for (const m of code.matchAll( + /(?:const|let|var)\s*\{([^}]*)\}\s*=\s*await\s+import\s*\(\s*['"](?:node:)?module['"]\s*\)/g, + )) { + const alias = /\bcreateRequire\s*:\s*(\w+)/.exec(m[1]!); + if (alias) crNames.add(alias[1]!); + } + // Destructuring from a STATIC or dynamically-bound module namespace: + // import * as M from 'node:module'; const { createRequire: x } = M; + const modNs = new Set(); + for (const m of code.matchAll(/import\s*\*\s*as\s+(\w+)\s*from\s*['"](?:node:)?module['"]/g)) { + modNs.add(m[1]!); + } + for (const m of code.matchAll( + /(?:const|let|var)\s+(\w+)\s*=\s*await\s+import\s*\(\s*['"](?:node:)?module['"]\s*\)/g, + )) { + modNs.add(m[1]!); + } + for (const ns of modNs) { + for (const m of code.matchAll( + new RegExp(`(?:const|let|var)\\s*\\{([^}]*)\\}\\s*=\\s*${ns}\\b`, 'g'), + )) { + const alias = /\bcreateRequire\s*(?::\s*(\w+))?/.exec(m[1]!); + if (alias) crNames.add(alias[1] ?? 'createRequire'); + } + } + for (const n of crNames) { + if (n === 'createRequire' || new RegExp(`\\b${n}${CALL_OPEN}`).test(code)) { + violations.push({ file: rel, prong: 'dynamic-import', detail: 'createRequire()' }); + break; + } + } + } + + const aliases = classAliases(f, conduits, fileSet); + + // Prong (i): schema-symbol writes — alias-, namespace-, and conduit-aware. + if (!inAllowlist) { + // Word boundaries live inside each alternative: the bracket form ends in + // `]` (non-word), where a trailing `\b` could never match. + const targets: string[] = [...aliases.named].map((a) => `${a}\\b`); + // Namespace members match the source's EXPORTED names (renames included), + // not just the original class symbols. + const symAlt = [...aliases.memberSyms].join('|'); + for (const ns of aliases.namespaces) { + // Allow intermediate property segments (ns.schema.companies — nested + // namespace re-exports), literal computed access (ns['companies']), + // and optional-chain/non-null markers (ns?.companies, ns!.companies). + targets.push(`${ns}${MEMBER_SEG}*${memberTail(symAlt)}`); + } + if (targets.length > 0) { + // The argument prefix tolerates parenthesization, spread, and array + // wrapping: .insert((companies)), .insert(...[companies]). + const writeRe = new RegExp( + `\\.\\s*(insert|update|delete)${CALL_OPEN}\\s*(?:(?:\\(|\\[|\\.\\.\\.)\\s*)*(${targets.join('|')})`, + 'g', + ); + for (const m of code.matchAll(writeRe)) { + violations.push({ + file: rel, + prong: 'i-symbol', + detail: `.${m[1]}(${m[2]}) outside the writer allowlist`, + }); + } + } + } + + // Computed member access with a literal verb name (obj['insert'](…)) + // bypasses dot-based call detection; the tree has no legitimate use of the + // form (measured), so it fails outright. Write verbs fail outside the + // allowlist; execution verbs fail outside allowlist ∪ register. + if (!inAllowlist) { + const writeBracket = new RegExp( + `\\[\\s*['"](insert|update|delete)['"]\\s*\\]${CALL_OPEN}`, + 'g', + ); + for (const m of code.matchAll(writeBracket)) { + violations.push({ + file: rel, + prong: 'i-symbol', + detail: `['${m[1]}']() computed-member write call`, + }); + } + if (!inRegister) { + for (const m of code.matchAll( + new RegExp(`\\[\\s*['"](execute|query|unsafe)['"]\\s*\\]${CALL_OPEN}`, 'g'), + )) { + violations.push({ + file: rel, + prong: 'iii-raw-execution', + detail: `['${m[1]}']() computed-member execution call`, + }); + } + } + } + + // Prong (ii): class-table names in string/template spans with SQL context. + // The SQL keyword must be ADJACENT to the table name — co-residence + // anywhere in one span over-matches prose ("pnpm workspaces" plus an + // unrelated "from" in an embedded doc string is not SQL). Adjacency + // tolerates schema qualification (public.hierarchy_grants), bare + // qualifier words (TABLE, ONLY, IF EXISTS), interposed block comments, + // and quoting (including escaped quotes in span text). + if (!inAllowlist && !isSchemaDefinition) { + const kw = + '(?:insert\\s+into|update|delete\\s+from|from|join|truncate(?:\\s+table)?|alter\\s+table|drop\\s+table|references|into|copy|on|lock(?:\\s+table)?)'; + const gap = '(?:\\s|/\\*[\\s\\S]*?\\*/|--[^\\n]*\\n)+'; + // Bare qualifier words may sit between keyword and table name: + // GRANT … ON TABLE t, DELETE FROM ONLY t, DROP TABLE IF EXISTS t. + const qual = `(?:(?:table|only|if\\s+exists)${gap})*`; + const q = `(?:\\\\?["'\`])?`; + const sqlAdjacentRe = new RegExp( + `\\b${kw}${gap}${qual}${q}(?:${q}\\w+${q}\\s*\\.\\s*)?${q}(${CLASS_TABLES.join('|')})\\b`, + 'i', + ); + for (const span of f.spans) { + if (sqlAdjacentRe.test(span)) { + violations.push({ + file: rel, + prong: 'ii-literal', + detail: `class-table name in SQL context: ${span.slice(0, 80)}`, + }); + } + } + } + + // Prong (iii): content-independent raw execution. + if (!inAllowlist && !inRegister) { + const literalDriver = new RegExp( + `(from\\s*|import\\s*\\(\\s*)['"](${DRIVER_SPECIFIERS.map((s) => s.replace('/', '\\/')).join('|')})['"]`, + ).test(code); + // Any value import from a driver conduit confers driver capability. + let conduitDriver = false; + let factoryConduitImport = false; + for (const m of f.code.matchAll(IMPORT_RE)) { + if (m[1]) continue; // import type — erased + const r = resolveSpecifier(rel, m[6]!, fileSet); + if (r !== null && driverConduits.has(r)) conduitDriver = true; + if (r !== null && factoryConduits.has(r)) factoryConduitImport = true; + } + // A TRACKED dynamic import of a conduit is an import edge like any + // static one (contract rev 9) — for capability too, not just prong (i). + for (const m of f.code.matchAll(/=\s*await\s+import\s*\(\s*(['"])([^'"]+)\1\s*\)/g)) { + const r = resolveSpecifier(rel, m[2]!, fileSet); + if (r !== null && driverConduits.has(r)) conduitDriver = true; + if (r !== null && factoryConduits.has(r)) factoryConduitImport = true; + } + const driverImport = literalDriver || conduitDriver; + // Factory capability: a named/namespace edge to the factory symbols, OR + // ANY value import from a factory conduit — the conduit may re-export + // the factory under a different name (export { createDb as default }), + // so the consumer-side name gate is dropped for conduit imports. + const factoryImport = + factoryConduitImport || + hasSymbolImportEdge(code, DB_FACTORY_SYMBOLS, '@mosaicstack/db') || + (aliases.namespaces.length > 0 && + new RegExp( + `\\b(?:${aliases.namespaces.join('|')})${MEMBER_SEG}*${memberTail('createDb|createPgliteDb')}`, + ).test(code)) || + // Destructuring a factory symbol OUT of a schema namespace confers + // capability whatever the local rename: const { createDb: mk } = dbns. + (aliases.namespaces.length > 0 && + new RegExp( + `(?:const|let|var)\\s*\\{[^}]*\\b(?:createDb|createPgliteDb)\\b[^}]*\\}\\s*=\\s*(?:await\\s+)?(?:${aliases.namespaces.join('|')})\\b`, + ).test(code)); + if (driverImport) { + violations.push({ file: rel, prong: 'iii-raw-execution', detail: 'direct driver import' }); + // A driver client exposes query/execute/unsafe as raw primitives: + // flag them all on any receiver, any argument, spaced, optional-call + // (`?.(`) or not. + for (const m of code.matchAll(new RegExp(`\\.\\s*(execute|query|unsafe)${CALL_OPEN}`, 'g'))) { + violations.push({ + file: rel, + prong: 'iii-raw-execution', + detail: `.${m[1]}() in a driver-importing module`, + }); + } + } else if (factoryImport) { + // A drizzle handle's raw primitive is .execute (its .query namespace is + // the relational builder, and unrelated .query() methods are common), + // so factory capability flags execute/unsafe on any receiver and query + // only on the db/client-shaped receiver backstop below. + for (const m of code.matchAll(new RegExp(`\\.\\s*(execute|unsafe)${CALL_OPEN}`, 'g'))) { + violations.push({ + file: rel, + prong: 'iii-raw-execution', + detail: `.${m[1]}() in a factory-importing module`, + }); + } + } + if (!driverImport) { + // DI residual backstop: db/client-shaped receivers fire regardless of + // detected capability (a handle can arrive by injection). + // The receiver may be a dotted name OR a literal bracketed member with + // a conventional name — this['db'].query(…) — and the final member + // access tolerates ?. and ! markers (this.db?.query(…)). + for (const m of code.matchAll( + new RegExp( + `(?:\\b(?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)|\\[\\s*['"](?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)['"]\\s*\\])${DOT}(execute|query|unsafe)${CALL_OPEN}`, + 'g', + ), + )) { + violations.push({ + file: rel, + prong: 'iii-raw-execution', + detail: `.${m[1]}() on a db-shaped receiver`, + }); + } + } + // sql.raw through aliases and namespaces (drizzle-orm and the db barrel). + const sqlAliases = new Set(); + for (const m of code.matchAll(IMPORT_RE)) { + const [, typeOnly, , namedList, nsName, , spec] = m; + if (typeOnly) continue; + if (!/^drizzle-orm|^@mosaicstack\/db$/.test(spec!)) continue; + if (namedList) { + for (const part of namedList.split(',')) { + const seg = part.trim(); + const asMatch = /^sql\s+as\s+(\w+)$/.exec(seg); + if (seg === 'sql') sqlAliases.add('sql'); + else if (asMatch) sqlAliases.add(asMatch[1]!); + } + } + if (nsName) sqlAliases.add(`${nsName}\\s*\\.\\s*sql`); + } + if (sqlAliases.size > 0) { + const rawRe = new RegExp(`\\b(${[...sqlAliases].join('|')})\\s*\\.\\s*raw${CALL_OPEN}`); + if (rawRe.test(code)) { + violations.push({ file: rel, prong: 'iii-raw-execution', detail: 'sql.raw()' }); + } + } + } + return violations; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- +describe('hierarchy writer coverage (contract 1 §6.3b)', () => { + const sourceRels = collectSources(); + const fileSet = new Set(sourceRels); + const files: FileFacts[] = sourceRels.map((rel) => { + const { code, spans } = lexSource(readFileSync(join(REPO_ROOT, rel), 'utf8')); + return { rel, code, spans }; + }); + const conduits = computeSchemaConduits(files, fileSet); + const capConduits = computeCapabilityConduits(files, fileSet); + const ctx: AnalysisCtx = { + fileSet, + conduits, + driverConduits: capConduits.driver, + factoryConduits: capConduits.factory, + }; + + it('scans a non-empty production source set including plugins', () => { + expect(files.length).toBeGreaterThan(100); + expect(sourceRels.some((r) => r.startsWith('plugins/'))).toBe(true); + }); + + it('enumerated modules exist on disk (no stale allowlist/register entries)', () => { + for (const p of [ + ...WRITER_ALLOWLIST, + ...INFRA_REGISTER, + ...MIGRATION_RUNNER_IMPORTERS, + ...MIGRATE_TIER_IMPORTERS, + ...DB_FACTORY_IMPORTERS, + ...DYNAMIC_IMPORT_REGISTER, + ...CREATE_REQUIRE_REGISTER, + ]) { + expect(existsSync(join(REPO_ROOT, p)), `enumerated module missing: ${p}`).toBe(true); + } + }); + + it('no registered module appears on the writer allowlist', () => { + for (const p of INFRA_REGISTER) { + expect(WRITER_ALLOWLIST, `register/allowlist overlap: ${p}`).not.toContain(p); + } + }); + + it('three-prong writer coverage holds over the production tree', () => { + const violations = files.flatMap((f) => analyzeFile(f, ctx)); + expect( + violations, + violations.map((v) => `[prong ${v.prong}] ${v.file}: ${v.detail}`).join('\n'), + ).toEqual([]); + }); + + it('migration-runner import edges are exactly the closed importer enumeration', () => { + const offenders: string[] = []; + for (const f of files) { + if (f.rel.startsWith('packages/db/src/')) continue; // the runner's own package + if (!new RegExp(`\\b(${MIGRATION_RUNNER_SYMBOLS.join('|')})\\b`).test(f.code)) continue; + if ( + hasSymbolImportEdge(f.code, MIGRATION_RUNNER_SYMBOLS, '@mosaicstack/db') && + !MIGRATION_RUNNER_IMPORTERS.includes(f.rel) + ) { + offenders.push(f.rel); + } + } + expect(offenders, `unenumerated migration-runner importers:\n${offenders.join('\n')}`).toEqual( + [], + ); + }); + + it('migrate-tier import edges are exactly the closed importer enumeration', () => { + const offenders: string[] = []; + for (const f of files) { + if (f.rel === 'packages/storage/src/migrate-tier.ts') continue; + const pathEdge = + /from\s*['"][^'"]*migrate-tier(\.js)?['"]/.test(f.code) || + /import\s*\(\s*['"][^'"]*migrate-tier(\.js)?['"]\s*\)/.test(f.code); + const symbolEdge = + new RegExp(`\\b(${MIGRATE_TIER_SYMBOLS.join('|')})\\b`).test(f.code) && + hasSymbolImportEdge(f.code, MIGRATE_TIER_SYMBOLS, '@mosaicstack/storage'); + if ((pathEdge || symbolEdge) && !MIGRATE_TIER_IMPORTERS.includes(f.rel)) { + offenders.push(f.rel); + } + } + expect(offenders, `unenumerated migrate-tier importers:\n${offenders.join('\n')}`).toEqual([]); + }); + + it('db-factory (createDb/createPgliteDb) import edges are exactly the closed importer enumeration', () => { + const offenders: string[] = []; + for (const f of files) { + if (f.rel.startsWith('packages/db/src/')) continue; // the factories' own package + if (!new RegExp(`\\b(${DB_FACTORY_SYMBOLS.join('|')})\\b`).test(f.code)) continue; + if ( + hasSymbolImportEdge(f.code, DB_FACTORY_SYMBOLS, '@mosaicstack/db') && + !DB_FACTORY_IMPORTERS.includes(f.rel) + ) { + offenders.push(f.rel); + } + } + expect(offenders, `unenumerated db-factory importers:\n${offenders.join('\n')}`).toEqual([]); + }); + + // ------------------------------------------------------------------------- + // Permanent evasion controls: every known escape from the M4-1a review must + // be flagged by the analyzer, and the two legitimate controls must pass. + // Synthetic files run through the exact production analyzer. + // ------------------------------------------------------------------------- + const EVASIONS: Array<{ + name: string; + src: string; + extras?: Array<{ rel: string; src: string }>; + }> = [ + { + name: 'E1 namespace import write', + src: `import * as dbSchema from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(dbSchema.companies).values({}); }`, + }, + { + name: 'E2 re-export laundering (conduit consumer)', + src: `import { companies } from './evasion-barrel.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(companies).values({}); }`, + }, + { + name: 'E3 renamed createDb handle', + src: `import { createDb } from '@mosaicstack/db';\nexport async function f(t: string) { const d = createDb('u'); await d.execute('DELETE FROM ' + t); }`, + }, + { + name: 'E4 aliased sql.raw', + src: `import { sql as q } from 'drizzle-orm';\nimport { db } from './x.js';\nexport async function f(t: string) { await db.execute(q.raw('TRUNCATE ' + t)); }`, + }, + { + name: 'E5 interpolated tagged template on db receiver', + src: `import { sql } from 'drizzle-orm';\nimport { db } from './x.js';\nexport async function f() { const tbl = 'hierarchy_grants'; await db.execute(sql\`DELETE FROM \${tbl}\`); }`, + }, + { + name: 'E6 driver client via import', + src: `import postgres from 'postgres';\nexport async function f(t: string) { const c = postgres('u'); await c.unsafe('TRUNCATE ' + t); }`, + }, + { + name: 'E7 DI-shaped driver client query', + src: `export class R { constructor(private client: { query(s: string): Promise }) {}\n async f(t: string) { await this.client.query('TRUNCATE ' + t); } }`, + }, + { + name: 'E8 string containing // does not hide code', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { const x = 'oops//'; await db.insert(companies).values({}); }`, + }, + { + name: 'C-literal SQL string with class table', + src: `export const q = 'DELETE FROM hierarchy_grants WHERE role = $1';`, + }, + { + name: 'C-eval', + src: `export function f(s: string) { return eval(s); }`, + }, + // --- round-2 review shapes (R1–R5) --- + { + name: 'E9 namespace destructuring', + src: `import * as dbSchema from '@mosaicstack/db';\nimport { db } from './x.js';\nconst { companies } = dbSchema;\nexport async function f() { await db.insert(companies).values({}); }`, + }, + { + name: 'E10 nested namespace re-export', + src: `import * as ns from './evasion-mid.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns.schema.companies).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid.ts', + src: `export * as schema from '@mosaicstack/db';`, + }, + ], + }, + { + name: 'E11 literal dynamic import, destructured binding', + src: `import { db } from './x.js';\nexport async function f() { const { companies } = await import('@mosaicstack/db'); await db.insert(companies).values({}); }`, + }, + { + name: 'E12 literal dynamic import, namespace binding', + src: `import { db } from './x.js';\nexport async function f() { const m = await import('@mosaicstack/db'); await db.insert(m.companies).values({}); }`, + }, + { + name: 'E13 createRequire outside its register', + src: `import { createRequire } from 'node:module';\nconst require = createRequire(import.meta.url);\nexport const pg = require('postgres');`, + }, + { + name: 'E14 factory capability laundered through re-export', + src: `import * as dbns from './evasion-mid2.js';\nexport async function f(t: string) { await dbns.createDb('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid2.ts', + src: `export { createDb } from '@mosaicstack/db';`, + }, + ], + }, + { + name: 'E15 driver default laundered through re-export', + src: `import pg from './evasion-mid3.js';\nexport async function f(t: string) { const c = pg('u'); await c.unsafe('TRUNCATE ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid3.ts', + src: `export { default } from 'postgres';`, + }, + ], + }, + { + name: 'E16 concatenated dynamic-import specifier', + src: `export async function f() { const m = await import('@mosaicstack/' + 'db'); return m; }`, + }, + { + name: 'E17 computed-member write call', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db['insert'](companies).values({}); }`, + }, + { + name: 'E18 spaced member access write', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db . insert (companies).values({}); }`, + }, + { + name: 'E19 computed-member execution call', + src: `export class R { constructor(private client: { query(s: string): Promise }) {}\n async f(t: string) { await this.client['query']('TRUNCATE ' + t); } }`, + }, + { + name: 'E20 schema-qualified table in SQL span', + src: `export const q = 'DELETE FROM public.hierarchy_grants WHERE role = $1';`, + }, + { + name: 'E21 comment interposed in SQL span', + src: `export const q = 'DELETE FROM /* audit */ hierarchy_grants WHERE role = $1';`, + }, + { + name: 'E22 COPY statement in SQL span', + src: `export const q = 'COPY hierarchy_grants FROM STDIN';`, + }, + { + name: 'E23 escaped-backtick-quoted table in SQL span', + src: 'export const q = `DELETE FROM \\`hierarchy_grants\\` WHERE role = 1`;', + }, + // --- round-3 review shapes (G1–G6) --- + { + name: 'E24 export-site rename through conduit', + src: `import { c } from './evasion-mid4.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid4.ts', + src: `export { companies as c } from '@mosaicstack/db';`, + }, + ], + }, + { + name: 'E25 export of locally bound alias, renamed', + src: `import { co } from './evasion-mid5.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid5.ts', + src: `import { companies } from '@mosaicstack/db';\nexport { companies as co };`, + }, + ], + }, + { + name: 'E26 alias-export helper over dynamic namespace', + src: `import { co } from './evasion-mid6.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid6.ts', + src: `const mod = await import('@mosaicstack/db');\nexport const co = mod.companies;`, + }, + ], + }, + { + name: 'E27 single-const alias from namespace', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nconst t = ns.companies;\nexport async function f() { await db.insert(t).values({}); }`, + }, + { + name: 'E28 non-class destructure from namespace (fail-closed)', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nconst { schema } = ns;\nexport async function f() { await db.insert(schema.companies).values({}); }`, + }, + { + name: 'E29 destructure with default value', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nconst { companies: co = null } = ns;\nexport async function f() { await db.insert(co).values({}); }`, + }, + { + name: 'E30 factory renamed to default through conduit', + src: `import createDbNow from './evasion-mid7.js';\nexport async function f(t: string) { await createDbNow('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid7.ts', + src: `export { createDb as default } from '@mosaicstack/db';`, + }, + ], + }, + { + name: 'E31 factory alias-export helper over dynamic namespace', + src: `import { mk } from './evasion-mid8.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid8.ts', + src: `const mod = await import('@mosaicstack/db');\nexport const mk = mod.createDb;`, + }, + ], + }, + { + name: 'E32 createRequire import alias', + src: `import { createRequire as mkReq } from 'node:module';\nconst req = mkReq(import.meta.url);\nexport const pg = req('postgres');`, + }, + { + name: 'E33 then-form dynamic schema import', + src: `import { db } from './x.js';\nexport function f() { return import('@mosaicstack/db').then((m) => db.insert(m.companies).values({})); }`, + }, + { + name: 'E34 quoted schema qualifier in SQL span', + src: `export const q = 'DELETE FROM "public".hierarchy_grants WHERE role = $1';`, + }, + { + name: 'E35 SQL line comment interposed in SQL span', + src: 'export const q = `DELETE FROM -- audit\nhierarchy_grants`;', + }, + { + name: 'E36 spaced member access on conventional receiver', + src: `export class R { constructor(private db: { query(s: string): Promise }) {}\n async f(t: string) { await this.db . query ('TRUNCATE ' + t); } }`, + }, + { + name: 'E37 bracket-form schema argument', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns['companies']).values({}); }`, + }, + { + name: 'E38 parenthesized schema argument', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert((companies)).values({}); }`, + }, + { + name: 'E39 computed-member eval', + src: `export function f(s: string) { return (globalThis as never)['eval'](s); }`, + }, + { + name: 'E40 schema symbol renamed to default through conduit', + src: `import c from './evasion-mid9.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid9.ts', + src: `export { companies as default } from '@mosaicstack/db';`, + }, + ], + }, + { + name: 'E41 export default of locally bound schema symbol', + src: `import c from './evasion-mid10.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid10.ts', + src: `import { companies } from '@mosaicstack/db';\nexport default companies;`, + }, + ], + }, + { + name: 'E42 export default of dynamic namespace member', + src: `import c from './evasion-mid11.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid11.ts', + src: `const mod = await import('@mosaicstack/db');\nexport default mod.companies;`, + }, + ], + }, + { + name: 'E43 namespace member under renamed export', + src: `import * as M from './evasion-mid12.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(M.co).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid12.ts', + src: `export { companies as co } from '@mosaicstack/db';`, + }, + ], + }, + { + name: 'E44 dynamic namespace member under renamed export', + src: `import { db } from './x.js';\nconst M = await import('./evasion-mid13.js');\nexport async function f() { await db.insert(M.co).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid13.ts', + src: `export { companies as co } from '@mosaicstack/db';`, + }, + ], + }, + { + name: 'E45 two-hop factory export-from rename', + src: `import { mk } from './evasion-mid15.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid14.ts', + src: `export { createDb as mk } from '@mosaicstack/db';`, + }, + { + rel: 'packages/db/src/evasion-mid15.ts', + src: `export { mk } from './evasion-mid14.js';`, + }, + ], + }, + { + name: 'E46 export default of locally bound factory', + src: `import mk from './evasion-mid16.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid16.ts', + src: `import { createDb } from '@mosaicstack/db';\nexport default createDb;`, + }, + ], + }, + { + name: 'E47 tracked dynamic import of a factory conduit', + src: `const F = await import('./evasion-mid17.js');\nexport async function f(t: string) { await F.mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid17.ts', + src: `export { createDb as mk } from '@mosaicstack/db';`, + }, + ], + }, + { + name: 'E48 factory destructure-rename from schema namespace', + src: `import * as dbns from '@mosaicstack/db';\nconst { createDb: mk } = dbns;\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + }, + { + name: 'E49 createRequire destructured from static module namespace', + src: `import * as M from 'node:module';\nconst { createRequire: mkReq } = M;\nconst req = mkReq(import.meta.url);\nexport const pg = req('postgres');`, + }, + { + name: 'E50 spread-argument schema write', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(...[companies]).values({}); }`, + }, + { + name: 'E51 GRANT ON class table in SQL span', + src: `export const q = 'GRANT SELECT ON hierarchy_grants TO auditor';`, + }, + { + name: 'E52 LOCK TABLE class table in SQL span', + src: `export const q = 'LOCK TABLE hierarchy_grants IN ACCESS EXCLUSIVE MODE';`, + }, + { + name: 'E53 bracketed conventional receiver', + src: `export class R { async f(t: string) { await this['db'].query('TRUNCATE ' + t); } }`, + }, + { + name: 'E54 parenthesized default export of schema symbol', + src: `import c from './evasion-mid18.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid18.ts', + src: `import { companies } from '@mosaicstack/db';\nexport default (companies);`, + }, + ], + }, + { + name: 'E55 bracket-member default export over dynamic namespace', + src: `import c from './evasion-mid19.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid19.ts', + src: `const mod = await import('@mosaicstack/db');\nexport default mod['companies'];`, + }, + ], + }, + { + name: 'E56 type-asserted default export of schema symbol', + src: `import c from './evasion-mid20.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid20.ts', + src: `import { companies } from '@mosaicstack/db';\nexport default companies as unknown as object;`, + }, + ], + }, + { + name: 'E57 export-const bracket member of schema namespace', + src: `import { co } from './evasion-mid21.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid21.ts', + src: `import * as ns from '@mosaicstack/db';\nexport const co = ns['companies'];`, + }, + ], + }, + { + name: 'E58 default export of dynamic namespace factory member', + src: `import mk from './evasion-mid22.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid22.ts', + src: `const mod = await import('@mosaicstack/db');\nexport default mod.createDb;`, + }, + ], + }, + { + name: 'E59 default export of static namespace factory member', + src: `import mk from './evasion-mid23.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid23.ts', + src: `import * as ns from '@mosaicstack/db';\nexport default ns.createDb;`, + }, + ], + }, + { + name: 'E60 export-const bracket factory member', + src: `import { mk } from './evasion-mid24.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid24.ts', + src: `import * as ns from '@mosaicstack/db';\nexport const mk = ns['createDb'];`, + }, + ], + }, + { + name: 'E61 parenthesized default export of factory local', + src: `import mk from './evasion-mid25.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid25.ts', + src: `import { createDb } from '@mosaicstack/db';\nexport default (createDb);`, + }, + ], + }, + { + name: 'E62 GRANT ON TABLE class table in SQL span', + src: `export const q = 'GRANT SELECT ON TABLE hierarchy_grants TO auditor';`, + }, + { + name: 'E63 DROP TABLE IF EXISTS class table in SQL span', + src: `export const q = 'DROP TABLE IF EXISTS hierarchy_grants';`, + }, + { + name: 'E64 DELETE FROM ONLY class table in SQL span', + src: `export const q = 'DELETE FROM ONLY hierarchy_grants WHERE id = $1';`, + }, + { + name: 'E65 optional-chained namespace write target', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns?.companies).values({}); }`, + }, + { + name: 'E66 non-null-asserted namespace write target', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns!.companies).values({}); }`, + }, + { + name: 'E67 optional-chained DI receiver', + src: `export class R { constructor(private db?: { query(s: string): Promise }) {}\n async f(t: string) { await this.db?.query('TRUNCATE ' + t); } }`, + }, + { + name: 'E68 optional-chained bracketed DI receiver', + src: `export class R { async f(t: string) { await this['db']?.query('TRUNCATE ' + t); } }`, + }, + { + name: 'E69 non-null-asserted default export of schema symbol', + src: `import c from './evasion-mid26.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid26.ts', + src: `import { companies } from '@mosaicstack/db';\nexport default companies!;`, + }, + ], + }, + { + name: 'E70 satisfies-dressed default export of schema symbol', + src: `import c from './evasion-mid27.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid27.ts', + src: `import { companies } from '@mosaicstack/db';\nexport default companies satisfies object;`, + }, + ], + }, + { + name: 'E71 paren-plus-assertion-plus-non-null default export', + src: `import c from './evasion-mid28.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid28.ts', + src: `import { companies } from '@mosaicstack/db';\nexport default (companies as unknown)!;`, + }, + ], + }, + { + name: 'E72 non-null-asserted ns-member default export (dynamic ns)', + src: `import c from './evasion-mid29.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid29.ts', + src: `const mod = await import('@mosaicstack/db');\nexport default mod.companies!;`, + }, + ], + }, + { + name: 'E73 parenthesized ns-member export-const of schema symbol', + src: `import { co } from './evasion-mid30.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid30.ts', + src: `import * as ns from '@mosaicstack/db';\nexport const co = (ns.companies);`, + }, + ], + }, + { + name: 'E74 non-null-asserted default export of factory local', + src: `import mk from './evasion-mid31.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid31.ts', + src: `import { createDb } from '@mosaicstack/db';\nexport default createDb!;`, + }, + ], + }, + { + name: 'E75 non-null-asserted ns-member factory default export', + src: `import mk from './evasion-mid32.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid32.ts', + src: `import * as ns from '@mosaicstack/db';\nexport default ns.createDb!;`, + }, + ], + }, + { + name: 'E76 satisfies-dressed default export of factory local', + src: `import mk from './evasion-mid33.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid33.ts', + src: `import { createDb } from '@mosaicstack/db';\nexport default createDb satisfies typeof createDb;`, + }, + ], + }, + { + name: 'E77 parenthesized ns-member factory derivation re-exported', + src: `import mk from './evasion-mid34.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid34.ts', + src: `import * as ns from '@mosaicstack/db';\nconst mk = (ns.createDb);\nexport default mk;`, + }, + ], + }, + { + name: 'E78 optional-call write verb', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert?.(companies).values({}); }`, + }, + { + name: 'E79 optional-call DI receiver chain', + src: `export class R { constructor(private pool?: { execute(s: string): Promise }) {}\n async f(t: string) { await this.pool?.execute?.('TRUNCATE ' + t); } }`, + }, + { + name: 'E80 optional-call execution in factory-capable file', + src: `import { createDb } from '@mosaicstack/db';\nexport async function f(t: string) { const d = createDb('u'); await d.execute?.('DELETE FROM ' + t); }`, + }, + { + name: 'E81 template-literal computed member call', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db[\`insert\`](companies).values({}); }`, + }, + { + name: 'E82 template-literal computed member as write target', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns[\`companies\`]).values({}); }`, + }, + { + name: 'E83 template-literal computed member in schema default export', + src: `import c from './evasion-mid35.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid35.ts', + src: `import * as ns from '@mosaicstack/db';\nexport default ns[\`companies\`];`, + }, + ], + }, + { + name: 'E84 template-literal computed member in factory extraction', + src: `import { mk } from './evasion-mid36.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid36.ts', + src: `import * as ns from '@mosaicstack/db';\nexport const mk = ns[\`createDb\`];`, + }, + ], + }, + { + name: 'E85 type-annotated re-export of schema binding', + src: `import { co } from './evasion-mid37.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid37.ts', + src: `import { companies } from '@mosaicstack/db';\nexport const co: typeof companies = companies;`, + }, + ], + }, + { + name: 'E86 second-declarator re-export of schema binding', + src: `import { co } from './evasion-mid38.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid38.ts', + src: `import { companies } from '@mosaicstack/db';\nexport const dummy = 0,\n co = companies;`, + }, + ], + }, + { + name: 'E87 type-annotated re-export of factory binding', + src: `import { mk } from './evasion-mid39.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid39.ts', + src: `import { createDb } from '@mosaicstack/db';\nexport const mk: typeof createDb = createDb;`, + }, + ], + }, + { + name: 'E88 second-declarator re-export of factory binding', + src: `import { mk } from './evasion-mid40.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid40.ts', + src: `import { createDb } from '@mosaicstack/db';\nexport const d0 = 0,\n mk = createDb;`, + }, + ], + }, + { + name: 'E89 apply-invoked write verb', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert.apply(db, [companies]).values({}); }`, + }, + { + name: 'E90 call-invoked write verb', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert.call(db, companies).values({}); }`, + }, + { + name: 'E91 apply-invoked bracket verb in capability-free file', + src: `export class R { constructor(private pool: { execute(s: string): Promise }) {}\n async f(t: string) { await this.pool['execute'].apply(this.pool, ['TRUNCATE ' + t]); } }`, + }, + { + name: 'E92 template-keyed receiver verb in capability-free file', + src: `export class R { constructor(private pool: { execute(s: string): Promise }) {}\n async f(t: string) { await this.pool[\`execute\`].apply(this.pool, ['TRUNCATE ' + t]); } }`, + }, + { + name: 'E93 as-dressed computed key as write target', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns['companies' as const]).values({}); }`, + }, + { + name: 'E94 satisfies-dressed computed key as write target', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns['companies' satisfies 'companies']).values({}); }`, + }, + { + name: 'E95 non-null-dressed computed key as write target', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns['companies'!]).values({}); }`, + }, + { + name: 'E96 escape-built computed key as write target', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns['\\u0063ompanies']).values({}); }`, + }, + { + name: 'E97 dressed computed key in schema conduit export', + src: `import { co } from './evasion-mid41.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid41.ts', + src: `import * as ns from '@mosaicstack/db';\nexport const co = ns['companies' as const];`, + }, + ], + }, + { + name: 'E98 dressed computed key in schema default export', + src: `import c from './evasion-mid42.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid42.ts', + src: `import * as ns from '@mosaicstack/db';\nexport default ns['companies' as const];`, + }, + ], + }, + { + name: 'E99 dressed computed key in single-file factory extraction', + src: `import * as ns from '@mosaicstack/db';\nexport async function f(t: string) { const mk = ns['createDb' as const]; const d = mk('u'); await d.execute('DELETE FROM ' + t); }`, + }, + { + name: 'E100 Reflect.apply of a write verb', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await Reflect.apply(db.insert, db, [companies]); }`, + }, + { + name: 'E101 Reflect.apply of an execution verb in capability-free file', + src: `export class R { constructor(private pool: { execute(s: string): Promise }) {}\n async f(t: string) { await Reflect.apply(this.pool.execute, this.pool, ['TRUNCATE ' + t]); } }`, + }, + { + name: 'E102 Reflect.get extraction of an execution verb', + src: `import { db } from './x.js';\nexport async function f(t: string) { const fn = Reflect.get(db, 'execute') as (s: string) => Promise; await fn.call(db, 'DELETE FROM ' + t); }`, + }, + { + name: 'E103 as-dressed template key as write target', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns[\`companies\` as const]).values({}); }`, + }, + { + name: 'E104 as-dressed template key as verb call', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db[\`insert\` as const](companies).values({}); }`, + }, + { + name: 'E105 non-null-dressed template key as write target', + src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns[\`companies\`!]).values({}); }`, + }, + { + name: 'E106 dressed template receiver verb in capability-free file', + src: `export class R { constructor(private pool: { query(s: string): Promise }) {}\n async f(t: string) { await this.pool[\`query\` as const]('TRUNCATE ' + t); } }`, + }, + { + name: 'E107 dressed template key in schema conduit export', + src: `import { co } from './evasion-mid43.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`, + extras: [ + { + rel: 'packages/db/src/evasion-mid43.ts', + src: `import * as ns from '@mosaicstack/db';\nexport const co = ns[\`companies\` as const];`, + }, + ], + }, + { + name: 'E108 dressed template key in single-file factory extraction', + src: `import * as ns from '@mosaicstack/db';\nexport async function f(t: string) { const mk = ns[\`createDb\` as const]; const d = mk('u'); await d.execute('DELETE FROM ' + t); }`, + }, + { + name: 'E109 multiline Reflect.apply in prettier-broken shape', + src: `export class R { constructor(private pool: { execute(s: string): Promise }) {}\n async f(t: string) {\n await Reflect.apply(\n this.pool.execute,\n this.pool,\n ['TRUNCATE ' + t],\n );\n } }`, + }, + { + name: 'E110 bracket-spelled Reflect method', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await Reflect['apply'](db.insert, db, [companies]); }`, + }, + { + name: 'E111 Reflect.apply with nested-paren first argument', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await Reflect.apply((fn: () => unknown) => fn(), db.insert, [db, [companies]]); }`, + }, + { + name: 'E112 aliased Reflect', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { const R = Reflect; await R.apply(db.insert, db, [companies]); }`, + }, + ]; + const CLEAN_CONTROLS: Array<{ name: string; src: string }> = [ + { + name: 'clean: schema read via select', + src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { return db.select().from(companies); }`, + }, + { + name: 'clean: comment mentioning a table is not SQL', + src: `// syncs hierarchy_grants downstream\nexport const n = 1;`, + }, + { + name: 'clean: interpolated template key is a non-literal computed member, not a text-only key', + src: `export function g(o: Record void>, k: string) { o[\`\${k}\`](); }`, + }, + { + name: 'clean: strict-inequality after a bracket member is not key dressing', + src: `export function h(o: Record) { return o['kind'] !== 'x'; }`, + }, + { + name: 'clean: undressed interpolated template key stays a non-literal member under the backtick key class', + src: `export function j(o: Record, k: string) { return o[\`\${k}\`]; }`, + }, + { + name: 'clean: compound identifier containing Reflect is not the Reflect object', + src: `export class ReflectHelper { reflectStyle = 1; }\n// Reflect in a comment is prose, not code`, + }, + ]; + + it('analyzer flags every known evasion form', () => { + // A synthetic conduit so E2 exercises the fixpoint through a real re-export. + const barrel: FileFacts = { + rel: 'packages/db/src/evasion-barrel.ts', + ...lexSource(`export { companies } from '@mosaicstack/db';`), + }; + for (const e of EVASIONS) { + const rel = 'packages/db/src/evasion-sample.ts'; + const synthetic: FileFacts = { rel, ...lexSource(e.src) }; + const extraFacts: FileFacts[] = (e.extras ?? []).map((x) => ({ + rel: x.rel, + ...lexSource(x.src), + })); + const synthFiles = [...files, barrel, ...extraFacts, synthetic]; + const synthSet = new Set([...fileSet, rel, barrel.rel, ...extraFacts.map((x) => x.rel)]); + const synthCap = computeCapabilityConduits(synthFiles, synthSet); + const synthCtx: AnalysisCtx = { + fileSet: synthSet, + conduits: computeSchemaConduits(synthFiles, synthSet), + driverConduits: synthCap.driver, + factoryConduits: synthCap.factory, + }; + // An evasion is caught when ANY file in the chain is flagged: some + // laundering routes fail closed at the HELPER (origin), not the + // consumer — e.g. a template-keyed conduit export — which keeps the + // chain out of the tree just as effectively. + const v = [synthetic, ...extraFacts].flatMap((ff) => analyzeFile(ff, synthCtx)); + expect(v.length, `evasion not caught: ${e.name}`).toBeGreaterThan(0); + } + }); + + it('analyzer passes legitimate non-writer code (no false positives on controls)', () => { + for (const c of CLEAN_CONTROLS) { + const rel = 'packages/db/src/clean-sample.ts'; + const synthetic: FileFacts = { rel, ...lexSource(c.src) }; + const synthFiles = [...files, synthetic]; + const synthSet = new Set([...fileSet, rel]); + const synthCap = computeCapabilityConduits(synthFiles, synthSet); + const synthCtx: AnalysisCtx = { + fileSet: synthSet, + conduits: computeSchemaConduits(synthFiles, synthSet), + driverConduits: synthCap.driver, + factoryConduits: synthCap.factory, + }; + const v = analyzeFile(synthetic, synthCtx); + expect( + v, + `false positive on ${c.name}: ${v.map((x) => `${x.prong}:${x.detail}`).join('; ')}`, + ).toEqual([]); + } + }); +}); diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index aa220616..d2358583 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -3,6 +3,7 @@ * drizzle-kit reads this file directly (avoids CJS/ESM extension issues). */ +import { sql } from 'drizzle-orm'; import { pgTable, pgEnum, @@ -13,6 +14,8 @@ import { jsonb, index, uniqueIndex, + unique, + check, real, integer, bigint, @@ -1048,3 +1051,104 @@ export const federationEnrollmentTokens = pgTable('federation_enrollment_tokens' createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }); + +// ─── Hierarchy (tenancy/authorization structure record class) ──────────────── +// Contract: docs/requirements/hierarchy-schema.md (D2, ratified 2026-08-27). +// Five tables: companies → estates → platform_projects → workspaces, plus +// hierarchy_grants. Class rows carry parentage, naming, grant, and +// audit-linkage data only — the column sets below are exhaustive (§2.7) and +// witnessed against information_schema (§6.2). No owner_id: ownership is the +// grant structure (§4.4). All writes flow through the Gateway hierarchy +// command family only (§5.1), enforced by the writer-coverage assertion +// (§6.3b) — do not add writers outside that allowlist. + +export const companies = pgTable('companies', { + id: uuid('id').primaryKey().defaultRandom(), + name: text('name').notNull(), + slug: text('slug').notNull().unique(), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), +}); + +export const estates = pgTable( + 'estates', + { + id: uuid('id').primaryKey().defaultRandom(), + name: text('name').notNull(), + slug: text('slug').notNull(), + companyId: uuid('company_id') + .notNull() + .references(() => companies.id, { onDelete: 'restrict' }), + }, + (t) => [unique('estates_company_slug_uniq').on(t.companyId, t.slug)], +); + +export const platformProjects = pgTable( + 'platform_projects', + { + id: uuid('id').primaryKey().defaultRandom(), + name: text('name').notNull(), + slug: text('slug').notNull(), + estateId: uuid('estate_id') + .notNull() + .references(() => estates.id, { onDelete: 'restrict' }), + }, + (t) => [unique('platform_projects_estate_slug_uniq').on(t.estateId, t.slug)], +); + +export const workspaces = pgTable( + 'workspaces', + { + id: uuid('id').primaryKey().defaultRandom(), + name: text('name').notNull(), + slug: text('slug').notNull(), + platformProjectId: uuid('platform_project_id') + .notNull() + .references(() => platformProjects.id, { onDelete: 'restrict' }), + }, + (t) => [unique('workspaces_platform_project_slug_uniq').on(t.platformProjectId, t.slug)], +); + +export const hierarchyGrants = pgTable( + 'hierarchy_grants', + { + id: uuid('id').primaryKey().defaultRandom(), + // Subject: exactly one of user/team (CHECK below). Principal FKs are + // RESTRICT until a deletion-and-retention contract rules otherwise (§3.3). + userId: text('user_id').references(() => users.id, { onDelete: 'restrict' }), + teamId: uuid('team_id').references(() => teams.id, { onDelete: 'restrict' }), + // Target: exactly one of the three grantable levels (CHECK below). + // Target FKs CASCADE — the one permitted cascade in the class (§3.3); + // cascaded grant deletions are audited by the command family (§5.2). + companyId: uuid('company_id').references(() => companies.id, { onDelete: 'cascade' }), + estateId: uuid('estate_id').references(() => estates.id, { onDelete: 'cascade' }), + platformProjectId: uuid('platform_project_id').references(() => platformProjects.id, { + onDelete: 'cascade', + }), + // Role vocabulary and its CHECK constraint are contract 2 §2 (M4-2). + role: text('role').notNull(), + grantedBy: text('granted_by') + .notNull() + .references(() => users.id, { onDelete: 'restrict' }), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + }, + (t) => [ + check('hierarchy_grants_subject_check', sql`num_nonnulls(user_id, team_id) = 1`), + check( + 'hierarchy_grants_target_check', + sql`num_nonnulls(company_id, estate_id, platform_project_id) = 1`, + ), + // At most one grant per (subject, target, role) across all six + // subject×target forms — NULLS NOT DISTINCT so nullable columns + // participate (§3.2). + unique('hierarchy_grants_subject_target_role_uniq') + .on(t.userId, t.teamId, t.companyId, t.estateId, t.platformProjectId, t.role) + .nullsNotDistinct(), + index('hierarchy_grants_company_id_idx').on(t.companyId), + index('hierarchy_grants_estate_id_idx').on(t.estateId), + index('hierarchy_grants_platform_project_id_idx').on(t.platformProjectId), + index('hierarchy_grants_user_id_idx').on(t.userId), + index('hierarchy_grants_team_id_idx').on(t.teamId), + index('hierarchy_grants_granted_by_idx').on(t.grantedBy), + ], +);