125 lines
3.3 KiB
Plaintext
125 lines
3.3 KiB
Plaintext
-- allium: 1
|
|
-- bDS Background Task Manager
|
|
-- Scope: core (Wave 1)
|
|
-- Distilled from: src/main/engine/TaskManager.ts
|
|
|
|
entity Task {
|
|
name: String
|
|
status: pending | running | completed | failed | cancelled
|
|
progress: Decimal? -- 0.0..1.0
|
|
message: String?
|
|
group_id: String? -- Optional task grouping
|
|
group_name: String?
|
|
created_at: Timestamp
|
|
|
|
transitions status {
|
|
pending -> running
|
|
running -> completed
|
|
running -> failed
|
|
running -> cancelled
|
|
}
|
|
}
|
|
|
|
surface TaskControlSurface {
|
|
facing _: TaskOperator
|
|
|
|
provides:
|
|
SubmitTaskRequested(name, work)
|
|
CancelTaskRequested(task)
|
|
RegisterExternalTaskRequested(name)
|
|
}
|
|
|
|
surface TaskRuntimeSurface {
|
|
facing _: TaskRuntime
|
|
|
|
provides:
|
|
TaskWorkCompleted(task)
|
|
TaskWorkFailed(task, error_message)
|
|
ProgressReported(task, value, message)
|
|
}
|
|
|
|
surface TaskSurface {
|
|
context task: Task
|
|
|
|
exposes:
|
|
task.name
|
|
task.status
|
|
task.progress when task.progress != null
|
|
task.message when task.message != null
|
|
task.group_id when task.group_id != null
|
|
task.group_name when task.group_name != null
|
|
task.created_at
|
|
}
|
|
|
|
config {
|
|
max_concurrent: Integer = 3
|
|
progress_throttle: Duration = 250.milliseconds
|
|
}
|
|
|
|
invariant MaxConcurrency {
|
|
-- At most max_concurrent tasks run simultaneously
|
|
let running_tasks = Tasks where status = running
|
|
running_tasks.count <= config.max_concurrent
|
|
}
|
|
|
|
invariant FifoQueue {
|
|
-- When max concurrent reached, new tasks queue in FIFO order
|
|
-- Queued tasks transition to running as slots open
|
|
}
|
|
|
|
rule SubmitTask {
|
|
when: SubmitTaskRequested(name, work)
|
|
let running_tasks = Tasks where status = running
|
|
ensures:
|
|
let task = Task.created(name: name, status: pending)
|
|
task.status = pending
|
|
if running_tasks.count < config.max_concurrent:
|
|
task.status = running
|
|
TaskStarted(task, work)
|
|
}
|
|
|
|
rule CompleteTask {
|
|
when: TaskWorkCompleted(task)
|
|
ensures: task.status = completed
|
|
ensures: task.progress = 1.0
|
|
ensures: NextQueuedTaskStarted()
|
|
}
|
|
|
|
rule FailTask {
|
|
when: TaskWorkFailed(task, error_message)
|
|
ensures: task.status = failed
|
|
ensures: task.message = error_message
|
|
ensures: NextQueuedTaskStarted()
|
|
}
|
|
|
|
rule CancelTask {
|
|
when: CancelTaskRequested(task)
|
|
requires: task.status = running or task.status = pending
|
|
-- Cancellation uses a runtime-specific cancellation mechanism
|
|
ensures: task.status = cancelled
|
|
ensures: NextQueuedTaskStarted()
|
|
}
|
|
|
|
rule ReportProgress {
|
|
when: ProgressReported(task, value, message)
|
|
-- Progress events throttled to 250ms
|
|
ensures: task.progress = value
|
|
ensures: task.message = message
|
|
}
|
|
|
|
invariant ProgressThrottled {
|
|
-- Progress update events are throttled to prevent UI flooding
|
|
-- At most one progress event per 250ms per task
|
|
}
|
|
|
|
-- External tasks: lifecycle controlled by caller (e.g., renderer-side scripts)
|
|
rule RegisterExternalTask {
|
|
when: RegisterExternalTaskRequested(name)
|
|
ensures:
|
|
let task = Task.created(name: name, status: running)
|
|
task.status = running
|
|
@guidance
|
|
-- External tasks are not managed by the queue
|
|
-- The caller is responsible for updating status
|
|
}
|