feat(02-01): define task hierarchy schema

Schema defines 5 tables with proper foreign key relationships:
- initiatives: Top-level projects (active/completed/archived)
- phases: Major milestones within initiatives
- plans: Groups of related tasks within phases
- tasks: Individual work items with type, priority, status, order
- task_dependencies: Dependency relationships between tasks

All tables have cascade deletes and Unix timestamp fields.
Types exported: Initiative, Phase, Plan, Task, TaskDependency + New* variants.
Relations exported for Drizzle relational queries.
This commit is contained in:
Lukas May
2026-01-30 14:24:41 +01:00
parent d7e4649e47
commit b492f64348

View File

@@ -7,9 +7,164 @@
* - Plan: Group of related tasks within phase
* - Task: Individual work item
*
* Schema will be defined in Task 3.
* Plus a task_dependencies table for task dependency relationships.
*/
// Placeholder export to allow index.ts to compile
// Full schema defined in Task 3
export {};
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
import { relations, type InferInsertModel, type InferSelectModel } from 'drizzle-orm';
// ============================================================================
// INITIATIVES
// ============================================================================
export const initiatives = sqliteTable('initiatives', {
id: text('id').primaryKey(),
name: text('name').notNull(),
description: text('description'),
status: text('status', { enum: ['active', 'completed', 'archived'] })
.notNull()
.default('active'),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
export const initiativesRelations = relations(initiatives, ({ many }) => ({
phases: many(phases),
}));
export type Initiative = InferSelectModel<typeof initiatives>;
export type NewInitiative = InferInsertModel<typeof initiatives>;
// ============================================================================
// PHASES
// ============================================================================
export const phases = sqliteTable('phases', {
id: text('id').primaryKey(),
initiativeId: text('initiative_id')
.notNull()
.references(() => initiatives.id, { onDelete: 'cascade' }),
number: integer('number').notNull(),
name: text('name').notNull(),
description: text('description'),
status: text('status', { enum: ['pending', 'in_progress', 'completed'] })
.notNull()
.default('pending'),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
export const phasesRelations = relations(phases, ({ one, many }) => ({
initiative: one(initiatives, {
fields: [phases.initiativeId],
references: [initiatives.id],
}),
plans: many(plans),
}));
export type Phase = InferSelectModel<typeof phases>;
export type NewPhase = InferInsertModel<typeof phases>;
// ============================================================================
// PLANS
// ============================================================================
export const plans = sqliteTable('plans', {
id: text('id').primaryKey(),
phaseId: text('phase_id')
.notNull()
.references(() => phases.id, { onDelete: 'cascade' }),
number: integer('number').notNull(),
name: text('name').notNull(),
description: text('description'),
status: text('status', { enum: ['pending', 'in_progress', 'completed'] })
.notNull()
.default('pending'),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
export const plansRelations = relations(plans, ({ one, many }) => ({
phase: one(phases, {
fields: [plans.phaseId],
references: [phases.id],
}),
tasks: many(tasks),
}));
export type Plan = InferSelectModel<typeof plans>;
export type NewPlan = InferInsertModel<typeof plans>;
// ============================================================================
// TASKS
// ============================================================================
export const tasks = sqliteTable('tasks', {
id: text('id').primaryKey(),
planId: text('plan_id')
.notNull()
.references(() => plans.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
description: text('description'),
type: text('type', {
enum: ['auto', 'checkpoint:human-verify', 'checkpoint:decision', 'checkpoint:human-action'],
})
.notNull()
.default('auto'),
priority: text('priority', { enum: ['low', 'medium', 'high'] })
.notNull()
.default('medium'),
status: text('status', {
enum: ['pending_approval', 'pending', 'in_progress', 'completed', 'blocked'],
})
.notNull()
.default('pending'),
order: integer('order').notNull().default(0),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
export const tasksRelations = relations(tasks, ({ one, many }) => ({
plan: one(plans, {
fields: [tasks.planId],
references: [plans.id],
}),
// Dependencies: tasks this task depends on
dependsOn: many(taskDependencies, { relationName: 'dependentTask' }),
// Dependents: tasks that depend on this task
dependents: many(taskDependencies, { relationName: 'dependencyTask' }),
}));
export type Task = InferSelectModel<typeof tasks>;
export type NewTask = InferInsertModel<typeof tasks>;
// ============================================================================
// TASK DEPENDENCIES
// ============================================================================
export const taskDependencies = sqliteTable('task_dependencies', {
id: text('id').primaryKey(),
taskId: text('task_id')
.notNull()
.references(() => tasks.id, { onDelete: 'cascade' }),
dependsOnTaskId: text('depends_on_task_id')
.notNull()
.references(() => tasks.id, { onDelete: 'cascade' }),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
});
export const taskDependenciesRelations = relations(taskDependencies, ({ one }) => ({
task: one(tasks, {
fields: [taskDependencies.taskId],
references: [tasks.id],
relationName: 'dependentTask',
}),
dependsOnTask: one(tasks, {
fields: [taskDependencies.dependsOnTaskId],
references: [tasks.id],
relationName: 'dependencyTask',
}),
}));
export type TaskDependency = InferSelectModel<typeof taskDependencies>;
export type NewTaskDependency = InferInsertModel<typeof taskDependencies>;