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
6.0 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 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.
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
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 effectsfindCycles()returns the actual cycle paths — for debugging and error reportingtopologicalOrder()throwsCircularDependencyErrorwhen the graph is cyclic, rather than returning a partial ordering — see ADR-003
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:
- Construction methods enforce uniqueness, not data quality —
fromTasks,fromRecords,fromJSON,addTask,addDependencythrow only for uniqueness constraint violations (DuplicateNodeError,DuplicateEdgeError) and missing targets (TaskNotFoundError). Data quality issues (invalid enum values, missing required fields, cycles) are the domain ofvalidate(), not construction. - Validation returns error arrays, never throws —
validateSchema(),validateGraph(), andvalidate()collect issues without throwing. 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).
topologicalOrderis the sole exception — it throws on cyclic graphs because returning a partial result would be silently incorrect.