spec: Invariant Management System — module boundaries, data models, CLI interfaces (v3.2.0) [AUTO-ARCH-2B] #8658

Closed
HAL9000 wants to merge 1 commits from spec/invariant-management-system-v3.2.0 into master
+128
View File
@@ -47095,3 +47095,131 @@ These architectural invariants must be maintained across all milestones:
8. **BDD tests**: All unit-level tests expressed as Behave/Gherkin scenarios. No xUnit-style tests.
9. **File size limit**: No source file exceeds 500 lines. Split into modules if approaching limit.
10. **Atomic commits**: One logical change per commit. No mixed concerns.
---
## Invariant Management System (v3.2.0)
### Overview
Invariants are user-defined constraints that must hold true throughout plan execution. They are enforced during the Strategize phase to constrain LLM decision-making. Users manage invariants via the CLI (`agents invariant add/list/remove`). Invariants are scoped to a project and optionally to specific plans.
### Module Boundaries
- **Module**: `cleveragents.invariants`
- **Layer**: Domain
- **Responsibilities**:
- CRUD operations for invariant definitions
- Enforcing invariants during Strategize phase
- Providing invariant context to the LLM prompt builder
- Tracking which invariants were applied to each decision
- **Public Interfaces**:
- `InvariantRepository` — CRUD for invariant entities (domain repository interface)
- `InvariantEnforcer` — validates proposed decisions against active invariants
- `InvariantContextBuilder` — builds invariant context strings for LLM prompts
- **Forbidden Dependencies**: Must not import from `cleveragents.cli` or `cleveragents.tui`
### Data Models
#### Invariant Entity
```python
@dataclass
class Invariant:
id: UUID
project_id: UUID
plan_id: Optional[UUID] # None = applies to all plans in project
name: str # Short human-readable name
description: str # Full invariant statement (natural language)
scope: Literal["global", "plan"]
enforcement_mode: Literal["hard", "soft"]
# hard = LLM must not violate; soft = LLM should prefer not to violate
created_at: datetime
created_by: str # User identifier
is_active: bool
metadata: dict
```
### Database Schema
```sql
CREATE TABLE invariants (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
plan_id UUID REFERENCES plans(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
scope VARCHAR(20) NOT NULL CHECK (scope IN ('global', 'plan')),
enforcement_mode VARCHAR(10) NOT NULL CHECK (enforcement_mode IN ('hard', 'soft')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
metadata JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX idx_invariants_project_id ON invariants(project_id);
CREATE INDEX idx_invariants_plan_id ON invariants(plan_id);
CREATE INDEX idx_invariants_active ON invariants(is_active) WHERE is_active = TRUE;
```
### CLI Interface Specification
#### `agents invariant add`
- Interactive or flag-based invariant creation
- Required flags: `--name <name>`, `--description <text>`
- Optional flags: `--plan <plan-id>` (scope to plan), `--mode hard|soft` (default: hard), `--project <project-id>` (default: current project)
- Output: Confirmation with assigned invariant ID
#### `agents invariant list`
- Lists all active invariants for the current project
- Optional flags: `--plan <plan-id>` (filter by plan), `--all` (include inactive), `--format table|json`
- Output: Table with columns: ID (short), Name, Scope, Mode, Created
#### `agents invariant remove <invariant-id>`
- Deactivates (soft-deletes) an invariant
- Requires confirmation unless `--yes` flag provided
- Output: Confirmation message
#### `agents invariant show <invariant-id>`
- Shows full details of a single invariant
- Output: Rich panel with all fields
### Enforcement Mechanism
During the Strategize phase, `InvariantEnforcer` is called before each LLM decision:
1. `InvariantContextBuilder.build(project_id, plan_id)` retrieves all active invariants and formats them as a structured prompt section
2. The invariant context is injected into the LLM system prompt
3. Hard invariants are prefixed with `MUST NOT:` in the prompt
4. Soft invariants are prefixed with `SHOULD PREFER NOT:` in the prompt
5. After the LLM responds, `InvariantEnforcer.validate(decision, invariants)` checks for obvious violations
6. If a hard invariant is violated, the decision is rejected and the LLM is re-prompted with explicit violation feedback
### Integration Points
| Integration | Direction | Description |
|---|---|---|
| Strategize Phase | Called by | `InvariantContextBuilder` called by prompt builder in strategize LangGraph node |
| Decision Recording | Calls | `InvariantEnforcer` passes applied invariant IDs to `DecisionRecorder` |
| Plan Correction Engine | Called by | When correcting a decision, active invariants at correction time are re-applied |
### Error Handling
- `InvariantNotFoundError(invariant_id)` — raised when invariant ID does not exist
- `InvariantViolationError(invariant_id, decision, reason)` — raised when a hard invariant is violated
- `InvariantConflictError(invariant_ids)` — raised when two invariants contradict each other
### Cross-Cutting Concerns
- **Logging**: Invariant enforcement logged at DEBUG level; violations logged at WARNING
- **Performance**: Invariant context building must complete within 10ms
- **Caching**: Active invariants for a project are cached per request (invalidated on add/remove)
---
**Automated by CleverAgents Bot**
Supervisor: Architecture | Agent: architecture-pool-supervisor
Worker: [AUTO-ARCH-2B]