Adds jray-project as a submodule at scripts/vendor/jray-project, so this repo runs the same extractor as every other component rather than its own copy, and gains the system spec that defines the PR/SR requirements its register traces up to. scripts/traceability-gate.sh is a thin wrapper holding only what is specific to this repo: UR/DR prefixes, .rs sources, and REPO_ROOT — which the shared gate cannot infer once vendored, since its default resolves to the submodule itself. Each override fails silently in a way that looks like "no work done" rather than "misconfigured", so the wrapper documents why each is needed. Annotates 35 units with TRACES tags, on the code that decides rather than every helper it calls. Coverage is 23/32 (71.9%) with no orphan tags. The nine untraced are genuinely unimplemented: UR-007 is plugin-side, UR-008 is federation, and UR-015..018 are the pending SR-003 schema bump. The gate caught a real error in the first pass: several tags separated IDs of different types with commas. A comma joins IDs within one type; a pipe separates types. Fixed, and the diagnostics are now clean. MIN_COVERAGE stays 0 deliberately. The gate still fails on orphan tags, a >100% ratio, a register parsing to nothing, or an empty source scan — raise the threshold as a ratchet once the remaining work lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
68 lines
2.6 KiB
Rust
68 lines
2.6 KiB
Rust
//! Router construction.
|
|
//!
|
|
//! §6 stage 0/1 body caps are applied here as per-route `DefaultBodyLimit`
|
|
//! layers: Axum rejects on `Content-Length` before reading a body *and* caps the
|
|
//! stream for chunked or mis-declared uploads, which is what makes a lying
|
|
//! header and a chunked upload both safe. Per-route means the bundle endpoint
|
|
//! gets its larger limit without widening the others (§6 stage 1).
|
|
|
|
use std::time::Duration;
|
|
|
|
use axum::extract::DefaultBodyLimit;
|
|
use axum::routing::{get, post};
|
|
use axum::Router;
|
|
use tower_http::timeout::TimeoutLayer;
|
|
use tower_http::trace::TraceLayer;
|
|
|
|
use crate::api::{exists, fetch, report, upload};
|
|
use crate::state::AppState;
|
|
use crate::validate::limits;
|
|
|
|
/// Small cap for endpoints that take a short JSON body. A read endpoint has no
|
|
/// business accepting a large payload, and the batch `exists` form is bounded at
|
|
/// 100 items.
|
|
const SMALL_BODY_LIMIT: usize = 256 * 1024;
|
|
|
|
/// TRACES: DR-009, DR-013 | SR-004
|
|
pub fn router(state: AppState) -> Router {
|
|
let timeout = state.config.request_timeout;
|
|
|
|
let v1 = Router::new()
|
|
// UR-1 — existence probes.
|
|
.route("/manifests/exists", get(exists::exists).post(exists::exists_batch))
|
|
// Reads.
|
|
.route("/manifests/movie", get(fetch::get_movie))
|
|
.route("/manifests/episode", get(fetch::get_episode))
|
|
.route("/manifests/series/{series_tmdb_id}", get(fetch::get_series))
|
|
.route("/manifests/{id}", get(fetch::get_by_id))
|
|
.route("/manifests/{id}/status", get(fetch::get_status))
|
|
.route("/manifests/{id}/report", post(report::post_report))
|
|
// UR-2 — contribution.
|
|
.route(
|
|
"/manifests",
|
|
post(upload::post_manifest).layer(DefaultBodyLimit::max(limits::BODY_LIMIT_MANIFEST)),
|
|
)
|
|
// UR-6 — whole-series contribution, with its own larger cap.
|
|
.route(
|
|
"/manifests/bundle",
|
|
post(upload::post_bundle).layer(DefaultBodyLimit::max(limits::BODY_LIMIT_BUNDLE)),
|
|
)
|
|
// §5a — anonymous bearer capability, not an account.
|
|
.route("/tokens", post(upload::post_token))
|
|
.layer(DefaultBodyLimit::max(SMALL_BODY_LIMIT));
|
|
|
|
Router::new()
|
|
.route("/health", get(report::health))
|
|
.route("/ready", get(report::ready))
|
|
.nest("/api/v1", v1)
|
|
// §8: a request timeout so a slow bundle query fails fast.
|
|
.layer(TimeoutLayer::with_status_code(axum::http::StatusCode::REQUEST_TIMEOUT, timeout))
|
|
.layer(TraceLayer::new_for_http())
|
|
.with_state(state)
|
|
}
|
|
|
|
/// Convenience for tests and `main`.
|
|
pub fn default_timeout() -> Duration {
|
|
Duration::from_secs(30)
|
|
}
|