Files
taskgraph_ts/docs/architecture/errors-validation.md
glm-5.1 4244c054b7 Fix critical architecture review issues
Critical fixes:
- Rename qualityDegradation → qualityRetention across all docs
  (semantically inverted: 0.9 meant 90% quality RETAINED, not 90%
  degradation). Updated schemas, graph-model, cost-benefit, ADRs.
- Add TaskInput → TaskGraphNodeAttributes transformation section
  to graph-model.md, documenting how Nullable(Optional) input fields
  map to Optional graph attributes
- Fix DuplicateEdgeError fields: source/target → prerequisite/dependent
  to match the established edge direction convention
- Fix resolveDefaults signature: Partial<TaskGraphNodeAttributes>
  → Partial<...> & Pick<TaskGraphNodeAttributes, 'name'> to
  require the name field
- Move Nullable helper definition before its first use in schemas.md
- Fix 'construction never throws' contradiction: rephrase to
  'construction enforces uniqueness, not data quality'
- Define all 6 enum value sets in schemas.md (previously only
  TaskScope and TaskRisk were explicit)
- Add EvConfig parameter table with defaults and semantics
- Document WorkflowCostOptions.limit parameter
- Add construction error handling table to graph-model.md
- Add graph.raw mutation safety warning to api-surface.md
- Update build-distribution.md error class list to include
  DuplicateNodeError and DuplicateEdgeError
2026-04-26 09:13:14 +00:00

129 lines
6.0 KiB
Markdown

---
status: draft
last_updated: 2026-04-26
---
# Errors & Validation
Error types and validation levels for the library.
## Error Types
Typed error classes for programmatic recovery. All library errors extend `TaskgraphError`.
```typescript
class TaskgraphError extends Error {}
class TaskNotFoundError extends TaskgraphError {
taskId: string
}
class CircularDependencyError extends TaskgraphError {
cycles: string[][] // each inner array is an ordered cycle path (last node → first node)
}
class InvalidInputError extends TaskgraphError {
field: string
message: string
}
class DuplicateNodeError extends TaskgraphError {
taskId: string
}
class DuplicateEdgeError extends TaskgraphError {
prerequisite: string
dependent: string
}
```
### When Each Error Is Thrown
| Error | Trigger |
|-------|---------|
| `TaskNotFoundError` | `getTask`, `dependencies`, `dependents` called with non-existent task ID |
| `CircularDependencyError` | `topologicalOrder()` called on a cyclic graph |
| `InvalidInputError` | Frontmatter parsing finds invalid field values or missing required fields |
| `DuplicateNodeError` | `addTask` called with an ID that already exists in the graph |
| `DuplicateEdgeError` | `addDependency` called for a prerequisite→dependent pair that already exists |
### Mutation Operations on Non-Existent Targets
| Operation | Behavior When Target Doesn't Exist |
|-----------|-----------------------------------|
| `removeTask(id)` | No-op — if the node doesn't exist, nothing to remove |
| `removeDependency(src, tgt)` | No-op — if the edge doesn't exist, nothing to remove |
| `updateTask(id, attrs)` | Throws `TaskNotFoundError` — cannot update attributes of a non-existent node |
| `updateEdgeAttributes(src, tgt, attrs)` | Throws `TaskNotFoundError` — cannot update attributes of a non-existent edge (implies at least one endpoint missing) |
| `addDependency(prereq, dep)` | Throws `TaskNotFoundError` — at least one endpoint must exist first (use `addTask` before `addDependency`) |
This policy avoids silent failures on writes that should succeed (update, add) while allowing idempotent removals (remove is a no-op, not an error).
## Validation Levels
Two validation levels, consistent with the Rust CLI's `validate` command:
### 1. Schema validation (`validateSchema()`)
TypeBox `Value.Check()` on input data — frontmatter fields, enum values, required fields. Returns `ValidationError[]`. The validation pipeline uses `Value.Errors()` for structured field-level detail (path, message, value) rather than `Value.Assert()` which throws without detail. `Value.Clean()` strips unknown properties before validation when processing untrusted input (e.g., frontmatter from external files). Catches:
- Missing required fields (`id`, `name`)
- Invalid enum values (e.g., `risk: "extreme"`)
- Type mismatches (e.g., `dependsOn: "not-an-array"`)
### 2. Graph validation (`validateGraph()`)
Graph-level invariants — catches problems that exist between tasks, not within a single task. Returns `GraphValidationError[]`:
- Cycle detection (via `findCycles()`)
- Dangling dependency references (task depends on an ID not in the graph)
### 3. Combined validation (`validate()`)
Runs both schema and graph validation. Returns `ValidationError[]` (the union of both types).
### Validation Return Types
```typescript
interface ValidationError {
type: "schema"
taskId?: string // which task has the issue (if applicable)
field: string // which field is invalid
message: string // human-readable description
value?: unknown // the invalid value (if safe to include)
}
interface GraphValidationError {
type: "graph"
category: "cycle" | "dangling-reference"
taskId?: string
message: string
details?: unknown // e.g., cycles: string[][] for cycle errors
}
```
Both types are returned as arrays. Validation never throws — it collects all issues and returns them. This allows consumers to implement "collect all errors" strategies.
## Cycle Handling
The library takes a strict approach to cycles:
- `hasCycles()` returns a boolean — no side effects
- `findCycles()` returns the actual cycle paths — for debugging and error reporting
- `topologicalOrder()` **throws** `CircularDependencyError` when the graph is cyclic, rather than returning a partial ordering — see [ADR-003](decisions/003-topo-order-throws-on-cycle.md)
**Cyclic graphs are a valid graph state** — they can be constructed, queried, and validated. Only operations that require a DAG (topo sort, critical path, parallel groups, workflow cost) throw on cycles. Construction methods enforce uniqueness but do not reject data quality issues.
## Construction vs. Validation Error Handling
The fundamental contract:
1. **Construction methods enforce uniqueness, not data quality**`fromTasks`, `fromRecords`, `fromJSON`, `addTask`, `addDependency` throw only for uniqueness constraint violations (`DuplicateNodeError`, `DuplicateEdgeError`) and missing targets (`TaskNotFoundError`). Data quality issues (invalid enum values, missing required fields, cycles) are the domain of `validate()`, not construction.
2. **Validation returns error arrays, never throws**`validateSchema()`, `validateGraph()`, and `validate()` collect issues without throwing.
3. **`topologicalOrder()` is the operation-level exception** — it throws on cyclic graphs because returning a partial result would be silently incorrect.
This distinction exists because validation is a "check before you proceed" operation (collect all issues, show the user), while topo sort is an operation that cannot produce a meaningful result on a cyclic graph.
## Constraints
- **All errors are typed** — no string-based error matching. Consumers can catch specific error classes.
- **Validation returns arrays, not throws** — consumers choose their own error handling strategy (fail-fast vs. collect-all-errors).
- **`topologicalOrder` is the sole exception** — it throws on cyclic graphs because returning a partial result would be silently incorrect.