Workflow Graph Validator
workflow.validate_dag
Overview
You send the steps of a workflow and the edges between them, and we tell you whether that graph can actually be run: no step depending on itself, no loop, no edge pointing at a step you never declared, no step declared twice. When something is wrong you get the offending ids back, not just a verdict — the loops, the dangling references, the duplicates, and the independent groups the graph splits into. A broken graph is a normal answer here, so structural problems come back as a report with valid set to false rather than as an error. We only read the graph you send: we run nothing, schedule nothing, and store nothing.
A workflow whose graph has a loop or a dangling edge fails somewhere in the middle of a run, after side effects have already happened. Checking the shape before anything executes turns that into a rejected submission with the offending ids in hand.
Reliability
Fully deterministic: the same graph always produces the same report, and every list comes back in declaration order, so two callers who describe one graph identically get identical bytes. Traversal is iterative, so a 10000-node chain is answered rather than overflowing. Failures, which are about the request rather than the graph: WORKFLOW_NODE_LIMIT_EXCEEDED and WORKFLOW_EDGE_LIMIT_EXCEEDED mean you sent more than 10000 nodes or 50000 edges, so split the graph. WORKFLOW_IDENTIFIER_INVALID means an id is empty, longer than 128 characters, padded with whitespace, or carries a control character; fix the id and resend, since none of these can be corrected for you without changing which steps match. WORKFLOW_OUTPUT_TOO_LARGE means the ids alone would exceed 2 MB of response, which needs a graph at the ceiling with very long ids; shorten the ids, lower options.maxReportedCycles, or split the graph.
Context footprint
The report holds ids and counts, no step payloads. A clean 1000-step graph answers in a few tens of kilobytes; report arrays stop at 1000 entries each and say so in _meta. A successful call costs 1 execution credit.
When to use it
When you accept, generate or merge a pipeline definition and want the structural verdict plus the exact ids to show the author.
When you need the execution order rather than the verdict, when loops are part of the design, or when you want the workflow actually run.
Scope
Non-goals
- Does not run, schedule, or store the workflow
- Does not order the steps — workflow.topological_sort does that
- Does not invent steps for edges that reference unknown ids
- Does not judge what a step does, only how it is wired
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response may contain submitted or transformed content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| nodes | array | Yes | Workflow steps, 0 to 10000 closed {id} objects with unique identifiers. |
| edges | array | No | Dependencies, 0 to 50000 closed {from, to} objects; omit it for a graph with no edges. |
| options | object | No | Closed reporting options; omit the object for allowDisconnected true and maxReportedCycles 100. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| valid | boolean | Yes | True when the graph has no duplicate ids, dangling references, loops, or a disallowed split. |
| nodeCount | integer | Yes | How many distinct steps remain after duplicates are counted once. |
| edgeCount | integer | Yes | How many distinct edges remain after identical ones are de-duplicated. |
| roots | array | Yes | Ids of steps nothing depends on, in declaration order, 0 to 10000 strings. |
| leaves | array | Yes | Ids of steps no other step waits for, in declaration order, 0 to 10000 strings. |
| missingNodes | array | Yes | Ids an edge references that no node declares, in first-reference order, 0 to 10000 strings. |
| duplicateNodes | array | Yes | Ids declared more than once, reported once each, 0 to 10000 strings. |
| duplicateEdges | array | Yes | Edges declared more than once, each a closed {from, to} object, informational only. |
| selfLoops | array | Yes | Ids of steps that depend on themselves, 0 through 10000 strings in first-seen order. |
| cycles | array | Yes | Loops as groups of ids in declaration order, capped by maxReportedCycles, 0 to 1000 groups. |
| disconnectedComponents | array | Yes | Independent groups when the graph splits, members in declaration order; empty when connected. |
| issues | array | Yes | One closed {code, count, message} object per problem kind; empty for a clean graph. |
| _meta | object | Yes | Always present on success, with declaredEdgeCount, componentCount, cyclesTruncated and issuesTruncated. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"workflow.validate_dag","input":{"nodes":[{"id":"extract"},{"id":"load"}],"edges":[{"from":"extract","to":"load"}]}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("workflow.validate_dag", {"edges":[{"from":"extract","to":"transform"},{"from":"fetch-rates","to":"transform"},{"from":"transform","to":"load"}],"nodes":[{"id":"extract"},{"id":"fetch-rates"},{"id":"transform"},{"id":"load"}]});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| WORKFLOW_NODE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| WORKFLOW_EDGE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| WORKFLOW_IDENTIFIER_INVALID | 400 | No | The request payload failed this skill's input validation. |
| WORKFLOW_OUTPUT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not run, schedule, or store the workflow
- Does not order the steps — workflow.topological_sort does that
- Does not invent steps for edges that reference unknown ids
- Does not judge what a step does, only how it is wired
- Executing or scheduling the workflow
- Graphs where cycles are legitimate, such as state machines with returns
- Ordering steps, which is a separate skill
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "workflow.validate_dag",
"input": {
"edges": [
{
"from": "extract",
"to": "transform"
},
{
"from": "fetch-rates",
"to": "transform"
},
{
"from": "transform",
"to": "load"
}
],
"nodes": [
{
"id": "extract"
},
{
"id": "fetch-rates"
},
{
"id": "transform"
},
{
"id": "load"
}
]
}
}Examples
A clean four-step pipeline: two roots, one join, one leaf
{
"edges": [
{
"from": "extract",
"to": "transform"
},
{
"from": "fetch-rates",
"to": "transform"
},
{
"from": "transform",
"to": "load"
}
],
"nodes": [
{
"id": "extract"
},
{
"id": "fetch-rates"
},
{
"id": "transform"
},
{
"id": "load"
}
]
}{
"roots": [
"extract",
"fetch-rates"
],
"cycles": [],
"duplicateNodes": [],
"valid": true,
"missingNodes": [],
"_meta": {
"issuesTruncated": false,
"cyclesTruncated": false,
"declaredEdgeCount": 3,
"componentCount": 1
},
"disconnectedComponents": [],
"edgeCount": 3,
"nodeCount": 4,
"issues": [],
"selfLoops": [],
"duplicateEdges": [],
"leaves": [
"load"
]
}A broken graph: a two-step loop, a self-loop and an edge to a step that was never declared
{
"edges": [
{
"from": "a",
"to": "b"
},
{
"from": "b",
"to": "a"
},
{
"from": "c",
"to": "c"
},
{
"from": "c",
"to": "d"
}
],
"nodes": [
{
"id": "a"
},
{
"id": "b"
},
{
"id": "c"
}
]
}{
"roots": [],
"cycles": [
[
"a",
"b"
],
[
"c"
]
],
"duplicateNodes": [],
"valid": false,
"missingNodes": [
"d"
],
"_meta": {
"issuesTruncated": false,
"cyclesTruncated": false,
"declaredEdgeCount": 4,
"componentCount": 2
},
"disconnectedComponents": [
[
"a",
"b"
],
[
"c"
]
],
"edgeCount": 4,
"nodeCount": 3,
"issues": [
{
"code": "MISSING_REFERENCE",
"count": 1,
"message": "An edge references an id that no node declares, so that dependency cannot be honoured."
},
{
"code": "SELF_LOOP",
"count": 1,
"message": "A node depends on itself, which can never be satisfied. Self-loops are also counted as cycles."
},
{
"code": "CYCLE",
"count": 2,
"message": "Nodes depend on each other in a loop, so no order can satisfy every edge."
},
{
"code": "DISCONNECTED",
"count": 2,
"message": "The graph splits into independent groups. You allowed that, so it does not make the graph invalid."
}
],
"selfLoops": [
"c"
],
"duplicateEdges": [],
"leaves": []
}Recommended use cases
- Reject a broken pipeline definition at submission time instead of mid-run
- Show an author exactly which steps form a loop
- Confirm a generated or merged graph is still executable
Avoid for
- Executing or scheduling the workflow
- Graphs where cycles are legitimate, such as state machines with returns
- Ordering steps, which is a separate skill
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.