feat(workflows): M5 — SDK workflow::start + admin run API + pic CLI
The user-facing surface for the durable DAG engine. A workflow run can now be
started three ways, all resolving the workflow by name in the caller's app
(`cx.app_id` / the resolved app — never a passed-in arg, the isolation
boundary), seeding a run the orchestrator advances.
SDK seam (shared):
- `WorkflowService` trait + `NoopWorkflowService` + `WorkflowError` in
`shared::workflow` (mirrors `InvokeService`); added to `Services` as a
noop-defaulted `with_workflow` builder (all `Services::new` call sites
unchanged).
- `WorkflowServiceImpl` (manager-core): resolves + seeds a run; authenticated
callers gated on `AppInvoke` (anonymous skips, script-as-gate), the same gate
`invoke()` uses.
- `executor-core::sdk::workflow` — the Rhai bridge `workflow::start(name, input)`
/ `workflow::start(name)`, registered in `register_all`. Returns the run id.
Admin API (manager-core `workflows_api`, mounted in the binary):
- `GET /apps/{app}/workflows` — definitions (AppRead)
- `POST /apps/{app}/workflows/{name}/runs` — start a run (AppInvoke)
- `GET /apps/{app}/workflows/{name}/runs` — run history (AppRead)
- `GET /apps/{app}/workflow-runs/{run_id}` — one run + its steps (AppRead)
`app_id` scopes every query; a foreign run 404s.
CLI (`pic workflows`): `ls` · `run <name> [--input JSON]` · `runs <name>` ·
`run-status <run_id>` — all `--app`-scoped; run definitions stay declarative
(`[[workflows]]` → `pic apply`).
Also: `list_runs_for_workflow` repo reader.
Tests: `workflow_service_start_seeds_a_run` (DB-gated — the SDK/API-shared
entry) + `workflow_run_executes_end_to_end` CLI journey (apply → `pic workflows
run` → poll `run-status` → succeeded, through the real binary + orchestrator).
fmt + clippy -D warnings clean, 449 manager-core lib tests, 14 orchestrator DB
tests, 18 executor-core lib tests, 3 workflow CLI journeys green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -411,6 +411,11 @@ pub async fn build_app(
|
||||
);
|
||||
let vars: Arc<dyn picloud_shared::VarsService> =
|
||||
Arc::new(VarsServiceImpl::new(pool.clone(), authz.clone()));
|
||||
// v1.2 Workflows (M5): `workflow::start(name, input)`. Gated on AppInvoke
|
||||
// for authenticated callers, like invoke() (anonymous skips it).
|
||||
let workflow: Arc<dyn picloud_shared::WorkflowService> = Arc::new(
|
||||
picloud_manager_core::WorkflowServiceImpl::new(pool.clone()).with_authz(authz.clone()),
|
||||
);
|
||||
let services = Services::new(
|
||||
kv,
|
||||
docs,
|
||||
@@ -431,7 +436,8 @@ pub async fn build_app(
|
||||
.with_group_docs(group_docs)
|
||||
.with_group_files(group_files)
|
||||
.with_group_pubsub(group_pubsub)
|
||||
.with_group_queue(group_queue);
|
||||
.with_group_queue(group_queue)
|
||||
.with_workflow(workflow);
|
||||
// v1.1.9: keep the invoke depth bound aligned with the dispatcher's
|
||||
// trigger-depth bound (same counter under the hood).
|
||||
let engine_limits = Limits {
|
||||
@@ -569,6 +575,12 @@ pub async fn build_app(
|
||||
config: trigger_config,
|
||||
master_key: master_key.clone(),
|
||||
};
|
||||
// v1.2 Workflows (M5): list definitions, start runs, read run history.
|
||||
let workflows_state = picloud_manager_core::WorkflowsState {
|
||||
pool: pool.clone(),
|
||||
apps: apps_repo.clone(),
|
||||
authz: authz.clone(),
|
||||
};
|
||||
// Declarative reconcile engine (pic plan / apply). Trait-object repos
|
||||
// for the read/diff path; shares the same handles as the CRUD routers.
|
||||
let apply_service = ApplyService {
|
||||
@@ -738,6 +750,7 @@ pub async fn build_app(
|
||||
))
|
||||
.merge(api_keys_router(api_keys_state))
|
||||
.merge(triggers_router(triggers_state))
|
||||
.merge(picloud_manager_core::workflows_router(workflows_state))
|
||||
.merge(apply_router(apply_service))
|
||||
.merge(projects_router(ProjectsState {
|
||||
projects: projects_repo.clone(),
|
||||
|
||||
Reference in New Issue
Block a user