feat(enabled): runtime honoring for disabled scripts and routes

Make the `enabled` flag bite at the data plane (§4.3).

- Routes: `compile_routes` drops disabled rows from the match table, so a
  request to a disabled route 404s indistinguishably from an absent one
  (no info leak). Rebuilt on every route write + at startup, as today.
- Scripts: the orchestrator's two execute paths — `/execute/{id}` bypass
  and the user-route handler — 404 when the resolved script is disabled.
  The route-handler check also covers an enabled-route-bound-to-disabled-
  script (§4.7): the binding is unreachable rather than 500/executing.

Still pending: the dispatcher fire-time re-check for already-enqueued
outbox rows (next commit).

Tested: route_admin disabled-route-dropped unit test + orchestrator suite
(75) green; clippy -D warnings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-23 19:42:45 +02:00
parent 55cf995eda
commit 4223d3c320
2 changed files with 27 additions and 0 deletions

View File

@@ -121,6 +121,11 @@ where
.resolve(id)
.await?
.ok_or(ApiError::NotFound(id))?;
// A disabled script (§4.3) is not invocable — 404, indistinguishable
// from an absent one (no info leak that the id exists but is off).
if !script.enabled {
return Err(ApiError::NotFound(id));
}
let mut req = build_exec_request(id, &script.name, &headers, &body, script.app_id, principal)?;
req.sandbox_overrides = script.sandbox;
@@ -164,6 +169,7 @@ where
Ok(exec_response_to_http(outcome?))
}
#[allow(clippy::too_many_lines)]
async fn user_route_handler<E, R>(
State(state): State<DataPlaneState<E, R>>,
Extension(principal): Extension<Option<Principal>>,
@@ -221,6 +227,12 @@ where
.resolve(matched.matched.script_id)
.await?
.ok_or(ApiError::NotFound(matched.matched.script_id))?;
// An enabled route bound to a disabled script (§4.3, §4.7) is
// unreachable: 404 as if no route matched (the disabled route is already
// dropped from the match table; this covers the route-on/script-off case).
if !script.enabled {
return Err(ApiError::NotFound(matched.matched.script_id));
}
// Drain the body now that we know we'll execute. 10 MiB cap matches
// the conservative default response/request size in the blueprint.