Skip to content

feat(router): promote stateless sandbox evaluator to production, keep /share out (#3825) - #3826

Draft
PetrefiedThunder wants to merge 2 commits into
mainfrom
claude/restore-sandbox-front-door-96801d
Draft

PetrefiedThunder wants to merge 2 commits into
mainfrom
claude/restore-sandbox-front-door-96801d

Conversation

@PetrefiedThunder

Copy link
Copy Markdown
Collaborator

Summary

  • Promotes sandbox (the stateless /evaluate + /trace pair) into PRODUCTION_ENABLED_ROUTERS, so the Inflow Lab CSV checker stops 404ing in production.
  • Splits the persisted /share endpoints into sandbox/share_router.py under a new, unpromoted sandbox_share owner, so promotion does not drag an unauthenticated write path onto the production surface.
  • Adds promotion-gate evidence per PRODUCTION_SURFACE.md:112-120, including guards that fail if the promoted router ever gains a write path, a tenant field, or a third route.

Why

/tools/inflow-lab ships in production (HTTP 200) and InflowLabClient.tsx:1104 POSTs to /api/ingestion/api/v1/sandbox/evaluate, which always 404'd. frontend/src/lib/experimental-features.ts:77 already documented the symptom: "the sandbox-evaluate feeder inside the page degrades to an inline error." Probed before the change:

POST https://regengine.co/api/ingestion/api/v1/sandbox/evaluate  ->  404
GET  https://regengine.co/tools/inflow-lab                        ->  200

Note this supersedes the original framing: the per-route allowlist this was once going to add already exists, and production now rejects ENABLE_EXPERIMENTAL_ROUTERS outright. The only remaining step was promotion by name.

Why /share is not promoted with it

The allowlist is router-granular, so promoting sandbox as it stood would have mounted all four endpoints. Three independent reasons that was wrong:

  1. Unauthenticated, tenant-less write. POST /share inserts up to 2 MB of caller-supplied CSV — plausibly real supplier data — into sandbox_shares with no auth principal, retained 30 days. The table exists (docs/schema/expected_schema.json:6766), so it would have been live.
  2. Its output is already dead. The response sets share_url=f"/sandbox/results/{share_id}..."; that page was deleted when the public sandbox was retired, so a successful share returns a link to a 404.
  3. The rate limit does not hold. _share_buckets is a per-process dict, so the 10/hour/IP cap is per-replica, not per-deployment.

Inflow Lab only calls /evaluate, so nothing needed /share to ship.

Changes

Area Change
server/router_policy.py sandbox → PRODUCTION_ENABLED_ROUTERS, out of EXPERIMENTAL_ROUTERS; new sandbox_share in VALID_ROUTER_NAMES + EXPERIMENTAL_ROUTERS
server/main.py sandbox_share mounts behind its own _router_enabled gate
sandbox/share_router.py new — the two share endpoints, moved verbatim, with the non-promotion rationale in the module docstring
sandbox/router.py share endpoints and their DB/SQL imports removed; docstring states statelessness is the promotion's basis
server/route_security_policy.py both promoted ops added to PUBLIC_OPERATIONS with a none:stateless-no-persistence boundary
docs/security/route-security-manifest.json regenerated — 261 enabled / 178 omitted
ROUTER_SURFACE.md, PRODUCTION_SURFACE.md, .env.example ENABLED_ROUTERS line plus ownership and rationale sections (both docs are invariant-enforced)

The manifest generator refused the promotion until the operations were classified — RouteManifestError: Unclassified production operation sandbox:POST:/api/v1/sandbox/evaluate. That gate worked as designed.

Type of Change

  • New feature (production surface promotion)
  • Bug fix (shipped page calling a route that cannot answer)
  • Refactor (no behavior change) — the share-endpoint move is verbatim
  • Security fix (keeps an unauthenticated write path off the production surface)

Test Plan

  • Unit tests added/updated — test_sandbox_production_promotion_3825.py, 21 cases: release-gate membership (including sandbox_share must never be in the production allowlist), tenant isolation (no tenant path param, no tenant field on any request model, forged tenant_id/X-Tenant-ID changes nothing, and a source-level assertion that the promoted module contains no shared.database/get_db/sqlalchemy/INSERT/UPDATE/DELETE), contract, and six failure modes.
  • Integration tests pass — full services/ingestion/tests suite: 0 failures/errors. Green: tests/test_router_policy.py, tests/invariants/ (incl. test_router_surface_doc_2838), tests/security/test_route_security_manifest.py, tests/test_server_import_smoke.py, tests/test_monolith_route_parity.py, tests/sdk/test_route_contract.py, tests/contract/test_openapi_production_surface_2861.py, services/shared/tests/test_secrets_fail_closed.py. generate_route_security_manifest.py --check clean. ruff clean on every changed file. Frontend sandbox-cta.test.ts passes.
  • Manual testing performed — verified in the regenerated manifest that sandbox is in production_router_groups with both ops enabled at none:stateless-no-persistence, while both sandbox_share ops sit in disabled_operations; the manifest's own probe confirms all 178 omitted paths return exact 404.

Four existing tests asserted "sandbox 404s in production" — the precise behaviour being changed. Each was updated to assert the new split rather than relaxed: the negative-contract probe now requires /evaluate → 400 and /share → 404.

Not verified here: DB-backed tests skipped for lack of Postgres/Docker in this environment. Per #2530 these must fail rather than skip under REGENGINE_REQUIRE_DB=1, so CI remains the authority. No schema or migration change, so no expected_schema.json regeneration applies.

Checklist

  • My code follows the existing patterns in the codebase — mirrors how proof_verify/proof_recall are classified as public, tenantless operations
  • I have not introduced hardcoded secrets or credentials
  • No sys.modules[X] = stub tests added
  • I have updated relevant documentation — both invariant-enforced docs, with ownership and FSMA rationale as the Promotion Rule requires
  • My changes generate no new warnings or lint errors
  • Tenant isolation preserved — the promoted surface takes no caller-supplied tenant id, and tests enforce that

Deployment note

ENABLED_ROUTERS must be exact in production, so this needs the Railway variable updated to append ,sandbox in the same release. Without it the app refuses to boot (Production ENABLED_ROUTERS does not match the release-approved set exactly: missing=['sandbox']) — fail-closed, by design.

Related Issues

Fixes #3825.

Follow-up, deliberately not bundled: /share is dead functionality pointing at a deleted page. It should be removed outright, or repointed if sharing is still wanted — a product call.



Generated by Claude Code

… /share out (#3825)

/tools/inflow-lab ships in production and POSTs to
/api/ingestion/api/v1/sandbox/evaluate, which always 404'd because `sandbox`
was absent from PRODUCTION_ENABLED_ROUTERS. experimental-features.ts already
documented the symptom: "the sandbox-evaluate feeder inside the page degrades
to an inline error."

Promoting the router as it stood would also have mounted two endpoints that do
not belong on the production surface. POST /share is an unauthenticated,
tenant-less write that stores up to 2MB of caller-supplied CSV in
sandbox_shares for 30 days; its share_url points at /sandbox/results/[id],
a page deleted when the public sandbox was retired, so a successful share
returns a link to a 404; and its 10/hour/IP cap is a per-process dict that
does not hold across replicas.

So split rather than promote wholesale:

- Move /share and /share/{id} into sandbox/share_router.py under a new
  `sandbox_share` owner, which stays experimental and unpromoted.
- Promote `sandbox` (evaluate + trace) into PRODUCTION_ENABLED_ROUTERS. Both
  are stateless: they persist nothing, accept no tenant id, and read no tenant
  data, so there is no tenant boundary to cross. Bounded by a per-IP rate
  limit and 2MB / 500-event caps.
- Classify both as PUBLIC_OPERATIONS with a none:stateless-no-persistence
  storage boundary; the route-complete manifest rejected them as unclassified
  until this was explicit.

Promotion-gate evidence per PRODUCTION_SURFACE.md:112-120 lives in
test_sandbox_production_promotion_3825.py (21 cases): tenant isolation,
contract, failure modes, and guards that fail if a write path, a tenant field,
or a third route is ever added to the promoted router — plus one asserting
sandbox_share is never in the production allowlist.

Verification: full ingestion suite 0 failures; router policy, router-surface
doc invariant, route-security manifest, server import smoke, monolith route
parity, SDK route contract, OpenAPI production surface and tests/invariants
all green; manifest --check clean at 261 enabled / 178 omitted-404.
DB-backed tests skipped locally (no Postgres/Docker in this environment).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014PP3CSvvRruKPuYTrLoXzq
@vercel

vercel Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
regengine Ready Ready Preview Nov 5, 2026 10:32am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Nov 5, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

PR Blocker Checklist

Head SHA: b813d0bd752de8ab4eda3dfde30e81f0dda467a2

Status: Ready

  • All review threads resolved
  • No reviews requesting changes
  • No blocked review engines

@github-actions github-actions Bot added backend security Security vulnerabilities and concerns frontend docs labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

✅ Bundle Size Report

Metric Size
Total JS 5693.5KB (5.56MB)
Total CSS 143.8KB
Limit 10.0MB

Bundle is within the 10.0MB limit.

Copy link
Copy Markdown
Collaborator Author

Engine Review Pass is red, and it is not a defect in this diff

Status: failure — "No passing review from a non-author engine for current head SHA" (4f90b23).

This is the cross-engine review gate in .github/workflows/pr-guards.yml:1165, not a test or build failure. Every other signal on this head is clean, and the PR Blocker Checklist reads Ready (all threads resolved, no changes requested, no blocked engines).

Why I cannot clear it myself

The gate scans reviews and comments for a line matching:

REVIEW: PASS <engine> <40-hex head sha>

and requires the marker's engine to differ from the author engine, which it derives from the branch prefix — claude/restore-sandbox-front-door-96801d → claude. A PASS from the author engine is discarded explicitly:

if (action === 'PASS' && isAuthorEngine) {
  core.info(`Ignoring PASS from author engine '${reviewerEngine}'`);
  continue;
}

I am the author engine, so any marker I could legitimately post is ignored by construction. Posting one under another engine's name would be fabricating a review verdict that engine never gave — the precise thing this gate exists to prevent, so I will not do it.

What actually satisfies it

Per the convention on #3824, the marker comes from your own account (association must be OWNER/MEMBER/COLLABORATOR), attributed to whichever engine did the review:

REVIEW: PASS codex 4f90b23a5f7577098c086669a049ce840267ad07

Review-bot output cannot substitute: coderabbitai[bot], chatgpt-codex-connector[bot] and cursor[bot] all carry author_association: NONE and are filtered out before markers are read. CodeRabbit also skipped this PR entirely because it is a draft.

So this needs a non-Claude engine — Codex CLI, Gemini, Grok — run against 4f90b23, with its verdict posted from your account. Taking the PR out of draft would additionally wake the review apps for extra signal, though their output cannot satisfy the gate on its own.

On re-running

A re-run cannot change the outcome: the check is a deterministic scan of existing reviews, and no qualifying marker exists at this SHA. The workflow already self-heals genuinely stale runs via rerunStaleEvaluation. Burning a run to reproduce a verdict I can read from the source would be noise, so I have not re-run it, and I am saying so here rather than leaving it unexplained.

Verification on this head, for whoever reviews

  • Full services/ingestion/tests suite: 0 failures/errors
  • Green: router policy, test_router_surface_doc_2838, route-security manifest, server import smoke, monolith route parity, SDK route contract, OpenAPI production surface, tests/invariants/
  • generate_route_security_manifest.py --check: clean at 261 enabled / 178 omitted-404
  • ruff: clean on every changed file
  • DB-backed tests skipped locally for lack of Postgres/Docker — CI is the authority there

Worth a reviewer's attention specifically: this promotes a router into the production surface, so the judgement calls are whether /evaluate and /trace are genuinely stateless, and whether holding /share back under a separate unpromoted owner is the right split rather than promoting or deleting it.

I will stay on this PR and keep watching.


Generated by Claude Code

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Vercel preview: https://regengine-hlleswb6p-petrefiedthunders-projects.vercel.app

Access is via Vercel login (Deployment Protection).

…3825)

The repo-root security/RLS/isolation suite caught a pinned copy of the
production allowlist that my grep for `^ENABLED_ROUTERS=` missed, because it
is a JSON key rather than an env line:

  Release-evidence validation failed:
  - docs/security/release-evidence/2026-08-03-security-remediation-candidate.json:
    $.enabled_routers: must exactly match production policy
    (missing=['sandbox'], extra=[])

scripts/validate_release_evidence.py requires that field to equal
PRODUCTION_ENABLED_ROUTERS exactly, so promoting a router means updating the
record. Rather than silently appending to a security artifact, the note now
records that sandbox was promoted after this record's 2026-08-26 verification
date, was NOT exercised by it, and carries its own promotion-gate evidence.
The record is an explicitly unapproved candidate (status not_approved,
release_approved false) whose route_manifest counts were already stale against
main, so the field is a living constraint rather than a frozen attestation.

Also mirrors the share-router split into services/ingestion/main.py. That
legacy per-service entrypoint mounts app.sandbox.router directly, so after the
split it would have silently dropped both /share endpoints; server/main.py
documents that the two mount sites are kept in sync.

Verified: validate_release_evidence.py and generate_route_security_manifest.py
--check both clean; check_enabled_outbound_http.py passes; the failing job's
non-DB steps all green locally (outbound-HTTP guard, disabled-router task
handlers, release evidence, anti-stub canonical guard); route parity, router
policy, route-security manifest, router-surface doc invariant and import smoke
green; sandbox suites green. The job's remaining steps need isolated test
databases, which this environment has no Postgres for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014PP3CSvvRruKPuYTrLoXzq

This branch was successfully deployed

1 active deployment
Preview — b813d0bd Deployed Nov 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend docs frontend security Security vulnerabilities and concerns

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Promote sandbox evaluate/trace to the production router surface, keep /share experimental

2 participants