{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://stellaops.io/schemas/taskpack-control-flow.v1.json", "title": "TaskPackControlFlow", "description": "TaskPack control-flow contract for loop, conditional, and policy-gate step definitions", "type": "object", "$defs": { "LoopStep": { "type": "object", "description": "Loop iteration step - executes sub-steps for each item in a collection", "required": ["id", "type", "items", "body"], "properties": { "id": { "type": "string", "description": "Unique step identifier within the pack" }, "type": { "const": "loop" }, "items": { "$ref": "#/$defs/LoopItemsExpression" }, "iterator": { "type": "string", "description": "Variable name bound to current item (default: 'item')", "default": "item" }, "index": { "type": "string", "description": "Variable name bound to current index (default: 'index')", "default": "index" }, "body": { "type": "array", "items": {"$ref": "#/$defs/Step"}, "minItems": 1, "description": "Steps to execute for each iteration" }, "maxIterations": { "type": "integer", "minimum": 1, "maximum": 10000, "default": 1000, "description": "Safety limit to prevent infinite loops" }, "continueOnError": { "type": "boolean", "default": false, "description": "Whether to continue with next iteration on error" }, "aggregation": { "$ref": "#/$defs/LoopAggregation" }, "when": { "$ref": "#/$defs/ConditionalExpression", "description": "Optional condition to skip entire loop" } } }, "LoopItemsExpression": { "oneOf": [ { "type": "object", "required": ["expression"], "properties": { "expression": { "type": "string", "description": "JMESPath expression yielding an array" } } }, { "type": "object", "required": ["range"], "properties": { "range": { "type": "object", "required": ["start", "end"], "properties": { "start": {"type": "integer"}, "end": {"type": "integer"}, "step": {"type": "integer", "default": 1} } } } }, { "type": "object", "required": ["static"], "properties": { "static": { "type": "array", "items": {} } } } ] }, "LoopAggregation": { "type": "object", "description": "How to aggregate loop iteration outputs", "properties": { "mode": { "type": "string", "enum": ["collect", "merge", "last", "first", "none"], "default": "collect", "description": "collect=array of outputs, merge=deep merge objects, last/first=single output" }, "outputPath": { "type": "string", "description": "JMESPath to extract from each iteration result" } } }, "ConditionalStep": { "type": "object", "description": "Conditional branching step - if/else-if/else logic", "required": ["id", "type", "branches"], "properties": { "id": { "type": "string" }, "type": { "const": "conditional" }, "branches": { "type": "array", "items": {"$ref": "#/$defs/ConditionalBranch"}, "minItems": 1, "description": "Ordered list of condition/body pairs; first matching branch executes" }, "else": { "type": "array", "items": {"$ref": "#/$defs/Step"}, "description": "Steps to execute if no branch conditions match" }, "outputUnion": { "type": "boolean", "default": false, "description": "Whether to union outputs from all branches (for deterministic output shape)" } } }, "ConditionalBranch": { "type": "object", "required": ["condition", "body"], "properties": { "condition": { "$ref": "#/$defs/ConditionalExpression" }, "body": { "type": "array", "items": {"$ref": "#/$defs/Step"}, "minItems": 1 } } }, "ConditionalExpression": { "oneOf": [ { "type": "string", "description": "JMESPath expression that evaluates to boolean" }, { "type": "object", "required": ["operator", "left", "right"], "properties": { "operator": { "type": "string", "enum": ["eq", "ne", "gt", "ge", "lt", "le", "contains", "startsWith", "endsWith", "matches"] }, "left": {"$ref": "#/$defs/ExpressionValue"}, "right": {"$ref": "#/$defs/ExpressionValue"} } }, { "type": "object", "required": ["and"], "properties": { "and": { "type": "array", "items": {"$ref": "#/$defs/ConditionalExpression"}, "minItems": 2 } } }, { "type": "object", "required": ["or"], "properties": { "or": { "type": "array", "items": {"$ref": "#/$defs/ConditionalExpression"}, "minItems": 2 } } }, { "type": "object", "required": ["not"], "properties": { "not": {"$ref": "#/$defs/ConditionalExpression"} } } ] }, "ExpressionValue": { "oneOf": [ {"type": "string"}, {"type": "number"}, {"type": "boolean"}, {"type": "null"}, { "type": "object", "required": ["expr"], "properties": { "expr": { "type": "string", "description": "JMESPath expression to evaluate" } } } ] }, "PolicyGateStep": { "type": "object", "description": "Policy gate step - blocks until policy evaluation passes", "required": ["id", "type", "policyRef"], "properties": { "id": { "type": "string" }, "type": { "const": "gate.policy" }, "policyRef": { "$ref": "#/$defs/PolicyReference" }, "input": { "type": "object", "description": "Input data for policy evaluation (can use expressions)", "additionalProperties": true }, "inputExpression": { "type": "string", "description": "JMESPath expression to construct policy input from step context" }, "timeout": { "type": "string", "pattern": "^\\d+[smh]$", "default": "5m", "description": "Timeout for policy evaluation (e.g., '30s', '5m')" }, "failureAction": { "$ref": "#/$defs/PolicyFailureAction" }, "evidence": { "$ref": "#/$defs/PolicyEvidenceConfig" }, "when": { "$ref": "#/$defs/ConditionalExpression", "description": "Optional condition to skip gate evaluation" } } }, "PolicyReference": { "type": "object", "required": ["policyId"], "properties": { "policyId": { "type": "string", "description": "Policy identifier in the policy registry" }, "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", "description": "Specific policy version (semver); omit for active version" }, "digest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$", "description": "Policy digest for reproducibility" } } }, "PolicyFailureAction": { "type": "object", "description": "What to do when policy evaluation fails", "properties": { "action": { "type": "string", "enum": ["abort", "warn", "requestOverride", "branch"], "default": "abort" }, "retryCount": { "type": "integer", "minimum": 0, "maximum": 3, "default": 0 }, "retryDelay": { "type": "string", "pattern": "^\\d+[smh]$", "default": "10s" }, "overrideApprovers": { "type": "array", "items": {"type": "string"}, "description": "Required approvers for override (if action=requestOverride)" }, "branchTo": { "type": "string", "description": "Step ID to branch to on failure (if action=branch)" } } }, "PolicyEvidenceConfig": { "type": "object", "description": "Evidence recording for policy evaluations", "properties": { "recordDecision": { "type": "boolean", "default": true, "description": "Record policy decision in evidence locker" }, "recordInput": { "type": "boolean", "default": false, "description": "Record policy input (may contain sensitive data)" }, "recordRationale": { "type": "boolean", "default": true, "description": "Record policy rationale/explanation" }, "attestation": { "type": "boolean", "default": false, "description": "Create DSSE attestation for policy decision" } } }, "ApprovalGateStep": { "type": "object", "description": "Approval gate step - blocks until human approval received", "required": ["id", "type", "approvers"], "properties": { "id": { "type": "string" }, "type": { "const": "gate.approval" }, "approvers": { "$ref": "#/$defs/ApproverRequirements" }, "message": { "type": "string", "description": "Message shown to approvers" }, "timeout": { "type": "string", "pattern": "^\\d+[smhd]$", "description": "Approval timeout (e.g., '24h', '7d')" }, "autoApprove": { "$ref": "#/$defs/AutoApprovalConfig" }, "evidence": { "$ref": "#/$defs/ApprovalEvidenceConfig" }, "when": { "$ref": "#/$defs/ConditionalExpression" } } }, "ApproverRequirements": { "type": "object", "properties": { "minimum": { "type": "integer", "minimum": 1, "default": 1, "description": "Minimum approvals required" }, "roles": { "type": "array", "items": {"type": "string"}, "description": "Required approver roles/groups" }, "users": { "type": "array", "items": {"type": "string"}, "description": "Specific user identities allowed to approve" }, "excludeSubmitter": { "type": "boolean", "default": true, "description": "Prevent pack submitter from self-approval" } } }, "AutoApprovalConfig": { "type": "object", "description": "Automatic approval rules", "properties": { "enabled": { "type": "boolean", "default": false }, "conditions": { "type": "array", "items": {"$ref": "#/$defs/ConditionalExpression"}, "description": "All conditions must match for auto-approval" }, "reason": { "type": "string", "description": "Recorded reason for auto-approval" } } }, "ApprovalEvidenceConfig": { "type": "object", "properties": { "recordDecision": { "type": "boolean", "default": true }, "recordApprovers": { "type": "boolean", "default": true }, "attestation": { "type": "boolean", "default": true, "description": "Create DSSE attestation for approval" } } }, "MapStep": { "type": "object", "description": "Map step - parallel iteration over deterministic collection", "required": ["id", "type", "items", "body"], "properties": { "id": { "type": "string" }, "type": { "const": "map" }, "items": { "$ref": "#/$defs/LoopItemsExpression" }, "iterator": { "type": "string", "default": "item" }, "body": { "type": "array", "items": {"$ref": "#/$defs/Step"}, "minItems": 1 }, "maxParallel": { "type": "integer", "minimum": 1, "default": 10, "description": "Maximum concurrent iterations" }, "aggregation": { "$ref": "#/$defs/LoopAggregation" }, "when": { "$ref": "#/$defs/ConditionalExpression" } } }, "ParallelStep": { "type": "object", "description": "Parallel execution of independent sub-steps", "required": ["id", "type", "branches"], "properties": { "id": { "type": "string" }, "type": { "const": "parallel" }, "branches": { "type": "array", "items": { "type": "array", "items": {"$ref": "#/$defs/Step"} }, "minItems": 2, "description": "Independent step sequences to run concurrently" }, "maxParallel": { "type": "integer", "minimum": 1 }, "failFast": { "type": "boolean", "default": true, "description": "Abort all branches on first failure" }, "when": { "$ref": "#/$defs/ConditionalExpression" } } }, "RunStep": { "type": "object", "description": "Execute a module or built-in action", "required": ["id", "type", "module"], "properties": { "id": { "type": "string" }, "type": { "const": "run" }, "module": { "type": "string", "description": "Module reference (builtin:* or registry path)" }, "inputs": { "type": "object", "additionalProperties": true }, "outputs": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Output variable bindings" }, "timeout": { "type": "string", "pattern": "^\\d+[smh]$" }, "when": { "$ref": "#/$defs/ConditionalExpression" } } }, "Step": { "oneOf": [ {"$ref": "#/$defs/RunStep"}, {"$ref": "#/$defs/LoopStep"}, {"$ref": "#/$defs/ConditionalStep"}, {"$ref": "#/$defs/MapStep"}, {"$ref": "#/$defs/ParallelStep"}, {"$ref": "#/$defs/PolicyGateStep"}, {"$ref": "#/$defs/ApprovalGateStep"} ] }, "PackRunStepKind": { "type": "string", "enum": ["run", "loop", "conditional", "map", "parallel", "gate.policy", "gate.approval"], "description": "All supported step types in TaskPack v1" }, "ExecutionGraph": { "type": "object", "description": "Compiled execution graph from pack definition", "required": ["packId", "version", "steps"], "properties": { "packId": { "type": "string" }, "version": { "type": "string" }, "digest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }, "steps": { "type": "array", "items": {"$ref": "#/$defs/Step"} }, "dependencies": { "type": "object", "additionalProperties": { "type": "array", "items": {"type": "string"} }, "description": "Step ID -> dependent step IDs mapping" } } }, "DeterminismRequirements": { "type": "object", "description": "Determinism guarantees for control-flow execution", "properties": { "loopTermination": { "type": "string", "const": "guaranteed", "description": "Loops always terminate (maxIterations enforced)" }, "iterationOrdering": { "type": "string", "const": "stable", "description": "Loop iterations execute in deterministic order" }, "conditionalEvaluation": { "type": "string", "const": "pure", "description": "Conditional expressions have no side effects" }, "policyEvaluation": { "type": "string", "const": "versioned", "description": "Policy gates use versioned/digested policies" } } } }, "properties": { "version": { "const": "1.0.0" }, "supportedStepTypes": { "$ref": "#/$defs/PackRunStepKind" }, "determinism": { "$ref": "#/$defs/DeterminismRequirements" } }, "examples": [ { "id": "scan-all-repos", "type": "loop", "items": {"expression": "inputs.repositories"}, "iterator": "repo", "maxIterations": 100, "body": [ { "id": "scan-repo", "type": "run", "module": "builtin:scanner", "inputs": {"repository": "{{ repo }}"} } ], "aggregation": {"mode": "collect"} }, { "id": "severity-gate", "type": "gate.policy", "policyRef": {"policyId": "severity-threshold", "version": "1.0.0"}, "input": {"findings": "{{ steps.scan.outputs.findings }}"}, "failureAction": {"action": "requestOverride", "overrideApprovers": ["security-team"]}, "evidence": {"recordDecision": true, "attestation": true} }, { "id": "deploy-decision", "type": "conditional", "branches": [ { "condition": {"operator": "eq", "left": {"expr": "inputs.environment"}, "right": "production"}, "body": [ {"id": "prod-approval", "type": "gate.approval", "approvers": {"minimum": 2, "roles": ["release-manager"]}} ] } ], "else": [ {"id": "auto-deploy", "type": "run", "module": "builtin:deploy", "inputs": {"target": "{{ inputs.environment }}"}} ] } ] }