Orchestrata Ultimate / Specs / 068-schedule-cron-tool

068-schedule-cron-tool: ScheduleCronTool - Cron Job Scheduling with Timezone Support

A production-ready tool for scheduling recurring jobs using cron expressions with comprehensive timezone support, missed job recovery, and concurrent execution protection.

At a glance

Item Value
Spec directory specs/068-schedule-cron-tool/
Status line in the spec Complete
Requirement IDs defined 30
With a @traces citation in src/ 23 (76.7%)
With a @traces citation in tests/ 0 (0.0%)
Tracked files 3
RTM page 068-schedule-cron-tool

Reading this spec

This spec defines ScheduleCronTool, a tool for scheduling recurring jobs with cron expressions. It adds timezone support for distributed teams, recovery of missed jobs after system failures, and protection against concurrent execution of sensitive jobs. Its three scenarios are daily report generation, backup coordination across a distributed team and critical health-check monitoring.

The functional requirements (FR-001 onward) cover cron expression parsing, timezone support, job persistence, missed job recovery, concurrent execution protection and job execution. The overview describes the design as cron parsing with timezones, persistent job storage, a background asynchronous scheduler, recovery mechanisms and distributed locking.

The 30 requirement IDs (REQ-068-NNN, plus three REQ-068-EXT extensions) record where the implementation differs from the overview, which is the most useful thing a reader can learn here. Standard 5-field cron expressions are supported with lists, ranges and steps; timezones are IANA names through the standard-library zoneinfo; timestamps are stored in UTC. Job definitions persist in Redis through ScheduleCronStore, and the requirement notes this deviates from the SQLite store in the overview (a brief-directed fork). Jobs run asynchronously through asyncio.create_subprocess_shell, can be listed with filters and triggered manually with run_job_now, and old execution records can be cleared. The requirements mark as not implemented: UTC offset notation such as +05:30, job import and export, bulk operations, automatic missed-execution recovery on startup (it needs a long-running scheduler process), true FIFO queue semantics (allow and queue both run unlocked in this MVP) and an exponential backoff option.

The implementation is in the schedule cron tool module. The requirements traceability matrix lists each ID. The spec's own status line reads Complete and it tracks 3 files. Measured coverage is 23 of 30 requirements cited by at least one @traces in src/ (76.7%), spread over 1 module; the module citing the most of them is orchestrata_ultimate/tools/schedule_cron_tool (23 requirements). In tests/, 0 requirements are cited by a test-side @traces. The 7 requirements with no citing symbol are four REQ-068 items and the three EXT items; most are the not-implemented features above.

Requirements

Checkbox is the state recorded in the requirements checklist; Traced by is the count of src/ symbols whose @traces names the ID.

