forked from HAL9000/cleveragents-core
a0ac30d5de
- docs/reference/plan_cli.md: add note about legacy/v3 plan workflow mixing being disallowed (introduced in v3.8.0, issue #1577) - docs/reference/uko_runtime.md: document v3.8.0 provenance tracking (sourceResource, validFrom, isCurrent on typed triples) and revision chain for temporal queries across indexing runs (issue #891) - docs/reference/decision_correction.md: add CorrectionAttemptRecord section documenting the correction_attempts table, state lifecycle, repository API, and usage examples (issue #920) - docs/modules/uko-provenance.md: new module guide for UKO provenance tracking covering purpose, how provenance is attached, revision chain mechanics, query patterns, persistence format, BDD coverage, and gotchas
195 lines
6.6 KiB
Markdown
195 lines
6.6 KiB
Markdown
# Decision Correction Reference
|
||
|
||
## Overview
|
||
|
||
The correction subsystem allows operators to modify a plan's decision tree
|
||
after execution by either **reverting** a subtree of decisions or
|
||
**appending** new decisions as a child plan.
|
||
|
||
## Correction Modes
|
||
|
||
### Revert
|
||
|
||
Invalidates the targeted decision and every descendant reachable via
|
||
BFS traversal. Associated artifacts are archived and affected child
|
||
plans are rolled back.
|
||
|
||
```text
|
||
┌─── D1 (target) ◄── revert starts here
|
||
│ │
|
||
│ ┌──┴──┐
|
||
│ D2 D3 ← all invalidated
|
||
│ │
|
||
│ D4 ← also invalidated
|
||
```
|
||
|
||
### Append
|
||
|
||
Preserves the original decision and spawns a **new child plan** rooted
|
||
at the target node. The child plan carries the operator's guidance and
|
||
produces additional decisions without disturbing the existing tree.
|
||
|
||
```text
|
||
D1 (target)
|
||
│
|
||
┌──┴──────────┐
|
||
D2 (original) CP-new ← child plan appended
|
||
```
|
||
|
||
## Impact Analysis — BFS Algorithm
|
||
|
||
Impact analysis uses breadth-first search over the decision tree's
|
||
adjacency list (parent → children mapping):
|
||
|
||
```python
|
||
from collections import deque
|
||
|
||
def compute_affected_subtree(target_id, tree):
|
||
affected = []
|
||
queue = deque([target_id])
|
||
while queue:
|
||
node = queue.popleft()
|
||
affected.append(node)
|
||
queue.extend(tree.get(node, []))
|
||
return affected
|
||
```
|
||
|
||
### Risk Classification
|
||
|
||
| Affected Count | Risk Level |
|
||
|----------------|------------|
|
||
| ≤ 3 | low |
|
||
| 4 – 10 | medium |
|
||
| > 10 | high |
|
||
|
||
## Dry-Run Output
|
||
|
||
A dry-run report contains:
|
||
|
||
- **impact** — Full `CorrectionImpact` with affected decisions, files,
|
||
child plans, estimated cost, and risk level.
|
||
- **decisions_to_invalidate** — Decision IDs that *would* be marked
|
||
invalid (revert mode only).
|
||
- **child_plans_to_rollback** — Child plans that *would* be rolled back.
|
||
- **estimated_recompute_time_seconds** — Wall-clock estimate.
|
||
- **warnings** — Human-readable cautions (e.g. high risk).
|
||
|
||
## Status Lifecycle
|
||
|
||
```text
|
||
PENDING → ANALYZING → EXECUTING → APPLIED
|
||
→ FAILED
|
||
PENDING → CANCELLED
|
||
ANALYZING → CANCELLED
|
||
```
|
||
|
||
## Service API
|
||
|
||
| Method | Description |
|
||
|-----------------------------|-----------------------------------------------|
|
||
| `request_correction()` | Create a new correction request |
|
||
| `analyze_impact()` | BFS impact analysis on the decision tree |
|
||
| `generate_dry_run_report()` | Full report without side effects |
|
||
| `execute_revert()` | Invalidate subtree + archive artifacts |
|
||
| `execute_append()` | Spawn child plan preserving original decision |
|
||
| `execute_correction()` | Dispatch to revert or append based on mode |
|
||
| `get_correction()` | Retrieve a correction by ID |
|
||
| `list_corrections()` | List corrections (optional plan_id filter) |
|
||
| `list_attempts()` | List execution attempts for a correction |
|
||
| `cancel_correction()` | Cancel a pending/analyzing correction |
|
||
|
||
---
|
||
|
||
## Correction Attempts (v3.8.0+)
|
||
|
||
Each execution of a correction is tracked as a `CorrectionAttemptRecord`.
|
||
Multiple attempts may exist for a single correction (e.g. if a first attempt
|
||
fails and the operator retries).
|
||
|
||
### `CorrectionAttemptRecord`
|
||
|
||
**Module:** `cleveragents.domain.models.core.correction_attempt`
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `id` | `str` | ULID of this attempt |
|
||
| `correction_id` | `str` | Parent correction ULID |
|
||
| `plan_id` | `str` | Plan this correction targets |
|
||
| `original_decision_id` | `str` | Decision being corrected |
|
||
| `new_decision_id` | `str \| None` | Replacement decision (set on success) |
|
||
| `mode` | `CorrectionMode` | `revert` or `append` |
|
||
| `state` | `CorrectionAttemptState` | Current state (see lifecycle below) |
|
||
| `guidance` | `str` | Operator guidance text (1–10,000 chars) |
|
||
| `created_at` | `datetime` | UTC creation timestamp |
|
||
| `completed_at` | `datetime \| None` | UTC completion timestamp (terminal states only) |
|
||
| `archived_artifacts_path` | `str \| None` | Path to archived artifacts (revert mode) |
|
||
|
||
### `CorrectionAttemptState` Lifecycle
|
||
|
||
```text
|
||
pending → executing → complete
|
||
→ failed
|
||
```
|
||
|
||
Valid transitions:
|
||
|
||
| From | To | Notes |
|
||
|------|----|-------|
|
||
| `pending` | `executing` | Attempt begins execution |
|
||
| `executing` | `complete` | Correction applied successfully |
|
||
| `executing` | `failed` | Correction failed; `completed_at` auto-set |
|
||
|
||
Terminal states (`complete`, `failed`) cannot transition further.
|
||
Self-transitions are rejected.
|
||
|
||
### `CorrectionAttemptRepository`
|
||
|
||
**Module:** `cleveragents.infrastructure.repositories.correction_attempt`
|
||
|
||
| Method | Description |
|
||
|--------|-------------|
|
||
| `create(record)` | Persist a new attempt; raises `DatabaseError` on FK violation |
|
||
| `get(attempt_id)` | Retrieve by ULID; raises `ResourceNotFoundError` if absent |
|
||
| `list_by_plan(plan_id)` | List all attempts for a plan (ordered by `created_at`) |
|
||
| `update_state(attempt_id, new_state, *, new_decision_id, completed_at, archived_artifacts_path)` | Transition state; auto-sets `completed_at` for terminal states |
|
||
| `delete(attempt_id)` | Hard-delete an attempt record |
|
||
|
||
### Usage Example
|
||
|
||
```python
|
||
from cleveragents.domain.models.core.correction_attempt import (
|
||
CorrectionAttemptRecord,
|
||
CorrectionAttemptState,
|
||
)
|
||
from cleveragents.domain.models.core.correction import CorrectionMode
|
||
|
||
attempt = CorrectionAttemptRecord(
|
||
id="01HXYZ...",
|
||
correction_id="01HABC...",
|
||
plan_id="01HDEF...",
|
||
original_decision_id="01HGHI...",
|
||
mode=CorrectionMode.REVERT,
|
||
state=CorrectionAttemptState.PENDING,
|
||
guidance="Revert the database migration decision and retry with a safer approach.",
|
||
)
|
||
|
||
# Transition to executing
|
||
repo.update_state(attempt.id, CorrectionAttemptState.EXECUTING)
|
||
|
||
# Complete the attempt
|
||
repo.update_state(
|
||
attempt.id,
|
||
CorrectionAttemptState.COMPLETE,
|
||
new_decision_id="01HJKL...",
|
||
archived_artifacts_path="/var/cleveragents/archives/01HXYZ",
|
||
)
|
||
```
|
||
|
||
### Database Schema
|
||
|
||
The `correction_attempts` table uses `RESTRICT` foreign keys for both
|
||
`original_decision_id` and `new_decision_id`, preserving the correction
|
||
audit trail when decisions are cleaned up. The ORM-level
|
||
`cascade="all, delete-orphan"` on `LifecyclePlanModel` ensures that
|
||
deleting a plan cascades to its correction attempts.
|