Files
git.stella-ops.org/docs/schemas/taskpack-control-flow.schema.json
StellaOps Bot 05597616d6 feat: Add Go module and workspace test fixtures
- Created expected JSON files for Go modules and workspaces.
- Added go.mod and go.sum files for example projects.
- Implemented private module structure with expected JSON output.
- Introduced vendored dependencies with corresponding expected JSON.
- Developed PostgresGraphJobStore for managing graph jobs.
- Established SQL migration scripts for graph jobs schema.
- Implemented GraphJobRepository for CRUD operations on graph jobs.
- Created IGraphJobRepository interface for repository abstraction.
- Added unit tests for GraphJobRepository to ensure functionality.
2025-12-06 20:04:03 +02:00

671 lines
19 KiB
JSON

{
"$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 }}"}}
]
}
]
}