ID Done Requirement Traced by
REQ-068-001 yes Support standard 5-field cron expressions (minute, hour, day, month, weekday), including *, lists (,), ranges (-), and steps (/) 8
REQ-068-002 yes Support named months and weekdays (JAN-DEC, SUN-SAT), case-insensitive 4
REQ-068-003 yes Validate expression syntax before scheduling (validate_cron_expression) 2
REQ-068-004 no Support extended 6-field format with seconds, and L/W/# special characters — MVP scope cut: the parser rejects these with a clear CronParseError rather than silently mis-scheduling; only the 5-field form is implemented 1
REQ-068-005 yes Accept IANA timezone database names (e.g. America/New_York) via stdlib zoneinfo 3
REQ-068-006 yes Store all timestamps in UTC internally; job-timezone wall-clock fields are only used for cron matching none
REQ-068-007 yes Handle DST transitions automatically (search is carried out in the job's local wall-clock time, then converted back to UTC via zoneinfo) 2
REQ-068-008 no Support UTC offset notation (e.g. +05:30) as a timezone value — not implemented; only IANA zone names are accepted (ZoneInfo validates them) none
REQ-068-009 yes Persist job definitions across restarts — via Redis (ScheduleCronStore), not SQLite per spec.md (brief-directed fork; project's real hot store) 13
REQ-068-010 yes Maintain execution history — per-job and global Redis sorted sets of execution records 9
REQ-068-011 no Support job import/export — not implemented; out of MVP scope none
REQ-068-012 yes miss_policy is stored on every job so a future recovery daemon has the bookkeeping it needs 1
REQ-068-EXT-001 no Detect missed executions on startup and apply the recovery policy automatically — not implemented: this requires a long-running scheduler process that doesn't exist here (reported as a wiring fork, not invented) none
REQ-068-013 yes Acquire a distributed lock (Redis SET NX EX) before execution when concurrency_policy is forbid/skip 6
REQ-068-014 yes Lock has a TTL equal to timeout_seconds, so a stuck/crashed holder's lock self-expires (stuck-job detection via expiry, not active polling); get_job_status reports is_locked 5
REQ-068-EXT-002 no True FIFO queue concurrency semantics — not implemented: ALLOW and QUEUE both run unlocked in this MVP (documented simplification) none
REQ-068-015 yes Execute jobs asynchronously via asyncio.create_subprocess_shell 3
REQ-068-016 yes Support timeout enforcement (asyncio.wait_for + subprocess kill on expiry) and capture stdout/stderr output 3
REQ-068-017 yes Handle execution errors gracefully (non-zero exit, COMMAND_NOT_FOUND, timeout) without raising out of run_job_now 4
REQ-068-EXT-003 no Exponential backoff option — not implemented; delay is fixed per spec's retry_delay_seconds field only none
REQ-068-019 yes List jobs with enabled_only/tags/limit filters 5
REQ-068-020 yes Update job configuration (update_job), re-validating cron expression/timezone and recomputing next_run_at 5
REQ-068-021 yes Pause/resume jobs (disable/enable without removing) 5
REQ-068-022 yes Delete (unschedule_job) a job and its execution history 5
REQ-068-023 yes Manual trigger support (run_job_now, with a force override for disabled jobs / lock bypass) 1
REQ-068-024 yes Cancel a running job (cancel_job) — in-process only: cancels the asyncio.Task/subprocess this same tool instance started; no cross-process job registry exists to cancel a job triggered elsewhere 1
REQ-068-025 yes get_next_runs computes the next N upcoming fire times 2
REQ-068-026 yes get_execution_history with job_id/status/limit filters 5
REQ-068-027 yes clear_history deletes execution records older than a cutoff (older_than_days), optionally scoped to one job_id 2
REQ-068-028 no Bulk operations across multiple jobs in one call — not implemented; out of MVP scope none

Code that traces this spec

Module Symbols Requirements
orchestrata_ultimate/tools/schedule_cron_tool 62 23

Checklists

checklists/requirements.md

The requirement list above comes from this file; 23 of 30 checkbox items are ticked.

Other tracked files

Full spec text

Verbatim from specs/068-schedule-cron-tool/spec.md; headings are pushed down two levels.

SPEC-068: ScheduleCronTool - Cron Job Scheduling with Timezone Support

Metadata


Overview

What

A production-ready tool for scheduling recurring jobs using cron expressions with comprehensive timezone support, missed job recovery, and concurrent execution protection.

Why
How

Tool Interface

from datetime import datetime, timezone
from typing import Optional
from enum import Enum

class MissedJobPolicy(Enum):
    """Policy for handling missed jobs."""
    SKIP = "skip"
    RUN_ONCE = "run_once"
    RUN_ALL = "run_all"
    NOTIFY_ONLY = "notify_only"

class ConcurrencyPolicy(Enum):
    """Policy for handling concurrent job execution."""
    ALLOW = "allow"
    SKIP = "skip"
    QUEUE = "queue"
    FORBID = "forbid"

class JobStatus(Enum):
    """Job execution status."""
    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"
    RECOVERED = "recovered"

@dataclass
class CronJob:
    """Cron job definition."""
    id: str
    name: str
    cron_expression: str
    timezone: str
    command: str
    args: dict
    enabled: bool = True
    miss_policy: MissedJobPolicy = MissedJobPolicy.RUN_ONCE
    concurrency_policy: ConcurrencyPolicy = ConcurrencyPolicy.ALLOW
    max_retries: int = 3
    retry_delay_seconds: int = 60
    timeout_seconds: int = 300
    description: str = ""
    tags: list[str] = field(default_factory=list)
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    last_run_at: Optional[datetime] = None
    next_run_at: Optional[datetime] = None

@dataclass
class JobExecution:
    """Job execution record."""
    id: str
    job_id: str
    status: JobStatus
    started_at: datetime
    completed_at: Optional[datetime] = None
    error_message: Optional[str] = None
    output: Optional[str] = None
    retry_count: int = 0
    is_recovery: bool = False
    execution_node: Optional[str] = None

@dataclass
class JobScheduleResult:
    """Result of scheduling a job."""
    success: bool
    job_id: Optional[str] = None
    next_run_at: Optional[datetime] = None
    error_message: Optional[str] = None

@dataclass
class JobExecutionResult:
    """Result of job execution."""
    success: bool
    execution_id: Optional[str] = None
    output: Optional[str] = None
    error_message: Optional[str] = None
    duration_seconds: Optional[float] = None
class ScheduleCronTool(BaseTool):
    """Schedule and manage recurring jobs using cron expressions."""
    
    name = "ScheduleCronTool"
    description = "Schedule recurring jobs using cron expressions with timezone support"
    
    async def schedule_job(
        self,
        name: str,
        cron_expression: str,
        command: str,
        timezone: str = "UTC",
        args: dict = None,
        miss_policy: str = "run_once",
        concurrency_policy: str = "allow",
        max_retries: int = 3,
        retry_delay_seconds: int = 60,
        timeout_seconds: int = 300,
        description: str = "",
        tags: list[str] = None,
        enabled: bool = True
    ) -> JobScheduleResult:
        """Schedule a new cron job."""
    
    async def unschedule_job(
        self,
        job_id: str
    ) -> ToolResult:
        """Remove a scheduled job."""
    
    async def update_job(
        self,
        job_id: str,
        **updates
    ) -> ToolResult:
        """Update an existing job."""
    
    async def list_jobs(
        self,
        enabled_only: bool = False,
        tags: list[str] = None,
        limit: int = 100
    ) -> list[dict]:
        """List scheduled jobs."""
    
    async def get_job_status(
        self,
        job_id: str
    ) -> dict:
        """Get job status and execution history."""
    
    async def run_job_now(
        self,
        job_id: str,
        force: bool = False
    ) -> JobExecutionResult:
        """Trigger immediate job execution."""
    
    async def cancel_job(
        self,
        job_id: str
    ) -> ToolResult:
        """Cancel a running job."""
    
    async def get_next_runs(
        self,
        job_id: str,
        count: int = 5
    ) -> list[datetime]:
        """Get upcoming scheduled run times."""
    
    async def validate_cron_expression(
        self,
        expression: str,
        timezone: str = "UTC"
    ) -> dict:
        """Validate a cron expression."""
    
    async def pause_job(
        self,
        job_id: str
    ) -> ToolResult:
        """Pause a job (disable without removing)."""
    
    async def resume_job(
        self,
        job_id: str
    ) -> ToolResult:
        """Resume a paused job."""
    
    async def get_execution_history(
        self,
        job_id: str = None,
        status: str = None,
        limit: int = 50
    ) -> list[dict]:
        """Get job execution history."""
    
    async def clear_history(
        self,
        job_id: str = None,
        older_than_days: int = 30
    ) -> ToolResult:
        """Clear execution history records."""

User Scenarios

Scenario 1: Daily Report Generation

User: DevOps engineer automating daily system reports Context: Needs to run a report generation script at 8:00 AM in their local timezone (America/New_York) Flow:

  1. Schedule job with cron expression 0 8 * * *
  2. Set timezone to America/New_York
  3. Configure miss_policy to run_once to handle missed runs
  4. Set concurrency_policy to forbid to prevent duplicate reports
  5. Job executes daily at 8 AM EST/EDT automatically
Scenario 2: Distributed Team Backup Coordination

User: Platform team coordinating backups across global regions Context: Multiple team members in different timezones need to schedule backup jobs Flow:

  1. Team lead creates jobs for each region (US, EU, APAC)
  2. Each job uses corresponding timezone (America/Los_Angeles, Europe/London, Asia/Tokyo)
  3. System automatically handles DST transitions
  4. Recovery policy ensures no missed backups after system downtime
  5. Execution history provides audit trail for compliance
Scenario 3: Critical Health Check Monitoring

User: SRE team running critical health checks Context: Health checks must run every 5 minutes without fail Flow:

  1. Schedule job with */5 * * * * (every 5 minutes)
  2. Set timeout of 60 seconds
  3. Configure max_retries=3 with 30-second delay
  4. Set concurrency_policy to forbid to prevent overlapping checks
  5. Enable recovery mode to catch up after deployment windows

Terminology

Term Definition
Cron Expression A time-based job scheduler expression in the format "minute hour day month weekday" that defines when a job should run
Timezone A geographic region with a consistent standard time, identified by IANA database names (e.g., "America/New_York")
DST (Daylight Saving Time) Seasonal time adjustment where clocks are moved forward by one hour during summer months
Missed Job A scheduled job that should have executed but did not, typically due to system downtime or service interruption
Recovery The process of detecting and executing missed jobs after system restart or recovery
Concurrency Policy Rules that define how multiple instances of the same job should be handled when they might run simultaneously
Distributed Lock A synchronization mechanism that prevents concurrent execution across multiple nodes or processes
Job Execution A single run of a scheduled job, tracked with status, timing, and output information
Background Scheduler A service that continuously monitors scheduled jobs and triggers their execution at appropriate times
SQLite A lightweight, serverless database engine used for persistent job storage

Functional Requirements

FR-001: Cron Expression Parsing
FR-002: Timezone Support
FR-003: Job Persistence
FR-004: Missed Job Recovery
FR-005: Concurrent Execution Protection
FR-006: Job Execution
FR-007: Retry Mechanism
FR-008: Job Management

Error Conditions

ID Condition Description Handling
ERR-001 INVALID_CRON_EXPRESSION Cron expression syntax is invalid Return validation error with details
ERR-002 INVALID_TIMEZONE Timezone string is not recognized Suggest valid timezone names
ERR-003 JOB_NOT_FOUND Job ID does not exist Return error with job ID
ERR-004 JOB_ALREADY_EXISTS Job with same name already scheduled Allow with force flag or suggest rename
ERR-005 CONCURRENT_EXECUTION Another instance is running Apply concurrency policy
ERR-006 JOB_TIMEOUT Job exceeded timeout limit Mark as failed, trigger retry
ERR-007 COMMAND_NOT_FOUND Executable command not found Mark as failed with error details
ERR-008 PERMISSION_DENIED Insufficient permissions Log error, mark job failed
ERR-009 DATABASE_ERROR Database operation failed Retry with exponential backoff
ERR-010 LOCK_ACQUISITION_FAILED Could not acquire execution lock Apply concurrency policy
ERR-011 RECOVERY_OVERLOAD Too many missed jobs to recover Process in batches, notify user
ERR-012 INVALID_CONFIGURATION Job configuration is invalid Return validation errors
ERR-013 MAX_RETRIES_EXCEEDED Job failed after all retries Mark as failed, send notification
ERR-014 JOB_CANCELLED Job was cancelled during execution Update status, clean up
ERR-015 TIMEZONE_TRANSITION DST transition detected during execution Handle with timezone awareness

API Contracts

Schedule Job

Request:

{
  "name": "daily-report",
  "cron_expression": "0 8 * * *",
  "timezone": "America/New_York",
  "command": "/scripts/generate_report.sh",
  "args": {
    "format": "pdf",
    "recipients": ["team@example.com"]
  },
  "miss_policy": "run_once",
  "concurrency_policy": "forbid",
  "max_retries": 3,
  "retry_delay_seconds": 60,
  "timeout_seconds": 300,
  "description": "Generate daily system report",
  "tags": ["reporting", "daily"],
  "enabled": true
}

Response (Success):

{
  "success": true,
  "job_id": "job_abc123",
  "next_run_at": "2026-04-09T08:00:00-04:00",
  "timezone": "America/New_York",
  "created_at": "2026-04-08T10:30:00Z"
}

Response (Error):

{
  "success": false,
  "error_code": "E001",
  "error_message": "Invalid cron expression: '0 8 * *'",
  "validation_errors": [
    "Expression must have 5 fields, got 4"
  ]
}
List Jobs

Request:

{
  "enabled_only": false,
  "tags": ["reporting"],
  "limit": 50
}

Response:

{
  "jobs": [
    {
      "id": "job_abc123",
      "name": "daily-report",
      "cron_expression": "0 8 * * *",
      "timezone": "America/New_York",
      "enabled": true,
      "status": "active",
      "last_run_at": "2026-04-08T08:00:00-04:00",
      "next_run_at": "2026-04-09T08:00:00-04:00",
      "tags": ["reporting", "daily"]
    }
  ],
  "total": 1,
  "limit": 50
}
Get Execution History

Request:

{
  "job_id": "job_abc123",
  "status": "failed",
  "limit": 20
}

Response:

{
  "executions": [
    {
      "id": "exec_xyz789",
      "job_id": "job_abc123",
      "status": "failed",
      "started_at": "2026-04-08T08:00:00-04:00",
      "completed_at": "2026-04-08T08:05:00-04:00",
      "error_message": "Command exited with code 1",
      "output": "Report generation failed: database connection error",
      "retry_count": 2,
      "is_recovery": false
    }
  ],
  "total": 1,
  "limit": 20
}

Non-Functional Requirements

Performance
Reliability
Scalability
Compatibility

Technical Design

Architecture Overview

The ScheduleCronTool follows a modular architecture with clear separation of concerns:

┌─────────────────────────────────────────────────────────┐
│                    ScheduleCronTool                      │
├─────────────────────────────────────────────────────────┤
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │   Scheduler  │  │   Executor   │  │   Storage    │   │
│  │    Engine    │  │    Service   │  │    Layer     │   │
│  └──────────────┘  └──────────────┘  └──────────────┘   │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │    Lock      │  │  Recovery    │  │    Config    │   │
│  │   Manager    │  │   Service    │  │   Manager    │   │
│  └──────────────┘  └──────────────┘  └──────────────┘   │
└─────────────────────────────────────────────────────────┘
Core Components

Scheduler Engine

Executor Service

Storage Layer

Lock Manager

Recovery Service


Examples

Example 1: Daily Email Report Job

Schedule a job that sends a daily report at 9:00 AM in Europe/London timezone:

result = await tool.schedule_job(
    name="daily-email-report",
    cron_expression="0 9 * * *",
    timezone="Europe/London",
    command="/scripts/send_daily_report.py",
    args={
        "recipients": ["team@example.com"],
        "format": "html",
        "include_charts": True
    },
    miss_policy="run_once",
    concurrency_policy="forbid",
    timeout_seconds=600,
    description="Send daily sales report to team",
    tags=["reporting", "email", "daily"]
)
Example 2: Hourly Health Check with Recovery

Configure a critical health check that runs every hour with recovery enabled:

result = await tool.schedule_job(
    name="service-health-check",
    cron_expression="0 * * * *",
    timezone="UTC",
    command="/scripts/health_check.sh",
    args={"service": "api-gateway", "threshold": 5},
    miss_policy="run_all",
    concurrency_policy="forbid",
    max_retries=3,
    retry_delay_seconds=30,
    timeout_seconds=30,
    enabled=True,
    tags=["monitoring", "critical"]
)
Example 3: Weekly Database Backup

Schedule a weekly backup job that runs on Sunday at 2:00 AM:

result = await tool.schedule_job(
    name="weekly-database-backup",
    cron_expression="0 2 * * 0",
    timezone="America/Los_Angeles",
    command="/usr/local/bin/backup_db.sh",
    args={
        "database": "production",
        "compression": "gzip",
        "retention_days": 30
    },
    miss_policy="skip",
    concurrency_policy="forbid",
    max_retries=2,
    timeout_seconds=3600,
    description="Weekly production database backup",
    tags=["backup", "database", "weekly", "critical"]
)
Example 4: Monitoring and Alerts

Get execution history and set up alerting for failed jobs:

# Get recent failed executions
executions = await tool.get_execution_history(
    job_id="job_abc123",
    status="failed",
    limit=10
)

# Check next scheduled runs
next_runs = await tool.get_next_runs(
    job_id="job_abc123",
    count=5
)
for run_time in next_runs:
    print(f"Next run: {run_time}")

Integration Points

External System Integrations

Redis (Distributed Locking)

SQLite Database

Command Execution

API Integration Patterns

Direct Tool Usage

tool = ScheduleCronTool(config)
await tool.schedule_job(name="my-job", cron_expression="0 * * * *", command="echo hello")

REST API Exposure

Event-Driven Integration


Data Models

CronJob Schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "CronJob",
  "type": "object",
  "required": ["id", "name", "cron_expression", "timezone", "command"],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^job_[a-zA-Z0-9_]{8,}$"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$"
    },
    "cron_expression": {
      "type": "string",
      "description": "Cron expression: minute hour day month weekday. Supports numbers, *, ranges (1-5), steps (*/5), lists (1,3,5), and extensions (L, W, # for advanced scheduling)"
    },
    "timezone": {
      "type": "string",
      "pattern": "^[A-Za-z]+/[A-Za-z_]+|[+-]\\d{2}:\\d{2}$"
    },
    "command": {
      "type": "string",
      "minLength": 1,
      "maxLength": 4096
    },
    "args": {
      "type": "object"
    },
    "enabled": {
      "type": "boolean",
      "default": true
    },
    "miss_policy": {
      "type": "string",
      "enum": ["skip", "run_once", "run_all", "notify_only"],
      "default": "run_once"
    },
    "concurrency_policy": {
      "type": "string",
      "enum": ["allow", "skip", "queue", "forbid"],
      "default": "allow"
    },
    "max_retries": {
      "type": "integer",
      "minimum": 0,
      "maximum": 10,
      "default": 3
    },
    "retry_delay_seconds": {
      "type": "integer",
      "minimum": 0,
      "maximum": 3600,
      "default": 60
    },
    "timeout_seconds": {
      "type": "integer",
      "minimum": 1,
      "maximum": 86400,
      "default": 300
    },
    "description": {
      "type": "string",
      "maxLength": 1024
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$"
      },
      "maxItems": 20
    },
    "created_at": {
      "type": "string",
      "format": "date-time"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time"
    },
    "last_run_at": {
      "type": ["string", "null"],
      "format": "date-time"
    },
    "next_run_at": {
      "type": ["string", "null"],
      "format": "date-time"
    }
  }
}
JobExecution Schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "JobExecution",
  "type": "object",
  "required": ["id", "job_id", "status", "started_at"],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^exec_[a-zA-Z0-9_]{8,}$"
    },
    "job_id": {
      "type": "string",
      "pattern": "^job_[a-zA-Z0-9_]{8,}$"
    },
    "status": {
      "type": "string",
      "enum": ["pending", "running", "completed", "failed", "cancelled", "recovered"]
    },
    "started_at": {
      "type": "string",
      "format": "date-time"
    },
    "completed_at": {
      "type": ["string", "null"],
      "format": "date-time"
    },
    "error_message": {
      "type": ["string", "null"]
    },
    "output": {
      "type": ["string", "null"]
    },
    "retry_count": {
      "type": "integer",
      "minimum": 0,
      "maximum": 10
    },
    "is_recovery": {
      "type": "boolean",
      "default": false
    },
    "execution_node": {
      "type": ["string", "null"]
    }
  }
}
ScheduleJobRequest Schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "ScheduleJobRequest",
  "type": "object",
  "required": ["name", "cron_expression", "command"],
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "cron_expression": {
      "type": "string"
    },
    "timezone": {
      "type": "string",
      "default": "UTC"
    },
    "command": {
      "type": "string"
    },
    "args": {
      "type": "object"
    },
    "miss_policy": {
      "type": "string",
      "enum": ["skip", "run_once", "run_all", "notify_only"],
      "default": "run_once"
    },
    "concurrency_policy": {
      "type": "string",
      "enum": ["allow", "skip", "queue", "forbid"],
      "default": "allow"
    },
    "max_retries": {
      "type": "integer",
      "minimum": 0,
      "maximum": 10,
      "default": 3
    },
    "retry_delay_seconds": {
      "type": "integer",
      "minimum": 0,
      "maximum": 3600,
      "default": 60
    },
    "timeout_seconds": {
      "type": "integer",
      "minimum": 1,
      "maximum": 86400,
      "default": 300
    },
    "description": {
      "type": "string",
      "maxLength": 1024
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 20
    },
    "enabled": {
      "type": "boolean",
      "default": true
    }
  }
}

