d70a5d84ae
ISSUES CLOSED: #186
215 lines
6.6 KiB
Markdown
215 lines
6.6 KiB
Markdown
# Plan Diff & Apply Reference
|
|
|
|
This document describes the diff review and apply integration features
|
|
added in D0b.apply.
|
|
|
|
## CLI Commands
|
|
|
|
### `agents plan diff <plan_id>`
|
|
|
|
Show the ChangeSet produced during Execute as a unified diff, grouped
|
|
by resource path.
|
|
|
|
**Options:**
|
|
|
|
| Flag | Description |
|
|
|------|-------------|
|
|
| `--format`, `-f` | Output format: `rich` (default), `plain`, `json`, `yaml` |
|
|
|
|
**Examples:**
|
|
|
|
```bash
|
|
# Rich output with coloured operation labels
|
|
agents plan diff 01HXYZ...
|
|
|
|
# Plain unified-diff output
|
|
agents plan diff 01HXYZ... --format plain
|
|
|
|
# Machine-readable JSON
|
|
agents plan diff 01HXYZ... --format json
|
|
```
|
|
|
|
**Output fields (JSON/YAML):**
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `changeset_id` | string | ULID of the ChangeSet |
|
|
| `plan_id` | string | ULID of the plan |
|
|
| `total_changes` | int | Number of change entries |
|
|
| `summary` | object | `{creates, modifies, deletes, renames, paths_changed, resources_involved}` |
|
|
| `entries` | list | Per-file entries with `path`, `operation`, hashes, timestamps |
|
|
|
|
### `agents plan artifacts <plan_id>`
|
|
|
|
Show plan artifacts: ChangeSet metadata, sandbox references, file
|
|
change list, and validation results.
|
|
|
|
**Options:**
|
|
|
|
| Flag | Description |
|
|
|------|-------------|
|
|
| `--format`, `-f` | Output format: `rich` (default), `plain`, `json`, `yaml` |
|
|
|
|
**Output fields (JSON/YAML):**
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `plan_id` | string | Plan ULID |
|
|
| `phase` | string | Current lifecycle phase |
|
|
| `processing_state` | string | Current processing state |
|
|
| `changeset_id` | string | ChangeSet ULID (null if Execute not complete) |
|
|
| `sandbox_refs` | list | Active sandbox reference IDs |
|
|
| `changeset_summary` | object | Summary counts from SpecChangeSet |
|
|
| `files_changed` | list | `{path, operation}` per changed file |
|
|
| `validation_summary` | object | Validation results (if available) |
|
|
| `apply_summary` | object | Files changed count, validations run (after apply) |
|
|
|
|
## Apply Integration
|
|
|
|
### Empty ChangeSet Guard
|
|
|
|
The apply pipeline checks whether the ChangeSet has any entries before
|
|
proceeding. If the ChangeSet is empty, apply is blocked with a clear
|
|
message:
|
|
|
|
```
|
|
Plan <id> has an empty ChangeSet. No changes to apply. Use --allow-empty to override.
|
|
```
|
|
|
|
Set `--allow-empty` to bypass this check for plans that intentionally
|
|
produce no file changes (e.g. validation-only plans).
|
|
|
|
### Apply Summary Persistence
|
|
|
|
When apply completes, the following metadata is stored in the plan:
|
|
|
|
- `apply_files_changed`: Number of files written/modified/deleted
|
|
- `apply_validations_run`: Number of validation checks executed
|
|
- `apply_completed_at`: ISO-8601 timestamp of apply completion
|
|
|
|
This metadata is visible via `agents plan status` and `agents plan artifacts`.
|
|
|
|
### Merge Failure Handling
|
|
|
|
When a sandbox merge fails during apply:
|
|
|
|
1. The plan transitions to `ERRORED` processing state
|
|
2. `error_message` is set to `"Merge failed: <details>"`
|
|
3. `error_details` includes:
|
|
- `merge_conflict`: Description of the conflict
|
|
- `sandbox_rollback`: Set to `"pending"` for cleanup
|
|
|
|
**Recovery steps:**
|
|
|
|
1. Review error details and recovery hints via `agents plan errors <id>`
|
|
2. Review conflict details via `agents plan status <id>`
|
|
3. Re-run execute phase after resolving conflicts: `agents plan execute <id>`
|
|
4. Or fix validation issues and retry apply
|
|
|
|
See [Error Recovery Reference](error_recovery.md) for the full error
|
|
classification, retry policy, and recovery hint system.
|
|
|
|
### Processing State Flow
|
|
|
|
```
|
|
Execute/COMPLETE
|
|
|
|
|
v
|
|
Apply/QUEUED --> Apply/PROCESSING --> Apply/APPLIED (terminal success)
|
|
|
|
|
+--> Apply/ERRORED (merge failure)
|
|
+--> Apply/CONSTRAINED (invariant violation)
|
|
```
|
|
|
|
### Validation-Gated Apply
|
|
|
|
The apply pipeline gates commits on validation results. Before any
|
|
file changes are committed, `apply_with_validation_gate()` checks
|
|
the validation summary (either from the plan or supplied externally)
|
|
and blocks apply if any **required** validations have failed.
|
|
|
|
#### Outcomes
|
|
|
|
| Outcome | Description |
|
|
|---------|-------------|
|
|
| `applied` | All required validations passed; changes committed |
|
|
| `constrained` | Required validations failed; apply blocked |
|
|
| `already_applied` | Plan was already in a terminal applied state |
|
|
| `blocked_empty` | ChangeSet was empty and `allow_empty` not set |
|
|
|
|
#### Flow
|
|
|
|
```
|
|
apply_with_validation_gate(plan_id)
|
|
|
|
|
+-- Plan in terminal state? --> already_applied / constrained
|
|
|
|
|
+-- Empty ChangeSet? --> blocked_empty (unless allow_empty)
|
|
|
|
|
+-- required_failed > 0? --> constrained (with actionable message)
|
|
|
|
|
+-- All passed --> persist_apply_summary() --> complete_apply()
|
|
--> ApplyResult(outcome="applied")
|
|
```
|
|
|
|
#### `ApplyResult` Model
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `outcome` | `ApplyOutcome` | One of the outcomes above |
|
|
| `plan_id` | `str` | Plan ULID |
|
|
| `message` | `str` | Human-readable result message |
|
|
| `files_changed` | `int` | Number of files changed during apply |
|
|
| `validations_total` | `int` | Total validations evaluated |
|
|
| `validations_passed` | `int` | Required validations that passed |
|
|
| `validations_failed` | `int` | Required validations that failed |
|
|
|
|
#### External Validation Summary
|
|
|
|
You can supply a validation summary from an external source (e.g. a
|
|
CI pipeline) to override the plan's stored summary:
|
|
|
|
```python
|
|
result = service.apply_with_validation_gate(
|
|
plan_id,
|
|
validation_summary={"total": 3, "required_passed": 2, "required_failed": 1},
|
|
)
|
|
```
|
|
|
|
## Service API
|
|
|
|
### `PlanApplyService`
|
|
|
|
```python
|
|
from cleveragents.application.services.plan_apply_service import PlanApplyService
|
|
|
|
service = PlanApplyService(
|
|
lifecycle_service=lifecycle,
|
|
changeset_store=store, # optional InMemoryChangeSetStore
|
|
)
|
|
|
|
# Generate diff
|
|
diff_text = service.diff(plan_id, fmt="json")
|
|
|
|
# Get artifacts
|
|
artifacts = service.artifacts(plan_id, fmt="json")
|
|
|
|
# Guard empty changeset
|
|
service.guard_empty_changeset(plan_id, allow_empty=False)
|
|
|
|
# Persist apply summary
|
|
service.persist_apply_summary(plan_id, files_changed=5, validations_run=3)
|
|
|
|
# Handle merge failure
|
|
service.handle_merge_failure(plan_id, conflict_details="...")
|
|
|
|
# Validation-gated apply
|
|
from cleveragents.application.services.plan_apply_service import ApplyOutcome, ApplyResult
|
|
|
|
result = service.apply_with_validation_gate(plan_id, allow_empty=False)
|
|
if result.outcome == ApplyOutcome.APPLIED:
|
|
print(f"Applied: {result.files_changed} files changed")
|
|
elif result.outcome == ApplyOutcome.CONSTRAINED:
|
|
print(f"Blocked: {result.message}")
|
|
```
|