# Database Schema Reference This document describes the spec-aligned database tables created by the A5.alpha migrations (`a5_003_spec_aligned_actions` and `a5_004_spec_aligned_plans`). These tables replace the legacy `actions_v3` and `lifecycle_plans` tables with a normalized schema using namespaced names as action identity and child tables for arguments, invariants, and project links. ## Migration Chain | Revision | Description | |---|---| | `a5_003_spec_aligned_actions` | Drops `lifecycle_plans` and `actions_v3`; creates `actions`, `action_invariants`, `action_arguments` | | `a5_004_spec_aligned_plans` | Creates `v3_plans`, `plan_projects`, `plan_arguments`, `plan_invariants` | --- ## Table: `actions` The primary action definition table. Each row represents a reusable action template identified by its namespaced name. **Primary Key:** `namespaced_name` | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `namespaced_name` | String(255) | No | -- | PK. Full namespaced name (e.g. `local/code-review`) | | `namespace` | String(100) | No | -- | Extracted namespace component | | `name` | String(150) | No | -- | Extracted short name component | | `description` | Text | No | -- | Short description | | `long_description` | Text | Yes | NULL | Detailed description | | `definition_of_done` | Text | No | -- | Explicit testable completion criteria | | `strategy_actor` | String(255) | No | -- | Actor for Strategize phase | | `execution_actor` | String(255) | No | -- | Actor for Execute phase | | `review_actor` | String(255) | Yes | NULL | Actor for reviewing results | | `apply_actor` | String(255) | Yes | NULL | Actor for Apply phase | | `estimation_actor` | String(255) | Yes | NULL | Actor for cost estimation | | `invariant_actor` | String(255) | Yes | NULL | Actor for invariant reconciliation | | `automation_profile` | String(255) | Yes | NULL | Default automation profile name | | `reusable` | Boolean | No | `1` | Whether action persists after use | | `read_only` | Boolean | No | `0` | Read-only mode | | `inputs_schema_json` | Text | Yes | NULL | JSON Schema dict for input validation | | `state` | String(20) | No | `'available'` | `available` or `archived` | | `created_by` | String(255) | Yes | NULL | Creator identifier | | `tags_json` | Text | No | `'[]'` | JSON array of tag strings | | `created_at` | String(30) | No | -- | ISO-8601 creation timestamp | | `updated_at` | String(30) | No | -- | ISO-8601 last-update timestamp | **Check Constraints:** - `ck_actions_state`: `state IN ('available', 'archived')` **Indexes:** - `ix_actions_namespace` on `namespace` - `ix_actions_state` on `state` - `ix_actions_name` on `name` --- ## Table: `action_invariants` Ordered invariant constraints attached to an action. Cascades on action delete. **Primary Key:** `id` (auto-increment) | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `id` | Integer | No | auto | Surrogate PK | | `action_name` | String(255) | No | -- | FK to `actions.namespaced_name` (CASCADE) | | `invariant_text` | Text | No | -- | The invariant constraint text | | `position` | Integer | No | -- | Display ordering | | `created_at` | String(30) | No | -- | ISO-8601 timestamp | **Foreign Keys:** - `fk_action_invariants_action`: `action_name` -> `actions.namespaced_name` ON DELETE CASCADE **Indexes:** - `ix_action_invariants_action` on `action_name` - `ix_action_invariants_position` on `(action_name, position)` --- ## Table: `action_arguments` Typed argument definitions for an action. Cascades on action delete. **Primary Key:** `id` (auto-increment) | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `id` | Integer | No | auto | Surrogate PK | | `action_name` | String(255) | No | -- | FK to `actions.namespaced_name` (CASCADE) | | `name` | String(100) | No | -- | Argument identifier | | `arg_type` | String(20) | No | `'string'` | `string`, `integer`, `float`, `boolean`, `list` | | `requirement` | String(20) | No | `'required'` | `required` or `optional` | | `description` | Text | Yes | NULL | Human-readable description | | `default_value_json` | Text | Yes | NULL | JSON-encoded default value | | `min_value` | Float | Yes | NULL | Min bound for numeric types | | `max_value` | Float | Yes | NULL | Max bound for numeric types | | `validation_pattern` | String(500) | Yes | NULL | Regex pattern for string validation | | `position` | Integer | No | -- | Display ordering | **Foreign Keys:** - `fk_action_arguments_action`: `action_name` -> `actions.namespaced_name` ON DELETE CASCADE **Unique Constraints:** - `uq_action_arguments_name` on `(action_name, name)` **Check Constraints:** - `ck_action_arguments_type`: `arg_type IN ('string', 'integer', 'float', 'boolean', 'list')` - `ck_action_arguments_requirement`: `requirement IN ('required', 'optional')` **Indexes:** - `ix_action_arguments_action` on `action_name` - `ix_action_arguments_position` on `(action_name, position)` --- ## Table: `v3_plans` The primary plan table. Each row is a plan instance created from an action, identified by a ULID. Plans reference actions by namespaced name and support hierarchical subplan trees via self-referencing foreign keys. **Primary Key:** `plan_id` (ULID, String(26)) | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `plan_id` | String(26) | No | -- | ULID primary key | | `parent_plan_id` | String(26) | Yes | NULL | FK to `v3_plans.plan_id` (SET NULL) | | `root_plan_id` | String(26) | Yes | NULL | FK to `v3_plans.plan_id` (SET NULL) | | `action_name` | String(255) | No | -- | FK to `actions.namespaced_name` (RESTRICT) | | `namespaced_name` | String(255) | No | -- | Plan's own namespaced name | | `namespace` | String(100) | No | -- | Extracted namespace | | `phase` | String(20) | No | `'strategize'` | Current lifecycle phase | | `processing_state` | String(20) | No | `'queued'` | Current processing state | | `attempt` | Integer | No | `1` | Retry attempt counter | | `description` | Text | No | -- | Rendered description (from action template + args) | | `definition_of_done` | Text | Yes | NULL | Rendered completion criteria | | `strategy_actor` | String(255) | Yes | NULL | Strategize phase actor | | `execution_actor` | String(255) | Yes | NULL | Execute phase actor | | `review_actor` | String(255) | Yes | NULL | Review actor | | `apply_actor` | String(255) | Yes | NULL | Apply phase actor | | `estimation_actor` | String(255) | Yes | NULL | Estimation actor | | `invariant_actor` | String(255) | Yes | NULL | Invariant reconciliation actor | | `automation_profile` | String(255) | Yes | NULL | Resolved automation profile name | | `automation_level` | String(30) | No | `'manual'` | `manual`, `review_before_apply`, `full_automation` | | `reusable` | Boolean | No | `1` | Copied from action | | `read_only` | Boolean | No | `0` | Copied from action | | `inputs_schema_json` | Text | Yes | NULL | JSON Schema dict (copied from action) | | `changeset_id` | String(26) | Yes | NULL | Changeset ULID from Execute phase | | `sandbox_refs_json` | Text | Yes | NULL | JSON sandbox references | | `validation_summary_json` | Text | Yes | NULL | JSON validation summary | | `decision_root_id` | String(26) | Yes | NULL | Root decision tree ULID | | `error_message` | Text | Yes | NULL | Error message if errored | | `error_details_json` | Text | Yes | NULL | JSON error details dict | | `cost_estimate_usd` | Float | Yes | NULL | Estimated cost in USD | | `cost_actual_usd` | Float | Yes | NULL | Actual cost in USD | | `token_count_input` | Integer | Yes | `0` | Input token count | | `token_count_output` | Integer | Yes | `0` | Output token count | | `created_by` | String(255) | Yes | NULL | Creator identifier | | `tags_json` | Text | No | `'[]'` | JSON array of tag strings | | `created_at` | String(30) | No | -- | ISO-8601 creation timestamp | | `updated_at` | String(30) | No | -- | ISO-8601 last-update timestamp | | `completed_at` | String(30) | Yes | NULL | ISO-8601 completion timestamp | | `strategize_started_at` | String(30) | Yes | NULL | Strategize phase start | | `strategize_completed_at` | String(30) | Yes | NULL | Strategize phase end | | `execute_started_at` | String(30) | Yes | NULL | Execute phase start | | `execute_completed_at` | String(30) | Yes | NULL | Execute phase end | | `apply_started_at` | String(30) | Yes | NULL | Apply phase start | | `applied_at` | String(30) | Yes | NULL | Terminal apply timestamp | **Foreign Keys:** - `fk_v3_plans_parent`: `parent_plan_id` -> `v3_plans.plan_id` ON DELETE SET NULL - `fk_v3_plans_root`: `root_plan_id` -> `v3_plans.plan_id` ON DELETE SET NULL - `fk_v3_plans_action`: `action_name` -> `actions.namespaced_name` ON DELETE RESTRICT **Check Constraints:** - `ck_v3_plans_phase`: `phase IN ('strategize', 'execute', 'apply', 'applied')` - `ck_v3_plans_state`: `processing_state IN ('queued', 'processing', 'errored', 'complete', 'cancelled')` - `ck_v3_plans_automation`: `automation_level IN ('manual', 'review_before_apply', 'full_automation')` **Indexes:** - `ix_v3_plans_phase` on `phase` - `ix_v3_plans_state` on `processing_state` - `ix_v3_plans_parent` on `parent_plan_id` - `ix_v3_plans_root` on `root_plan_id` - `ix_v3_plans_created` on `created_at` - `ix_v3_plans_action` on `action_name` - `ix_v3_plans_namespace` on `namespace` - `ix_v3_plans_phase_state` on `(phase, processing_state)` (composite) --- ## Table: `plan_projects` Links plans to target projects. Composite primary key. Cascades on plan delete. **Primary Key:** `(plan_id, project_name)` | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `plan_id` | String(26) | No | -- | FK to `v3_plans.plan_id` (CASCADE) | | `project_name` | String(255) | No | -- | Target project namespaced name | | `alias` | String(100) | Yes | NULL | Short alias for plan context | | `read_only` | Boolean | No | `0` | Whether project is read-only for this plan | | `created_at` | String(30) | No | -- | ISO-8601 timestamp | **Foreign Keys:** - `fk_plan_projects_plan`: `plan_id` -> `v3_plans.plan_id` ON DELETE CASCADE **Indexes:** - `ix_plan_projects_project` on `project_name` --- ## Table: `plan_arguments` Resolved argument values for a plan instance. Cascades on plan delete. **Primary Key:** `id` (auto-increment) | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `id` | Integer | No | auto | Surrogate PK | | `plan_id` | String(26) | No | -- | FK to `v3_plans.plan_id` (CASCADE) | | `name` | String(100) | No | -- | Argument name | | `value_json` | Text | Yes | NULL | JSON-encoded argument value | | `value_type` | String(20) | No | `'string'` | Value type hint | | `position` | Integer | No | -- | Display ordering | **Foreign Keys:** - `fk_plan_arguments_plan`: `plan_id` -> `v3_plans.plan_id` ON DELETE CASCADE **Unique Constraints:** - `uq_plan_arguments_name` on `(plan_id, name)` **Indexes:** - `ix_plan_arguments_plan` on `plan_id` - `ix_plan_arguments_position` on `(plan_id, position)` --- ## Table: `plan_invariants` Plan-level invariant constraints with scope provenance. Cascades on plan delete. **Primary Key:** `id` (auto-increment) | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `id` | Integer | No | auto | Surrogate PK | | `plan_id` | String(26) | No | -- | FK to `v3_plans.plan_id` (CASCADE) | | `invariant_text` | Text | No | -- | The invariant constraint text | | `source_scope` | String(20) | No | `'plan'` | Origin scope: `global`, `project`, `action`, `plan` | | `source_name` | String(255) | Yes | NULL | Name of the source entity | | `position` | Integer | No | -- | Display ordering | | `created_at` | String(30) | No | -- | ISO-8601 timestamp | **Foreign Keys:** - `fk_plan_invariants_plan`: `plan_id` -> `v3_plans.plan_id` ON DELETE CASCADE **Unique Constraints:** - `uq_plan_invariants_text` on `(plan_id, invariant_text)` **Check Constraints:** - `ck_plan_invariants_scope`: `source_scope IN ('global', 'project', 'action', 'plan')` **Indexes:** - `ix_plan_invariants_plan` on `plan_id` - `ix_plan_invariants_position` on `(plan_id, position)` --- ## Entity Relationship Diagram ``` actions (PK: namespaced_name) |-- 1:N --> action_invariants (FK: action_name) |-- 1:N --> action_arguments (FK: action_name) |-- 1:N --> v3_plans (FK: action_name, RESTRICT) v3_plans (PK: plan_id) |-- self --> parent_plan_id (SET NULL) |-- self --> root_plan_id (SET NULL) |-- 1:N --> plan_projects (FK: plan_id, CASCADE) |-- 1:N --> plan_arguments (FK: plan_id, CASCADE) |-- 1:N --> plan_invariants (FK: plan_id, CASCADE) ``` ## Source Location - Migration (actions): `alembic/versions/a5_003_spec_aligned_actions.py` - Migration (plans): `alembic/versions/a5_004_spec_aligned_plans.py` - ORM models: `src/cleveragents/infrastructure/database/models.py` - Repositories: `src/cleveragents/infrastructure/database/repositories.py`