# 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 | `'action'` | 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'` | **Legacy.** `manual`, `review_before_apply`, `full_automation`. Retained for backward compatibility; prefer `automation_profile`. | | `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 ('action', 'strategize', 'execute', 'apply')` - `ck_v3_plans_state`: `processing_state IN ('queued', 'processing', 'errored', 'complete', 'cancelled', 'applied', 'constrained')` - `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)` --- --- ## Table: `resource_types` Schema-level definitions that constrain categories of resources. Built-in types (e.g. `builtin/git-checkout`) are registered at startup; custom types are loaded from YAML configuration files. **Primary Key:** `name` | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `name` | String(255) | No | -- | PK. Namespaced type name (e.g. `builtin/git-checkout`) | | `namespace` | String(100) | No | -- | Extracted namespace component | | `description` | Text | Yes | NULL | Human-readable description | | `resource_kind` | String(20) | No | -- | `physical` or `virtual` | | `sandbox_strategy` | String(50) | Yes | NULL | Default sandbox strategy for instances | | `user_addable` | Boolean | No | `0` | Whether users can add instances via CLI | | `handler_ref` | String(500) | Yes | NULL | Handler implementation reference | | `args_schema_json` | Text | Yes | NULL | JSON: CLI argument definitions | | `allowed_parent_types_json` | Text | Yes | NULL | JSON: allowed parent type names | | `allowed_child_types_json` | Text | Yes | NULL | JSON: allowed child type definitions | | `auto_discover_json` | Text | Yes | NULL | JSON: auto-discovery configuration | | `capabilities_json` | Text | Yes | NULL | JSON: capability flags | | `equivalence_json` | Text | Yes | NULL | JSON: equivalence criteria (virtual types) | | `source` | String(500) | Yes | NULL | `builtin` or path to YAML config | | `created_at` | String(30) | No | -- | ISO-8601 creation timestamp | | `updated_at` | String(30) | No | -- | ISO-8601 last-update timestamp | **Check Constraints:** - `ck_resource_types_kind`: `resource_kind IN ('physical', 'virtual')` **Indexes:** - `ix_resource_types_namespace` on `namespace` - `ix_resource_types_kind` on `resource_kind` - `ix_resource_types_user_addable` on `user_addable` --- ## Table: `resources` Independently registered resources representing anything that can be read, written, or queried. Each resource has a ULID primary key and an optional namespaced name (auto-discovered children have no name). **Primary Key:** `resource_id` (ULID, String(26)) | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `resource_id` | String(26) | No | -- | ULID primary key | | `namespaced_name` | String(255) | Yes | NULL | Optional namespaced name (unique when non-null) | | `namespace` | String(100) | Yes | NULL | Extracted namespace component | | `type_name` | String(255) | No | -- | FK to `resource_types.name` (RESTRICT) | | `resource_kind` | String(20) | No | -- | `physical` or `virtual` (denormalized from type) | | `location` | Text | Yes | NULL | Physical location (path, URL, etc.) | | `description` | Text | Yes | NULL | Human-readable description | | `read_only` | Boolean | No | `0` | Read-only flag | | `auto_discovered` | Boolean | No | `0` | Whether auto-discovered (not user-registered) | | `sandbox_strategy` | String(50) | Yes | NULL | Per-resource sandbox strategy override | | `content_hash` | String(128) | Yes | NULL | Content hash for equivalence tracking | | `properties_json` | Text | Yes | NULL | JSON: type-specific properties | | `metadata_json` | Text | Yes | NULL | JSON: type-specific metadata | | `created_at` | String(30) | No | -- | ISO-8601 creation timestamp | | `updated_at` | String(30) | No | -- | ISO-8601 last-update timestamp | **Foreign Keys:** - `fk_resources_type`: `type_name` -> `resource_types.name` ON DELETE RESTRICT **Check Constraints:** - `ck_resources_kind`: `resource_kind IN ('physical', 'virtual')` **Indexes:** - `ix_resources_name` on `namespaced_name` (unique, partial - non-null only) - `ix_resources_namespace` on `namespace` - `ix_resources_type` on `type_name` - `ix_resources_kind` on `resource_kind` - `ix_resources_content_hash` on `content_hash` --- ## Table: `resource_edges` DAG edges representing parent-child relationships between resources. A resource can have multiple parents and multiple children, subject to type constraints. **Primary Key:** `(parent_id, child_id)` (composite) | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `parent_id` | String(26) | No | -- | FK to `resources.resource_id` (CASCADE) | | `child_id` | String(26) | No | -- | FK to `resources.resource_id` (CASCADE) | | `link_type` | String(30) | No | `'contains'` | `contains`, `references`, or `derived_from` | | `auto_discovered` | Boolean | No | `0` | Whether auto-discovered (vs manually linked) | | `created_at` | String(30) | No | -- | ISO-8601 timestamp | **Foreign Keys:** - `fk_resource_edges_parent`: `parent_id` -> `resources.resource_id` ON DELETE CASCADE - `fk_resource_edges_child`: `child_id` -> `resources.resource_id` ON DELETE CASCADE **Check Constraints:** - `ck_resource_edges_no_self_loop`: `parent_id != child_id` - `ck_resource_edges_link_type`: `link_type IN ('contains', 'references', 'derived_from')` **Indexes:** - `ix_resource_edges_child` on `child_id` - `ix_resource_edges_link_type` on `link_type` --- ## 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) resource_types (PK: name) |-- 1:N --> resources (FK: type_name, RESTRICT) resources (PK: resource_id) |-- N:M --> resource_edges (FK: parent_id/child_id, CASCADE) |-- 1:N --> project_resource_links (FK: resource_id, RESTRICT) ns_projects (PK: namespaced_name) |-- 1:N --> project_resource_links (FK: project_name, CASCADE) ``` --- ## Table: `ns_projects` Spec-aligned project definitions identified solely by their namespaced name (e.g., `local/my-project`). Projects store invariants, automation profile, context policy, and tags as JSON text fields. **Primary Key:** `namespaced_name` | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `namespaced_name` | String(255) | No | -- | PK. Full namespaced name (e.g. `local/my-project`) | | `namespace` | String(100) | No | -- | Extracted namespace component | | `description` | Text | Yes | NULL | Human-readable description | | `invariants_json` | Text | Yes | NULL | JSON list of invariant strings | | `automation_profile` | String(255) | Yes | NULL | Default automation profile name | | `invariant_actor` | String(255) | Yes | NULL | Actor for invariant reconciliation | | `context_policy_json` | Text | Yes | NULL | JSON context policy configuration | | `tags_json` | Text | No | `'[]'` | JSON array of tag strings | | `created_by` | String(255) | Yes | NULL | Creator identifier | | `created_at` | String(30) | No | -- | ISO-8601 creation timestamp | | `updated_at` | String(30) | No | -- | ISO-8601 last-update timestamp | **Indexes:** - `ix_ns_projects_namespace` on `namespace` --- ## Table: `project_resource_links` Many-to-many links between projects and resources with optional alias and read-only override. Each link has a ULID primary key. **Primary Key:** `link_id` (ULID, String(26)) | Column | Type | Nullable | Default | Description | |---|---|---|---|---| | `link_id` | String(26) | No | -- | ULID primary key | | `project_name` | String(255) | No | -- | FK to `projects.namespaced_name` (CASCADE) | | `resource_id` | String(26) | No | -- | FK to `resources.resource_id` (RESTRICT) | | `alias` | String(255) | Yes | NULL | Optional short name within the project | | `read_only` | Boolean | No | `0` | Read-only override | | `created_at` | String(30) | No | -- | ISO-8601 timestamp | **Foreign Keys:** - `fk_project_resource_links_project`: `project_name` -> `ns_projects.namespaced_name` ON DELETE CASCADE - `fk_project_resource_links_resource`: `resource_id` -> `resources.resource_id` ON DELETE RESTRICT **Unique Constraints:** - `uq_project_resource_links_project_resource` on `(project_name, resource_id)` **Indexes:** - `ix_project_resource_links_project_name` on `project_name` - `ix_project_resource_links_resource_id` on `resource_id` - `ix_project_resource_links_project_alias` on `(project_name, alias)` --- ## 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` | | `b1_001_resource_registry` | Creates `resource_types`, `resources`, `resource_edges` | | `a5_005_rebaseline_plan_phases` | Rebaselines `v3_plans` phase/state CHECK constraints: adds `action` phase, removes `applied` phase, adds `applied`/`constrained` processing states, changes default phase to `action` | | `b0_001_projects` | Creates spec-aligned `ns_projects`, `project_resource_links` | ## Source Location - Migration (actions): `alembic/versions/a5_003_spec_aligned_actions.py` - Migration (plans): `alembic/versions/a5_004_spec_aligned_plans.py` - Migration (resources): `alembic/versions/b1_001_resource_registry_tables.py` - Migration (projects): `alembic/versions/b0_001_projects.py` - ORM models: `src/cleveragents/infrastructure/database/models.py` - Repositories: `src/cleveragents/infrastructure/database/repositories.py`