Files
tireless/Cargo.toml
rob thijssen 98f193d16b
All checks were successful
deploy / deploy (push) Successful in 6m1s
feat(data): implement JobStore and the Gitea read client
Closes #3 and #4.

Both specs had gaps that only appeared on contact, which is worth recording
because it is evidence for the plan-quality question in #10:

- `enqueue(&[DiscoveredIssue])` cannot know a JobKind. It is derivable from
  labels, so the store now carries the LabelProtocol and core gained
  `job_kind_for`. The precedence when an operator applies several mode labels
  had to be decided: plan beats implement, because planning produces the
  implementation children and so loses nothing, while the reverse silently
  discards the decomposition that was also asked for.
- `claim_next(worker, allowed_lanes)` had no lane to filter on. Migration 0002
  adds one, recorded at enqueue by `routing::lane_for` — the same function
  `route` now delegates to, so the two cannot drift. Deriving it at claim time
  instead would mean a forge request per claim, since labels are not stored.
- `list_opted_in_issues` returned a bare Vec with nowhere to put the ETag that
  #4's own step 3 requires, so a caller could not make the next poll
  conditional. It returns an `IssuePage` now, which also carries `not_modified`
  — a 304 is not an empty repo, and a poller that conflated them would treat
  every quiet poll as every issue having disappeared.

The claim is one statement: a CTE takes the row lock with SKIP LOCKED and the
update writes the claim, so select and update share a transaction without
managing one by hand. Returning a job to pending clears the claim, because
`pending_holds_no_claim` refuses the half-done version — the database is what
makes lease expiry safe rather than the code remembering to.

Enum values round-trip through serde rather than a hand-written match, so the
schema's check constraints and the Rust types are provably one vocabulary.

Also replaced a test from #2 that asserted exactly one migration exists. It
failed on the first legitimate migration, which teaches people to edit the
assertion rather than think. It now asserts what it was reaching for: versions
unique and ascending.

Verified against Postgres 18 — 15 database tests covering enqueue idempotency
across repeated polls, lane filtering including the agent:oc override, claim
metadata, lease expiry and reclaim, terminal jobs refusing transition, renewal
requiring you still hold the claim, and re-routing a queued job when its labels
change. Plus 12 mock-forge tests: If-None-Match sent and 304 distinguished from
empty, ETag surfaced, 429 retried but bounded, 503 retried then succeeding, 404
not retried, pull requests filtered out, and every unimplemented write failing
without touching the forge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013TxK1CWPkFXqdcXMJ4hVe6
2026-08-07 20:20:09 +03:00

72 lines
1.8 KiB
TOML

[workspace]
resolver = "3"
members = ["crates/*"]
[workspace.package]
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
license = "GPL-3.0-or-later"
authors = ["Rob Thijssen <rob@rob.tn>"]
[workspace.dependencies]
# internal
tireless-entities = { path = "crates/tireless-entities", version = "=0.1.0" }
tireless-core = { path = "crates/tireless-core", version = "=0.1.0" }
tireless-data = { path = "crates/tireless-data", version = "=0.1.0" }
tireless-agent = { path = "crates/tireless-agent", version = "=0.1.0" }
# runtime
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7", features = ["io", "rt"] }
tokio-stream = { version = "0.1", features = ["io-util"] }
futures = "0.3"
async-trait = "0.1"
# web
axum = { version = "0.8", features = ["ws", "macros"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["trace", "cors"] }
# data
sqlx = { version = "0.8", default-features = false, features = [
"postgres",
"runtime-tokio-rustls",
"macros",
"migrate",
"chrono",
"uuid",
"json",
] }
# serde / types
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
ts-rs = { version = "10", features = ["chrono-impl", "uuid-impl", "url-impl"] }
# http client
reqwest = { version = "0.12", default-features = false, features = [
"json",
"rustls-tls",
"stream",
] }
# process control
command-group = { version = "5", features = ["with-tokio"] }
# observability + errors
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
thiserror = "2"
anyhow = "1"
# misc
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4", "serde"] }
clap = { version = "4", features = ["derive", "env"] }
figment = { version = "0.10", features = ["toml", "env"] }
rand = "0.8"
wiremock = "0.6"
url = { version = "2", features = ["serde"] }