/** * Mosaic Native Kanban — frozen Drizzle schema contract v1. * * This is the canonical target/compatibility declaration for integration into * the ONE current-main packages/db/src/schema.ts. It MUST NOT be imported as a * competing schema module. KBN-100 applies the field-by-field expand/backfill/ * switch/contract map in SHARED-CONTRACT.md; legacy fields marked below remain * declared throughout the expand and N-1 window. * * Existing Better Auth users.id is the identity parent. User references require * active workspace membership checks in the same authoritative transaction. */ import { sql } from 'drizzle-orm'; import { bigint, boolean, check, foreignKey, index, integer, jsonb, numeric, pgEnum, pgTable, primaryKey, text, timestamp, uniqueIndex, uuid, } from 'drizzle-orm/pg-core'; // ─── Frozen vocabularies ───────────────────────────────────────────────────── export const workspaceLifecycleEnum = pgEnum('workspace_lifecycle', [ 'active', 'suspended', 'archived', ]); export const workspaceMemberRoleEnum = pgEnum('workspace_member_role', [ 'owner', 'admin', 'member', 'auditor', 'service', ]); export const teamMemberRoleEnum = pgEnum('team_member_role', ['manager', 'member']); export const agentLifecycleEnum = pgEnum('agent_lifecycle', ['enabled', 'disabled']); export const agentSessionStateEnum = pgEnum('agent_session_state', [ 'starting', 'available', 'busy', 'degraded', 'offline', 'ended', ]); export const projectStatusEnum = pgEnum('project_status_v1', [ 'planning', 'active', 'paused', 'completed', 'archived', ]); export const missionStatusEnum = pgEnum('mission_status_v1', [ 'draft', 'awaiting_approval', 'active', 'paused', 'certifying', 'completed', 'failed', 'cancelled', ]); export const milestoneStatusEnum = pgEnum('milestone_status_v1', [ 'planned', 'active', 'at_risk', 'completed', 'cancelled', ]); export const taskStatusEnum = pgEnum('task_status_v1', [ 'backlog', 'ready', 'in_progress', 'blocked', 'in_review', 'done', 'cancelled', ]); export const priorityEnum = pgEnum('work_priority_v1', ['critical', 'high', 'medium', 'low']); export const specialistRoleEnum = pgEnum('specialist_role_v1', [ 'planning', 'enhance', 'coder', 'review', 'security-review', 'pr-monitor', 'certifier', ]); export const dependencyTypeEnum = pgEnum('task_dependency_type', [ 'blocks', 'review_gate', 'certification_gate', ]); /** Must match assignmentStates in mechanical-coordinator.v1.ts exactly. */ export const assignmentStateEnum = pgEnum('task_assignment_state_v1', [ 'awaiting_approval', 'policy_pre_authorized', 'approved', 'rejected', 'leased', 'released', 'expired', 'superseded', ]); export const leaseStateEnum = pgEnum('task_lease_state', [ 'pending_ack', 'active', 'released', 'expired', 'revoked', ]); export const actorKindEnum = pgEnum('actor_kind_v1', [ 'user', 'agent', 'session', 'service', 'policy', 'system', ]); export const approvalDecisionEnum = pgEnum('approval_decision_v1', [ 'requested', 'approved', 'rejected', 'escalated', ]); export const executionDispositionEnum = pgEnum('task_execution_disposition_v1', [ 'available', 'retry_delayed', 'quarantined', 'exhausted', ]); export const changeProposalStateEnum = pgEnum('change_proposal_state_v1', [ 'pending', 'accepted', 'rejected', ]); export const outboxStateEnum = pgEnum('outbox_state_v1', [ 'pending', 'publishing', 'published', 'failed', ]); // ─── Tenant and identity ───────────────────────────────────────────────────── export const workspacesV1 = pgTable( 'workspaces', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull(), slug: text('slug').notNull(), settings: jsonb('settings').notNull().$type>().default({}), lifecycle: workspaceLifecycleEnum('lifecycle').notNull().default('active'), ownerId: text('owner_id').notNull(), // FK to existing users.id at integration version: integer('version').notNull().default(1), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ uniqueIndex('workspaces_slug_uidx').on(t.slug), check('workspaces_version_positive_chk', sql`${t.version} > 0`), ], ); export const workspaceMembersV1 = pgTable( 'workspace_members', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), userId: text('user_id').notNull(), // FK to existing users.id at integration role: workspaceMemberRoleEnum('role').notNull().default('member'), active: boolean('active').notNull().default(true), joinedAt: timestamp('joined_at', { withTimezone: true }).notNull().defaultNow(), revokedAt: timestamp('revoked_at', { withTimezone: true }), }, (t) => [ uniqueIndex('workspace_members_workspace_user_uidx').on(t.workspaceId, t.userId), index('workspace_members_user_active_idx').on(t.userId, t.active), ], ); export const teamsV1 = pgTable( 'teams', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), name: text('name').notNull(), slug: text('slug').notNull(), ownerId: text('owner_id').notNull(), // legacy/current users.id field retained managerId: text('manager_id').notNull(), // legacy/current users.id field retained createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ uniqueIndex('teams_workspace_slug_uidx').on(t.workspaceId, t.slug), uniqueIndex('teams_workspace_id_uidx').on(t.workspaceId, t.id), ], ); export const teamMembersV1 = pgTable( 'team_members', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), teamId: uuid('team_id').notNull(), userId: text('user_id').notNull(), role: teamMemberRoleEnum('role').notNull().default('member'), invitedBy: text('invited_by'), joinedAt: timestamp('joined_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ foreignKey({ name: 'team_members_workspace_team_fk', columns: [t.workspaceId, t.teamId], foreignColumns: [teamsV1.workspaceId, teamsV1.id], }).onDelete('restrict'), uniqueIndex('team_members_workspace_team_user_uidx').on(t.workspaceId, t.teamId, t.userId), ], ); export const agentsV1 = pgTable( 'agents', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), name: text('name').notNull(), provider: text('provider').notNull(), // legacy retained model: text('model').notNull(), // legacy retained legacyStatus: text('status', { enum: ['idle', 'active', 'error', 'offline'], }) .notNull() .default('idle'), projectId: uuid('project_id'), // legacy retained through N-1 ownerId: text('owner_id'), // legacy retained; active membership required systemPrompt: text('system_prompt'), // legacy retained allowedTools: jsonb('allowed_tools').$type(), // legacy retained skills: jsonb('skills').$type(), // legacy retained isSystem: boolean('is_system').notNull().default(false), // legacy retained config: jsonb('config'), // legacy retained runtime: text('runtime').notNull(), roles: jsonb('roles').notNull().$type().default([]), capabilities: jsonb('capabilities').notNull().$type().default([]), lifecycle: agentLifecycleEnum('lifecycle').notNull().default('enabled'), metadata: jsonb('metadata').notNull().$type>().default({}), version: integer('version').notNull().default(1), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ uniqueIndex('agents_workspace_id_uidx').on(t.workspaceId, t.id), uniqueIndex('agents_workspace_name_uidx').on(t.workspaceId, t.name), index('agents_workspace_lifecycle_idx').on(t.workspaceId, t.lifecycle), check('agents_version_positive_chk', sql`${t.version} > 0`), ], ); export const agentSessionsV1 = pgTable( 'agent_sessions', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), agentId: uuid('agent_id').notNull(), harnessSessionKey: text('harness_session_key').notNull(), host: text('host').notNull(), state: agentSessionStateEnum('state').notNull().default('starting'), declaredRoles: specialistRoleEnum('declared_primary_role'), roleSet: jsonb('role_set').notNull().$type().default([]), capabilities: jsonb('capabilities').notNull().$type().default([]), capacity: integer('capacity').notNull().default(1), contextUsagePercent: integer('context_usage_percent'), startedAt: timestamp('started_at', { withTimezone: true }).notNull().defaultNow(), lastHeartbeatAt: timestamp('last_heartbeat_at', { withTimezone: true }), endedAt: timestamp('ended_at', { withTimezone: true }), metadata: jsonb('metadata').notNull().$type>().default({}), }, (t) => [ foreignKey({ name: 'agent_sessions_workspace_agent_fk', columns: [t.workspaceId, t.agentId], foreignColumns: [agentsV1.workspaceId, agentsV1.id], }).onDelete('restrict'), uniqueIndex('agent_sessions_workspace_harness_key_uidx').on(t.workspaceId, t.harnessSessionKey), uniqueIndex('agent_sessions_workspace_id_uidx').on(t.workspaceId, t.id), uniqueIndex('agent_sessions_workspace_agent_id_uidx').on(t.workspaceId, t.agentId, t.id), index('agent_sessions_workspace_state_heartbeat_idx').on( t.workspaceId, t.state, t.lastHeartbeatAt, ), check('agent_sessions_capacity_positive_chk', sql`${t.capacity} > 0`), check( 'agent_sessions_context_percent_chk', sql`${t.contextUsagePercent} is null or (${t.contextUsagePercent} >= 0 and ${t.contextUsagePercent} <= 100)`, ), ], ); // ─── Planning hierarchy ────────────────────────────────────────────────────── export const projectsV1 = pgTable( 'projects', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), name: text('name').notNull(), description: text('description'), /** Legacy status remains declared/readable through the N-1 window. */ legacyStatus: text('status', { enum: ['active', 'paused', 'completed', 'archived'], }) .notNull() .default('active'), canonicalStatus: projectStatusEnum('canonical_status').notNull().default('planning'), /** Legacy ownership fields retained until contract release. */ legacyOwnerId: text('owner_id'), legacyTeamId: uuid('team_id'), legacyOwnerType: text('owner_type', { enum: ['user', 'team'] }) .notNull() .default('user'), accountableUserId: text('accountable_user_id'), accountableTeamId: uuid('accountable_team_id'), priority: priorityEnum('priority').notNull().default('medium'), repositoryUrl: text('repository_url'), repositoryProvider: text('repository_provider'), defaultBranch: text('default_branch'), domain: text('domain'), startDate: timestamp('start_date', { withTimezone: true }), targetDate: timestamp('target_date', { withTimezone: true }), blockerSummary: text('blocker_summary'), progressPolicy: jsonb('progress_policy').notNull().$type>().default({}), metadata: jsonb('metadata').notNull().$type>().default({}), version: integer('version').notNull().default(1), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ foreignKey({ name: 'projects_workspace_accountable_user_fk', columns: [t.workspaceId, t.accountableUserId], foreignColumns: [workspaceMembersV1.workspaceId, workspaceMembersV1.userId], }).onDelete('restrict'), foreignKey({ name: 'projects_workspace_accountable_team_fk', columns: [t.workspaceId, t.accountableTeamId], foreignColumns: [teamsV1.workspaceId, teamsV1.id], }).onDelete('restrict'), uniqueIndex('projects_workspace_id_uidx').on(t.workspaceId, t.id), index('projects_workspace_status_idx').on(t.workspaceId, t.canonicalStatus), check( 'projects_exactly_one_accountable_owner_chk', sql`num_nonnulls(${t.accountableUserId}, ${t.accountableTeamId}) = 1`, ), check('projects_version_positive_chk', sql`${t.version} > 0`), ], ); export const milestonesV1 = pgTable( 'milestones', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), projectId: uuid('project_id').notNull(), name: text('name').notNull(), description: text('description'), status: milestoneStatusEnum('status').notNull().default('planned'), sequence: integer('sequence').notNull(), targetDate: timestamp('target_date', { withTimezone: true }), completedAt: timestamp('completed_at', { withTimezone: true }), providerMilestoneRef: text('provider_milestone_ref'), acceptanceSummary: text('acceptance_summary'), version: integer('version').notNull().default(1), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ foreignKey({ name: 'milestones_workspace_project_fk', columns: [t.workspaceId, t.projectId], foreignColumns: [projectsV1.workspaceId, projectsV1.id], }).onDelete('restrict'), uniqueIndex('milestones_workspace_project_id_uidx').on(t.workspaceId, t.projectId, t.id), uniqueIndex('milestones_project_sequence_uidx').on(t.workspaceId, t.projectId, t.sequence), index('milestones_workspace_project_status_idx').on(t.workspaceId, t.projectId, t.status), check('milestones_sequence_positive_chk', sql`${t.sequence} > 0`), check('milestones_version_positive_chk', sql`${t.version} > 0`), ], ); /** Avoids an unsafe circular projects.current_milestone FK during expand. */ export const projectCurrentMilestonesV1 = pgTable( 'project_current_milestones', { workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), projectId: uuid('project_id').notNull(), milestoneId: uuid('milestone_id').notNull(), setByUserId: text('set_by_user_id').notNull(), setAt: timestamp('set_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ primaryKey({ name: 'project_current_milestones_pk', columns: [t.workspaceId, t.projectId] }), foreignKey({ name: 'project_current_milestones_project_fk', columns: [t.workspaceId, t.projectId], foreignColumns: [projectsV1.workspaceId, projectsV1.id], }).onDelete('restrict'), foreignKey({ name: 'project_current_milestones_milestone_fk', columns: [t.workspaceId, t.projectId, t.milestoneId], foreignColumns: [milestonesV1.workspaceId, milestonesV1.projectId, milestonesV1.id], }).onDelete('restrict'), foreignKey({ name: 'project_current_milestones_set_by_user_fk', columns: [t.workspaceId, t.setByUserId], foreignColumns: [workspaceMembersV1.workspaceId, workspaceMembersV1.userId], }).onDelete('restrict'), ], ); export const missionsV1 = pgTable( 'missions', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), projectId: uuid('project_id').notNull(), name: text('name').notNull(), /** Legacy fields remain through N-1 and are mapped, never dropped on expand. */ legacyDescription: text('description'), legacyStatus: text('status', { enum: ['planning', 'active', 'paused', 'completed', 'failed'], }) .notNull() .default('planning'), legacyUserId: text('user_id'), legacyMilestones: jsonb('milestones').$type[]>(), legacyConfig: jsonb('config'), objective: text('objective').notNull(), canonicalStatus: missionStatusEnum('canonical_status').notNull().default('draft'), phase: text('phase'), prdArtifactUri: text('prd_artifact_uri'), prdRevision: text('prd_revision'), portfolioOrchestratorId: text('portfolio_orchestrator_id'), projectSubOrchestratorId: text('project_sub_orchestrator_id'), approvalPolicy: jsonb('approval_policy').notNull().$type>().default({}), startedAt: timestamp('started_at', { withTimezone: true }), completedAt: timestamp('completed_at', { withTimezone: true }), metadata: jsonb('metadata').notNull().$type>().default({}), version: integer('version').notNull().default(1), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ foreignKey({ name: 'missions_workspace_project_fk', columns: [t.workspaceId, t.projectId], foreignColumns: [projectsV1.workspaceId, projectsV1.id], }).onDelete('restrict'), uniqueIndex('missions_workspace_project_id_uidx').on(t.workspaceId, t.projectId, t.id), index('missions_workspace_project_status_idx').on( t.workspaceId, t.projectId, t.canonicalStatus, ), check('missions_version_positive_chk', sql`${t.version} > 0`), ], ); export const missionMilestonesV1 = pgTable( 'mission_milestones', { workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), projectId: uuid('project_id').notNull(), missionId: uuid('mission_id').notNull(), milestoneId: uuid('milestone_id').notNull(), ordering: integer('ordering').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ primaryKey({ name: 'mission_milestones_pk', columns: [t.workspaceId, t.projectId, t.missionId, t.milestoneId], }), foreignKey({ name: 'mission_milestones_project_mission_fk', columns: [t.workspaceId, t.projectId, t.missionId], foreignColumns: [missionsV1.workspaceId, missionsV1.projectId, missionsV1.id], }).onDelete('restrict'), foreignKey({ name: 'mission_milestones_project_milestone_fk', columns: [t.workspaceId, t.projectId, t.milestoneId], foreignColumns: [milestonesV1.workspaceId, milestonesV1.projectId, milestonesV1.id], }).onDelete('restrict'), uniqueIndex('mission_milestones_order_uidx').on( t.workspaceId, t.projectId, t.missionId, t.ordering, ), check('mission_milestones_order_positive_chk', sql`${t.ordering} > 0`), ], ); // ─── Tasks, tags, dependencies ─────────────────────────────────────────────── export const tasksV1 = pgTable( 'tasks', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), projectId: uuid('project_id').notNull(), missionId: uuid('mission_id'), milestoneId: uuid('milestone_id'), parentTaskId: uuid('parent_task_id'), title: text('title').notNull(), description: text('description'), /** Legacy/current-main fields retained throughout expand/N-1. */ legacyStatus: text('status', { enum: ['not-started', 'in-progress', 'blocked', 'done', 'cancelled'], }) .notNull() .default('not-started'), legacyAssignee: text('assignee'), legacyTags: jsonb('tags').$type(), legacyDueDate: timestamp('due_date', { withTimezone: true }), acceptanceCriteria: jsonb('acceptance_criteria') .notNull() .$type | string[]>() .default([]), canonicalStatus: taskStatusEnum('canonical_status').notNull().default('backlog'), priority: priorityEnum('priority').notNull().default('medium'), boardRank: numeric('board_rank', { precision: 30, scale: 15 }).notNull().default('1000'), accountableUserId: text('accountable_user_id'), accountableTeamId: uuid('accountable_team_id'), assignedSpecialistRole: specialistRoleEnum('assigned_specialist_role'), dueAt: timestamp('due_at', { withTimezone: true }), notBeforeAt: timestamp('not_before_at', { withTimezone: true }), estimateMinutes: integer('estimate_minutes'), progressPercent: integer('progress_percent').notNull().default(0), blocker: text('blocker'), retryPolicy: jsonb('retry_policy').notNull().$type>().default({}), /** Atomically incremented under this task row lock for every new lease. */ fencingCounter: bigint('fencing_counter', { mode: 'bigint' }) .notNull() .default(sql`0`), archivedAt: timestamp('archived_at', { withTimezone: true }), archivedByUserId: text('archived_by_user_id'), archiveReason: text('archive_reason'), metadata: jsonb('metadata').notNull().$type>().default({}), version: integer('version').notNull().default(1), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), completedAt: timestamp('completed_at', { withTimezone: true }), }, (t) => [ foreignKey({ name: 'tasks_workspace_project_fk', columns: [t.workspaceId, t.projectId], foreignColumns: [projectsV1.workspaceId, projectsV1.id], }).onDelete('restrict'), foreignKey({ name: 'tasks_project_mission_fk', columns: [t.workspaceId, t.projectId, t.missionId], foreignColumns: [missionsV1.workspaceId, missionsV1.projectId, missionsV1.id], }).onDelete('restrict'), foreignKey({ name: 'tasks_project_milestone_fk', columns: [t.workspaceId, t.projectId, t.milestoneId], foreignColumns: [milestonesV1.workspaceId, milestonesV1.projectId, milestonesV1.id], }).onDelete('restrict'), foreignKey({ name: 'tasks_project_parent_fk', columns: [t.workspaceId, t.projectId, t.parentTaskId], foreignColumns: [t.workspaceId, t.projectId, t.id], }).onDelete('restrict'), foreignKey({ name: 'tasks_workspace_accountable_user_fk', columns: [t.workspaceId, t.accountableUserId], foreignColumns: [workspaceMembersV1.workspaceId, workspaceMembersV1.userId], }).onDelete('restrict'), foreignKey({ name: 'tasks_workspace_archived_by_user_fk', columns: [t.workspaceId, t.archivedByUserId], foreignColumns: [workspaceMembersV1.workspaceId, workspaceMembersV1.userId], }).onDelete('restrict'), foreignKey({ name: 'tasks_workspace_accountable_team_fk', columns: [t.workspaceId, t.accountableTeamId], foreignColumns: [teamsV1.workspaceId, teamsV1.id], }).onDelete('restrict'), uniqueIndex('tasks_workspace_id_uidx').on(t.workspaceId, t.id), uniqueIndex('tasks_workspace_project_id_uidx').on(t.workspaceId, t.projectId, t.id), index('tasks_workspace_project_status_rank_idx').on( t.workspaceId, t.projectId, t.canonicalStatus, t.boardRank, ), index('tasks_workspace_due_idx').on(t.workspaceId, t.dueAt), check( 'tasks_exactly_one_accountable_owner_chk', sql`num_nonnulls(${t.accountableUserId}, ${t.accountableTeamId}) = 1`, ), check('tasks_version_positive_chk', sql`${t.version} > 0`), check('tasks_fencing_counter_nonnegative_chk', sql`${t.fencingCounter} >= 0`), check( 'tasks_progress_percent_chk', sql`${t.progressPercent} >= 0 and ${t.progressPercent} <= 100`, ), check( 'tasks_archive_fields_chk', sql`(${t.archivedAt} is null and ${t.archivedByUserId} is null and ${t.archiveReason} is null) or (${t.archivedAt} is not null and ${t.archivedByUserId} is not null and ${t.archiveReason} is not null)`, ), ], ); export const tagsV1 = pgTable( 'tags', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), name: text('name').notNull(), normalizedName: text('normalized_name').notNull(), color: text('color'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ uniqueIndex('tags_workspace_id_uidx').on(t.workspaceId, t.id), uniqueIndex('tags_workspace_normalized_name_uidx').on(t.workspaceId, t.normalizedName), ], ); export const taskTagsV1 = pgTable( 'task_tags', { workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), taskId: uuid('task_id').notNull(), tagId: uuid('tag_id').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ primaryKey({ name: 'task_tags_pk', columns: [t.workspaceId, t.taskId, t.tagId] }), foreignKey({ name: 'task_tags_workspace_task_fk', columns: [t.workspaceId, t.taskId], foreignColumns: [tasksV1.workspaceId, tasksV1.id], }).onDelete('restrict'), foreignKey({ name: 'task_tags_workspace_tag_fk', columns: [t.workspaceId, t.tagId], foreignColumns: [tagsV1.workspaceId, tagsV1.id], }).onDelete('restrict'), ], ); export const taskDependenciesV1 = pgTable( 'task_dependencies', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), predecessorTaskId: uuid('predecessor_task_id').notNull(), successorTaskId: uuid('successor_task_id').notNull(), dependencyType: dependencyTypeEnum('dependency_type').notNull().default('blocks'), completionCondition: jsonb('completion_condition').$type>(), createdByActorKind: actorKindEnum('created_by_actor_kind').notNull(), createdByActorId: text('created_by_actor_id').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ foreignKey({ name: 'task_dependencies_predecessor_fk', columns: [t.workspaceId, t.predecessorTaskId], foreignColumns: [tasksV1.workspaceId, tasksV1.id], }).onDelete('restrict'), foreignKey({ name: 'task_dependencies_successor_fk', columns: [t.workspaceId, t.successorTaskId], foreignColumns: [tasksV1.workspaceId, tasksV1.id], }).onDelete('restrict'), /** One directed pair only; dependency type is an attribute, not a second edge. */ uniqueIndex('task_dependencies_directed_edge_uidx').on( t.workspaceId, t.predecessorTaskId, t.successorTaskId, ), index('task_dependencies_successor_idx').on(t.workspaceId, t.successorTaskId), check( 'task_dependencies_no_self_edge_chk', sql`${t.predecessorTaskId} <> ${t.successorTaskId}`, ), ], ); // ─── Links, immutable artifacts, audit events, outage proposals ────────────── export const externalLinksV1 = pgTable( 'external_links', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), entityType: text('entity_type', { enum: ['project', 'mission', 'milestone', 'task'], }).notNull(), entityId: uuid('entity_id').notNull(), provider: text('provider').notNull(), linkType: text('link_type', { enum: ['issue', 'pr', 'ci', 'document', 'release', 'deployment'], }).notNull(), externalId: text('external_id').notNull(), url: text('url').notNull(), repository: text('repository'), syncMetadata: jsonb('sync_metadata').notNull().$type>().default({}), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ uniqueIndex('external_links_entity_provider_type_external_uidx').on( t.workspaceId, t.entityType, t.entityId, t.provider, t.linkType, t.externalId, ), index('external_links_entity_idx').on(t.workspaceId, t.entityType, t.entityId), ], ); export const artifactsV1 = pgTable( 'artifacts', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), taskId: uuid('task_id'), missionId: uuid('mission_id'), type: text('type').notNull(), uri: text('uri').notNull(), immutableRevision: text('immutable_revision').notNull(), digest: text('digest').notNull(), producerActorKind: actorKindEnum('producer_actor_kind').notNull(), producerActorId: text('producer_actor_id').notNull(), evidenceClassification: text('evidence_classification').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ foreignKey({ name: 'artifacts_workspace_task_fk', columns: [t.workspaceId, t.taskId], foreignColumns: [tasksV1.workspaceId, tasksV1.id], }).onDelete('restrict'), foreignKey({ name: 'artifacts_workspace_mission_fk', columns: [t.workspaceId, t.missionId], foreignColumns: [missionsV1.workspaceId, missionsV1.id], }).onDelete('restrict'), uniqueIndex('artifacts_workspace_id_uidx').on(t.workspaceId, t.id), uniqueIndex('artifacts_workspace_digest_uidx').on(t.workspaceId, t.digest), check('artifacts_exactly_one_owner_chk', sql`num_nonnulls(${t.taskId}, ${t.missionId}) = 1`), ], ); export const taskEventsV1 = pgTable( 'task_events', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), aggregateType: text('aggregate_type', { enum: [ 'workspace', 'project', 'mission', 'milestone', 'task', 'assignment', 'lease', 'change_proposal', ], }).notNull(), aggregateId: uuid('aggregate_id').notNull(), eventType: text('event_type').notNull(), actorKind: actorKindEnum('actor_kind').notNull(), actorId: text('actor_id').notNull(), correlationId: uuid('correlation_id').notNull(), causationId: uuid('causation_id'), idempotencyKey: text('idempotency_key').notNull(), previousVersion: integer('previous_version'), newVersion: integer('new_version'), payload: jsonb('payload').notNull().$type>().default({}), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ uniqueIndex('task_events_workspace_id_uidx').on(t.workspaceId, t.id), uniqueIndex('task_events_workspace_idempotency_uidx').on(t.workspaceId, t.idempotencyKey), index('task_events_aggregate_created_idx').on( t.workspaceId, t.aggregateType, t.aggregateId, t.createdAt, ), ], ); export const changeProposalsV1 = pgTable( 'change_proposals', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), proposerUserId: text('proposer_user_id').notNull(), sourceNoteDigest: text('source_note_digest').notNull(), targetAggregateType: text('target_aggregate_type', { enum: ['project', 'mission', 'milestone', 'task'], }).notNull(), targetAggregateId: uuid('target_aggregate_id').notNull(), expectedAggregateVersion: integer('expected_aggregate_version').notNull(), proposedCommand: text('proposed_command').notNull(), proposedPayload: jsonb('proposed_payload').notNull().$type>(), state: changeProposalStateEnum('state').notNull().default('pending'), idempotencyKey: text('idempotency_key').notNull(), submittedAuditEventId: uuid('submitted_audit_event_id').notNull(), decisionActorUserId: text('decision_actor_user_id'), decisionReason: text('decision_reason'), decidedAt: timestamp('decided_at', { withTimezone: true }), acceptedCommandAuditEventId: uuid('accepted_command_audit_event_id'), version: integer('version').notNull().default(1), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ foreignKey({ name: 'change_proposals_workspace_proposer_user_fk', columns: [t.workspaceId, t.proposerUserId], foreignColumns: [workspaceMembersV1.workspaceId, workspaceMembersV1.userId], }).onDelete('restrict'), foreignKey({ name: 'change_proposals_workspace_decision_actor_user_fk', columns: [t.workspaceId, t.decisionActorUserId], foreignColumns: [workspaceMembersV1.workspaceId, workspaceMembersV1.userId], }).onDelete('restrict'), foreignKey({ name: 'change_proposals_workspace_submitted_event_fk', columns: [t.workspaceId, t.submittedAuditEventId], foreignColumns: [taskEventsV1.workspaceId, taskEventsV1.id], }).onDelete('restrict'), foreignKey({ name: 'change_proposals_workspace_accepted_command_event_fk', columns: [t.workspaceId, t.acceptedCommandAuditEventId], foreignColumns: [taskEventsV1.workspaceId, taskEventsV1.id], }).onDelete('restrict'), uniqueIndex('change_proposals_workspace_id_uidx').on(t.workspaceId, t.id), uniqueIndex('change_proposals_workspace_idempotency_uidx').on(t.workspaceId, t.idempotencyKey), index('change_proposals_workspace_target_state_idx').on( t.workspaceId, t.targetAggregateType, t.targetAggregateId, t.state, ), check('change_proposals_expected_version_positive_chk', sql`${t.expectedAggregateVersion} > 0`), check('change_proposals_version_positive_chk', sql`${t.version} > 0`), check( 'change_proposals_decision_fields_chk', sql`(${t.state} = 'pending' and ${t.decisionActorUserId} is null and ${t.decisionReason} is null and ${t.decidedAt} is null and ${t.acceptedCommandAuditEventId} is null) or (${t.state} = 'rejected' and ${t.decisionActorUserId} is not null and ${t.decisionReason} is not null and ${t.decidedAt} is not null and ${t.acceptedCommandAuditEventId} is null) or (${t.state} = 'accepted' and ${t.decisionActorUserId} is not null and ${t.decisionReason} is not null and ${t.decidedAt} is not null and ${t.acceptedCommandAuditEventId} is not null)`, ), ], ); // ─── Assignments, execution, fencing, checkpoints ──────────────────────────── export const taskAssignmentsV1 = pgTable( 'task_assignments', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), taskId: uuid('task_id').notNull(), taskVersion: integer('task_version').notNull(), targetUserId: text('target_user_id'), targetTeamId: uuid('target_team_id'), targetAgentId: uuid('target_agent_id'), targetSessionId: uuid('target_session_id'), specialistRole: specialistRoleEnum('specialist_role').notNull(), state: assignmentStateEnum('state').notNull().default('awaiting_approval'), policyRevision: text('policy_revision').notNull(), proposedByUserId: text('proposed_by_user_id'), proposedByAgentId: uuid('proposed_by_agent_id'), reason: text('reason').notNull(), proposedAt: timestamp('proposed_at', { withTimezone: true }).notNull().defaultNow(), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), approvedAt: timestamp('approved_at', { withTimezone: true }), endedAt: timestamp('ended_at', { withTimezone: true }), }, (t) => [ foreignKey({ name: 'task_assignments_workspace_task_fk', columns: [t.workspaceId, t.taskId], foreignColumns: [tasksV1.workspaceId, tasksV1.id], }).onDelete('restrict'), foreignKey({ name: 'task_assignments_workspace_user_fk', columns: [t.workspaceId, t.targetUserId], foreignColumns: [workspaceMembersV1.workspaceId, workspaceMembersV1.userId], }).onDelete('restrict'), foreignKey({ name: 'task_assignments_workspace_proposer_user_fk', columns: [t.workspaceId, t.proposedByUserId], foreignColumns: [workspaceMembersV1.workspaceId, workspaceMembersV1.userId], }).onDelete('restrict'), foreignKey({ name: 'task_assignments_workspace_proposer_agent_fk', columns: [t.workspaceId, t.proposedByAgentId], foreignColumns: [agentsV1.workspaceId, agentsV1.id], }).onDelete('restrict'), foreignKey({ name: 'task_assignments_workspace_team_fk', columns: [t.workspaceId, t.targetTeamId], foreignColumns: [teamsV1.workspaceId, teamsV1.id], }).onDelete('restrict'), foreignKey({ name: 'task_assignments_workspace_agent_fk', columns: [t.workspaceId, t.targetAgentId], foreignColumns: [agentsV1.workspaceId, agentsV1.id], }).onDelete('restrict'), foreignKey({ name: 'task_assignments_workspace_agent_session_fk', columns: [t.workspaceId, t.targetAgentId, t.targetSessionId], foreignColumns: [agentSessionsV1.workspaceId, agentSessionsV1.agentId, agentSessionsV1.id], }).onDelete('restrict'), uniqueIndex('task_assignments_workspace_id_uidx').on(t.workspaceId, t.id), uniqueIndex('task_assignments_exact_target_uidx').on( t.workspaceId, t.taskId, t.id, t.targetAgentId, t.targetSessionId, ), index('task_assignments_workspace_task_state_idx').on(t.workspaceId, t.taskId, t.state), check( 'task_assignments_exactly_one_principal_chk', sql`num_nonnulls(${t.targetUserId}, ${t.targetTeamId}, ${t.targetAgentId}) = 1`, ), check( 'task_assignments_exactly_one_proposer_chk', sql`num_nonnulls(${t.proposedByUserId}, ${t.proposedByAgentId}) = 1`, ), check( 'task_assignments_agent_session_pair_chk', sql`(${t.targetAgentId} is null and ${t.targetSessionId} is null) or (${t.targetAgentId} is not null and ${t.targetSessionId} is not null)`, ), check('task_assignments_task_version_positive_chk', sql`${t.taskVersion} > 0`), ], ); export const taskExecutionStatesV1 = pgTable( 'task_execution_states', { workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), taskId: uuid('task_id').notNull(), disposition: executionDispositionEnum('disposition').notNull().default('available'), attemptCount: integer('attempt_count').notNull().default(0), maxAttempts: integer('max_attempts').notNull(), nextEligibleAt: timestamp('next_eligible_at', { withTimezone: true }), terminalReason: text('terminal_reason'), updatedByActorKind: actorKindEnum('updated_by_actor_kind').notNull(), updatedByActorId: text('updated_by_actor_id').notNull(), policyRevision: text('policy_revision').notNull(), version: integer('version').notNull().default(1), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ primaryKey({ name: 'task_execution_states_pk', columns: [t.workspaceId, t.taskId] }), foreignKey({ name: 'task_execution_states_workspace_task_fk', columns: [t.workspaceId, t.taskId], foreignColumns: [tasksV1.workspaceId, tasksV1.id], }).onDelete('restrict'), index('task_execution_states_disposition_next_idx').on( t.workspaceId, t.disposition, t.nextEligibleAt, ), check('task_execution_states_attempt_nonnegative_chk', sql`${t.attemptCount} >= 0`), check('task_execution_states_max_positive_chk', sql`${t.maxAttempts} > 0`), check('task_execution_states_attempt_bound_chk', sql`${t.attemptCount} <= ${t.maxAttempts}`), check('task_execution_states_version_positive_chk', sql`${t.version} > 0`), ], ); export const taskLeasesV1 = pgTable( 'task_leases', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), taskId: uuid('task_id').notNull(), assignmentId: uuid('assignment_id').notNull(), agentId: uuid('agent_id').notNull(), agentSessionId: uuid('agent_session_id').notNull(), state: leaseStateEnum('state').notNull().default('pending_ack'), acquiredAt: timestamp('acquired_at', { withTimezone: true }).notNull().defaultNow(), acknowledgedAt: timestamp('acknowledged_at', { withTimezone: true }), acknowledgeBy: timestamp('acknowledge_by', { withTimezone: true }).notNull(), lastHeartbeatAt: timestamp('last_heartbeat_at', { withTimezone: true }), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), /** Exact value atomically returned from tasks.fencing_counter. */ fencingToken: bigint('fencing_token', { mode: 'bigint' }).notNull(), attemptNumber: integer('attempt_number').notNull(), releaseReason: text('release_reason'), releasedAt: timestamp('released_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ foreignKey({ name: 'task_leases_exact_assignment_target_fk', columns: [t.workspaceId, t.taskId, t.assignmentId, t.agentId, t.agentSessionId], foreignColumns: [ taskAssignmentsV1.workspaceId, taskAssignmentsV1.taskId, taskAssignmentsV1.id, taskAssignmentsV1.targetAgentId, taskAssignmentsV1.targetSessionId, ], }).onDelete('restrict'), uniqueIndex('task_leases_active_task_uidx') .on(t.workspaceId, t.taskId) .where(sql`${t.state} in ('pending_ack', 'active')`), uniqueIndex('task_leases_exact_fence_uidx').on(t.workspaceId, t.taskId, t.id, t.fencingToken), uniqueIndex('task_leases_task_fence_uidx').on(t.workspaceId, t.taskId, t.fencingToken), index('task_leases_workspace_state_expiry_idx').on(t.workspaceId, t.state, t.expiresAt), check('task_leases_fence_positive_chk', sql`${t.fencingToken} > 0`), check('task_leases_attempt_positive_chk', sql`${t.attemptNumber} > 0`), ], ); export const taskCheckpointsV1 = pgTable( 'task_checkpoints', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), taskId: uuid('task_id').notNull(), leaseId: uuid('lease_id').notNull(), fencingToken: bigint('fencing_token', { mode: 'bigint' }).notNull(), sequence: integer('sequence').notNull(), resumableSummary: text('resumable_summary').notNull(), contextUsagePercent: integer('context_usage_percent'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ foreignKey({ name: 'task_checkpoints_exact_lease_fence_fk', columns: [t.workspaceId, t.taskId, t.leaseId, t.fencingToken], foreignColumns: [ taskLeasesV1.workspaceId, taskLeasesV1.taskId, taskLeasesV1.id, taskLeasesV1.fencingToken, ], }).onDelete('restrict'), uniqueIndex('task_checkpoints_workspace_task_id_uidx').on(t.workspaceId, t.taskId, t.id), uniqueIndex('task_checkpoints_lease_sequence_uidx').on(t.workspaceId, t.leaseId, t.sequence), index('task_checkpoints_task_created_idx').on(t.workspaceId, t.taskId, t.createdAt), check('task_checkpoints_sequence_positive_chk', sql`${t.sequence} > 0`), check('task_checkpoints_fence_positive_chk', sql`${t.fencingToken} > 0`), check( 'task_checkpoints_context_percent_chk', sql`${t.contextUsagePercent} is null or (${t.contextUsagePercent} >= 0 and ${t.contextUsagePercent} <= 100)`, ), ], ); export const checkpointArtifactsV1 = pgTable( 'task_checkpoint_artifacts', { workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), taskId: uuid('task_id').notNull(), checkpointId: uuid('checkpoint_id').notNull(), artifactId: uuid('artifact_id').notNull(), }, (t) => [ primaryKey({ name: 'task_checkpoint_artifacts_pk', columns: [t.workspaceId, t.checkpointId, t.artifactId], }), foreignKey({ name: 'task_checkpoint_artifacts_checkpoint_fk', columns: [t.workspaceId, t.taskId, t.checkpointId], foreignColumns: [ taskCheckpointsV1.workspaceId, taskCheckpointsV1.taskId, taskCheckpointsV1.id, ], }).onDelete('restrict'), foreignKey({ name: 'task_checkpoint_artifacts_artifact_fk', columns: [t.workspaceId, t.artifactId], foreignColumns: [artifactsV1.workspaceId, artifactsV1.id], }).onDelete('restrict'), ], ); // ─── Approvals, evidence, outbox ───────────────────────────────────────────── export const approvalDecisionsV1 = pgTable( 'approval_decisions', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), taskId: uuid('task_id'), missionId: uuid('mission_id'), assignmentId: uuid('assignment_id'), gateType: text('gate_type').notNull(), requestedFromRole: specialistRoleEnum('requested_from_role'), decision: approvalDecisionEnum('decision').notNull().default('requested'), conditions: jsonb('conditions').notNull().$type>().default({}), actorKind: actorKindEnum('actor_kind'), actorId: text('actor_id'), policyRevision: text('policy_revision').notNull(), reason: text('reason'), requestedAt: timestamp('requested_at', { withTimezone: true }).notNull().defaultNow(), decidedAt: timestamp('decided_at', { withTimezone: true }), }, (t) => [ foreignKey({ name: 'approval_decisions_workspace_task_fk', columns: [t.workspaceId, t.taskId], foreignColumns: [tasksV1.workspaceId, tasksV1.id], }).onDelete('restrict'), foreignKey({ name: 'approval_decisions_workspace_mission_fk', columns: [t.workspaceId, t.missionId], foreignColumns: [missionsV1.workspaceId, missionsV1.id], }).onDelete('restrict'), foreignKey({ name: 'approval_decisions_workspace_assignment_fk', columns: [t.workspaceId, t.assignmentId], foreignColumns: [taskAssignmentsV1.workspaceId, taskAssignmentsV1.id], }).onDelete('restrict'), uniqueIndex('approval_decisions_workspace_id_uidx').on(t.workspaceId, t.id), index('approval_decisions_assignment_idx').on(t.workspaceId, t.assignmentId, t.decision), check( 'approval_decisions_one_target_chk', sql`num_nonnulls(${t.taskId}, ${t.missionId}, ${t.assignmentId}) = 1`, ), ], ); export const approvalDecisionArtifactsV1 = pgTable( 'approval_decision_artifacts', { workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), approvalDecisionId: uuid('approval_decision_id').notNull(), artifactId: uuid('artifact_id').notNull(), }, (t) => [ primaryKey({ name: 'approval_decision_artifacts_pk', columns: [t.workspaceId, t.approvalDecisionId, t.artifactId], }), foreignKey({ name: 'approval_decision_artifacts_decision_fk', columns: [t.workspaceId, t.approvalDecisionId], foreignColumns: [approvalDecisionsV1.workspaceId, approvalDecisionsV1.id], }).onDelete('restrict'), foreignKey({ name: 'approval_decision_artifacts_artifact_fk', columns: [t.workspaceId, t.artifactId], foreignColumns: [artifactsV1.workspaceId, artifactsV1.id], }).onDelete('restrict'), ], ); export const outboxEventsV1 = pgTable( 'outbox_events', { id: uuid('id').primaryKey().defaultRandom(), workspaceId: uuid('workspace_id') .notNull() .references(() => workspacesV1.id, { onDelete: 'restrict' }), aggregateType: text('aggregate_type').notNull(), aggregateId: uuid('aggregate_id').notNull(), aggregateRevision: integer('aggregate_revision').notNull(), eventType: text('event_type').notNull(), payload: jsonb('payload').notNull().$type>(), state: outboxStateEnum('state').notNull().default('pending'), attempts: integer('attempts').notNull().default(0), nextAttemptAt: timestamp('next_attempt_at', { withTimezone: true }), publishedAt: timestamp('published_at', { withTimezone: true }), lastError: text('last_error'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ uniqueIndex('outbox_events_aggregate_revision_type_uidx').on( t.workspaceId, t.aggregateType, t.aggregateId, t.aggregateRevision, t.eventType, ), index('outbox_events_state_next_attempt_idx').on(t.state, t.nextAttemptAt), check('outbox_events_revision_positive_chk', sql`${t.aggregateRevision} > 0`), check('outbox_events_attempts_nonnegative_chk', sql`${t.attempts} >= 0`), ], ); /** * Mandatory transaction/privilege rules frozen with this schema: * * 1. Every user principal/owner/proposer/decision actor must have ACTIVE * workspace_members membership; denials expose no foreign-ID existence. * 2. roleSet/roles JSON values are validated against specialist_role_v1 until * normalized role joins are introduced; the primary/assigned role is enum. * 3. Dependency cycles are rejected under a serialized recursive check. * 4. task_events is declared before change_proposals so migration DDL creates * task_events_workspace_id_uidx before both workspace-aware proposal event * FKs. Submission preallocates the proposal ID and, in one transaction, * inserts `change_proposal.submitted` with aggregate_type=change_proposal, * aggregate_id=proposal.id, previous_version=NULL, new_version=1, then the * proposal referencing that event. Missing or foreign-workspace events fail. * 5. change_proposals never mutate targets directly. Acceptance locks proposal * and target, proves fresh PG write health, checks expected version, invokes * the NORMAL typed command, and in that same transaction binds its emitted * event. The event workspace/aggregate type/aggregate ID must equal the * proposal workspace/target, causation_id must equal submittedAuditEventId, * and payload.changeProposalId must equal proposal.id; unrelated events fail. * 6. Lease acquisition locks task+assignment+approval+session, increments * tasks.fencing_counter atomically, and uses RETURNING bigint as the token. * 7. task_events, task_checkpoints, checkpoint/artifact evidence, approval * evidence, and immutable artifacts grant application roles INSERT/SELECT * only. Canonical parents are archived, not hard-deleted; all parent FKs use * RESTRICT. Purge requires an audited retention/break-glass procedure. * 8. Polymorphic external_links and change_proposals targets are validated in a * workspace-scoped transaction before insert; no existence oracle. * 9. Legacy fields remain in the unified Drizzle declaration throughout expand * and N-1. New Gateway commands never accept mission_tasks.status as a write * source; its concrete retirement is specified in SHARED-CONTRACT.md. */