mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
[sandbox-ext] Phase 3: parameter surface — schema + project field + verb override + cache-by-features
Migration 072 adds projects.sandbox_extensions (jsonb null): a per-service
extension/module map a venture declares up front (e.g. {"postgres":
["vector","postgis"],"redis":["search"]}). Additive + nullable so
existing opted-in projects stay byte-for-byte bare — no default set, opters
set the extensions they need explicitly (TimescaleDB out unless asked).
Project model validates the map against SANDBOX_ENGINE_FEATURES: unknown
service keys and unallowed features are rejected at the model boundary with
the allowlist named (plpython3u — superuser-RCE — excluded by construction),
empty feature lists drop to bare, order normalized + deduped. The allowlist
is the security containment, not privilege. Mirrors sandbox_services: not on
ProjectCreate, only Project + ProjectUpdate.
request_sandbox gains an extensions arg; _sandbox_features_scope unions a
per-call override with the project's standing set (trusted), bounds it to the
opted set + allowlist, rejects a non-opted service or unallowed feature with
the allowlist named in remediate — scope-first priority preserved by
rej_scope or rej_features. ensure_sandbox threads features through to
provision(); cache-by-features: a cached entry satisfies a new call iff
services are a subset AND every requested feature per service is already
cached — a feature superset re-provisions (rotates creds), mirroring the
services-superset case. available_extensions rides the evidence payload so an
agent doesn't guess what was activated.
Gate: ruff clean, mypy clean (9 modules), 51 tests pass (incl. migration
round-trip).
This commit is contained in:
+16
-8
@@ -726,22 +726,30 @@ def evidence(task_id: str) -> dict[str, Any]:
|
||||
return _post("/api/v1/do/evidence", {"task_id": task_id})
|
||||
|
||||
|
||||
def request_sandbox(services: list[str] | None = None) -> dict[str, Any]:
|
||||
def request_sandbox(
|
||||
services: list[str] | None = None,
|
||||
extensions: dict[str, list[str]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Provision (or reuse) a throwaway sandbox DB/Redis/Mongo for YOUR active task.
|
||||
|
||||
On-demand — nothing is provisioned at spawn. Omit ``services`` to get the
|
||||
project's whole opted-in set; requesting a service the project didn't opt
|
||||
into is rejected with the allowed set named. Creds come back in
|
||||
into is rejected with the allowed set named. ``extensions`` (e.g.
|
||||
``{"postgres": ["vector", "postgis"]}``) is an additive per-call override
|
||||
unioned with the project's standing ``sandbox_extensions`` and bounded by
|
||||
the opted set + the allowlist — a name outside the allowlist (e.g.
|
||||
``plpython3u``) is rejected with the allowed set named. Creds come back in
|
||||
``evidence``, one entry per service: ``{host, port, user, password,
|
||||
database, env: {ROBOCO_TEST_*: value}}`` — export the ``env`` values
|
||||
verbatim for gate tooling that reads them. The whole opted-in set is
|
||||
provisioned on first call, so calling this again for any subset or
|
||||
superset of it is a cheap no-op (same creds, no re-provisioning); a
|
||||
project that never opted into sandbox services will reject this.
|
||||
database, env: {ROBOCO_TEST_*: value}, available_extensions?: [...]}`` —
|
||||
export the ``env`` values verbatim for gate tooling that reads them. The
|
||||
whole opted-in set is provisioned on first call, so calling this again for
|
||||
any subset or superset of it is a cheap no-op (same creds, no
|
||||
re-provisioning); a project that never opted into sandbox services will
|
||||
reject this.
|
||||
"""
|
||||
return _post(
|
||||
"/api/v1/do/request_sandbox",
|
||||
{"services": services},
|
||||
{"services": services, "extensions": extensions},
|
||||
timeout=_SANDBOX_TIMEOUT,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user