- 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.
671 lines
19 KiB
JSON
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 }}"}}
|
|
]
|
|
}
|
|
]
|
|
}
|