Security Requirements

SR-001: Timezone Handling
SR-002: Missed Job Recovery
SR-003: Concurrent Execution Protection
SR-004: Command Execution Security
SR-005: Audit Logging
SR-006: Access Control

Testing Requirements

Unit Tests
Integration Tests
E2E Tests
Test Coverage Requirements

Dependencies

Runtime Dependencies
Package Version Purpose
croniter >=2.0.0 Cron expression parsing
pytz >=2024.1 Timezone handling
apscheduler >=8.0.0 Background scheduling
aiosqlite >=0.19.0 Async SQLite operations
redis >=5.0.0 Distributed locking
pydantic >=2.0.0 Data validation
Development Dependencies
Package Version Purpose
pytest >=8.0.0 Testing framework
pytest-asyncio >=0.23.0 Async test support
pytest-cov >=4.0.0 Coverage reporting
httpx >=0.27.0 HTTP client for API tests

Configuration

scheduler:
  database:
    path: "./data/scheduler.db"
    pool_size: 5
  
  execution:
    default_timeout: 300
    max_concurrent: 10
    retry_delay: 60
  
  recovery:
    enabled: true
    max_batch: 100
    check_interval: 300
  
  locking:
    provider: "redis"
    redis_url: "redis://10.53.2.11:6379"  # example: test fleet; in practice from the active ORCHESTRATA_ENV_PROFILE (RUL-003, no hardcoded default)
    lock_timeout: 600
    lock_retry_delay: 1
  
  logging:
    level: "INFO"
    format: "json"
    output: "stdout"
    retention_days: 90
  
  security:
    command_whitelist_enabled: false
    allowed_commands:
      - "/scripts/*.sh"
      - "/usr/bin/python3"
    run_as_user: "scheduler"

