Files
clawmates/crates/cm-api/Cargo.toml
T
Omar Sobh 4b48c521eb
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 24s
ci / rust (push) Successful in 3m43s
ci / publish (push) Successful in 2m15s
ci / e2e (push) Failing after 29m57s
loops: backend routes + repo + cron scheduler + HMAC webhook
Third commit of the Research + Loops arc. Lights up loops as durable
recurring topology executions:

  GET    /api/loops                 list workspace's loops
  POST   /api/loops                 create — returns webhook_token +
                                    signing_key ONCE when webhook trigger
                                    is enabled; never exposed again
  GET    /api/loops/:id             detail
  PATCH  /api/loops/:id             update definition
  DELETE /api/loops/:id             delete
  POST   /api/loops/:id/run         trigger one iteration NOW
  POST   /api/loops/:id/enable      set enabled=true
  POST   /api/loops/:id/disable     set enabled=false
  POST   /webhooks/loops/:token     public; HMAC-SHA256-verified

Scheduler (cm_runtime::spawn_loop_scheduler) wakes every 10s, queries the
partial index on (next_fire_at) for due loops, enqueues one topology_runs
row per fire with loop_id + iteration + parent_run_id chained back to the
previous iteration. Uses croner via the existing scheduling::next_occurrence
helper. Missed windows fire ONCE and skip the backlog — next_fire_at is
always computed strictly AFTER now(), so a late scheduler doesn't drain a
buildup.

Webhook signatures follow the same pattern as the Stripe billing webhook
(HMAC-SHA256 with constant-time hex compare). Token + signing key are
24-byte OS-RNG values; the URL uses base64-url for the token, and the
signing key is base64-std. Both surface exactly once at create time.

All three fire paths (scheduler, immediate-run, webhook) funnel through
`cm_db::repo::loops::enqueue_iteration` so the invariants stay in one
place. `iters` repeat policy is enforced by the scheduler tick; `until`
and `on_completion` land with the orchestrator hook in commit 4.

Adds cm-llm as a direct cm-api dep, getrandom for the webhook material
generator, and wires the scheduler spawn into the server binary alongside
the resume sweeper and outbox drainer.
2026-07-06 04:39:27 -07:00

65 lines
1.8 KiB
TOML

[package]
name = "cm-api"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
getrandom = "0.2"
hex = "0.4"
hmac = "0.12"
sha2 = "0.10"
base64 = "0.22"
async-stream = "0.3"
axum = { version = "0.8", features = ["ws"] }
futures = "0.3"
serde = { workspace = true }
serde_json = { workspace = true }
sqlx = { workspace = true }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
cm-auth = { path = "../cm-auth" }
cm-billing = { path = "../cm-billing" }
cm-brain = { path = "../cm-brain" }
cm-config = { path = "../cm-config" }
cm-db = { path = "../cm-db" }
cm-domain = { path = "../cm-domain" }
cm-llm = { path = "../cm-llm" }
cm-orchestrator = { path = "../cm-orchestrator", features = ["provider"] }
cm-runtime = { path = "../cm-runtime" }
cm-sandbox = { path = "../cm-sandbox" }
cm-safety = { path = "../cm-safety" }
async-trait = "0.1"
cm-scheduler = { path = "../cm-scheduler" }
cm-secrets = { path = "../cm-secrets" }
cm-topology = { path = "../cm-topology" }
thiserror = { workspace = true }
tokio-tungstenite = { version = "0.26", features = ["rustls-tls-webpki-roots"] }
tower-http = { version = "0.6", features = ["trace"] }
time = { workspace = true }
tokio = { workspace = true }
urlencoding = "2"
uuid = { workspace = true }
[dev-dependencies]
axum = { version = "0.8", features = ["ws"] }
jsonwebtoken = "9"
eventsource-stream = "0.2"
reqwest = { version = "0.12", default-features = false, features = [
"json",
"rustls-tls",
"stream",
] }
cm-llm = { path = "../cm-llm" }
cm-testkit = { path = "../cm-testkit" }
hex = "0.4"
hmac = "0.12"
base64 = "0.22"
rand_core = { version = "0.6", features = ["getrandom"] }
rsa = { version = "0.9", features = ["pem"] }
sha2 = "0.10"
[lints]
workspace = true