Governance

% Job scheduling governance rules
may_schedule_job(Agent, Job) :-
    not restricted_agent(Agent),
    has_permission(Agent, schedule_jobs).

may_execute_job(Agent, Job) :-
    may_schedule_job(Agent, Job),
    job_enabled(Job),
    not job_paused(Job).

may_modify_job(Agent, Job) :-
    owner(Agent, Job),
    has_permission(Agent, modify_jobs).

may_delete_job(Agent, Job) :-
    owner(Agent, Job),
    has_permission(Agent, delete_jobs).

% Restricted execution contexts
restricted_agent(ai_agent).
restricted_agent(automated_script) :- not whitelisted_script(script_id).

% Job ownership
owner(Agent, Job) :- created_by(Job, Agent).
owner(admin, Job) :- system_job(Job).

Success Criteria

AC-001: Job Scheduling
AC-002: Timezone Handling
AC-003: Recovery
AC-004: Concurrency
AC-005: Execution
AC-006: Management

Observability

Logging Requirements
  1. Component Logging
    • Log all major operations with appropriate log levels
    • Include correlation IDs for request tracing
    • Log performance metrics for critical operations
    • Log errors with full context for debugging
Metrics Requirements
  1. Performance Metrics
    • Operation duration histograms
    • Request/error rate counters
    • Resource utilization gauges
    • Business metrics relevant to domain
Tracing Requirements
  1. Distributed Tracing
    • Trace propagation across component boundaries
    • Span attributes for domain-specific context
    • Trace sampling based on operational needs
Alerting Requirements
  1. Critical Alerts
    • Error rate spikes
    • Performance degradation
    • Resource exhaustion
    • Business metric anomalies
Dashboard Requirements
  1. Operational Dashboard
    • Real-time system health
    • Performance trends
    • Error analysis
    • Resource monitoring

Revision History

Version Date Author Changes
1.0.0 2026-04-09 Mohamed Initial spec creation

End of SPEC-068