Updated in v7.0.0 (breaking).
StatsVerifier.verify_stats() now returns a DiagnosticResult, and a successful sandbox execution is reported as UNVERIFIABLE, never VERIFIED — computation is not verification. See the changelog for migration details.Features
- Docker sandbox — Full container isolation for all statistical code execution
- Fail-closed execution — If Docker is unavailable, verification is blocked rather than falling back to in-process execution
- Pre-execution security validation — AST-based code analysis before Docker execution
- Live Docker health checks — The executor verifies Docker availability on each request, not just at startup
Prerequisites
The Stats Engine requires a running Docker daemon. Without Docker, all statistical verification requests return HTTP 503. See the deployment guide for setup instructions.The DiagnosticResult contract
verify_stats() returns a DiagnosticResult. Execution success alone never produces VERIFIED:
A run that executes cleanly and returns an observed statistic is
UNVERIFIABLE because the engine has no deterministic proof of the original natural-language claim — it only observed a computation. VERIFIED with a proof_ref is reserved for deterministic claim evaluation, which this engine does not yet emit.
Execution evidence is preserved
OnUNVERIFIABLE, the full execution evidence is retained in developer_fields for audit and review:
agent_message is sanitized — raw subprocess output, sandbox identifiers, and error strings never reach the agent-facing layer. Blocked results carry no proof_ref and cannot be mistaken for a verdict.
Float precision advisory
When the generated statistics code contains binary floating-point constants, the completed-analysis result also carriesdeveloper_fields.advisory_checks with a precision.float-constants advisory. Generated numpy/pandas code is float-native, so this is the expected shape for most analyses. The advisory flags the constants for exactness-sensitive consumers and suggests decimal.Decimal or exact SymPy arithmetic.
AdvisoryCheck with advisory_only=True enforced at construction. It structurally cannot change the status or proof_ref — a completed analysis remains UNVERIFIABLE with or without it. The same advisory appears on POST /verify/math responses; see Math engine — Float precision advisory.
Usage
Upload limits
/verify/stats bounds both the CSV transfer and the parsed dataset. QWED enforces the byte cap while the body is received, before any parse work starts, and enforces the cell cap chunk by chunk during parsing.
The cell cap counts rows × columns, so a compact but very wide CSV is rejected even when the file is small. If your dataset exceeds a limit, split it or pre-aggregate before uploading — the limits cannot be raised per request.
The CSV parse, code generation, and Docker execution all run off the API event loop, so a large-but-valid upload slows only its own request rather than the whole service.
Bounded results
Sandbox output is size-capped end to end, so a runaway computation cannot exhaust memory or flood the audit log:- The sandbox result file is capped at 2 MB. The cap is enforced both inside the container as the result is serialized and again at host read-back. An oversized result fails the execution rather than being partially returned.
observed_resultindeveloper_fieldsis bounded before it is returned: long strings are truncated with markers and deeply nested structures are cut off at a traversal budget.- The copy of the result stored in the verification audit log is capped at 64,000 characters and always remains valid JSON, so the audit integrity verifier can still parse capped entries.
Execution model
All generated statistical code is executed inside a Docker container with enforced memory and CPU limits. The engine does not fall back to in-process execution under any circumstances.Pre-execution security validation
Generated statistical code is validated with an AST walk before it reaches the sandbox. Code that fails validation is blocked with a403 and never executes. The check blocks:
- Dangerous imports anywhere in the import path. OS, process, and reflection modules (
os,sys,subprocess,socket,posix,nt,importlib,ctypes,builtins) are rejected in every dotted segment of animportorfrom ... importstatement, including members bound under innocuous aliases (from pandas.io.common import os as safe). - Blocked call names on bare names and attribute targets. Interpreter builtins (
eval,exec,open, …) and OS primitives (system,popen,import_module, theexec*/spawn*families,fork) are rejected whether called directly or asx.eval(...)-style attribute calls. This also catches reflective re-binding such assys.modules['os'].system(...)at the call site. - Traversal through sandbox module internals. Attribute chains rooted at the sandbox aliases (
pd,np,json,sys) are rejected when any segment names a dangerous module — for example reaching the OS module through pandas or numpy internal re-exports. Legitimate nested public APIs (np.linalg.norm,np.random.seed,pd.Timestamp.now) pass, because they never name a dangerous module. sysrestricted to a read-only allowlist. Only known read-only interpreter metadata (sys.maxsize,sys.version,sys.float_info,sys.platform, …) is accessible. Any othersysmember, including frame introspection likesys._getframe, fails closed.
Known trade-off: a DataFrame column named like a dangerous module cannot be read with attribute access —
df.os is rejected because static analysis cannot distinguish it from a gadget. Use subscript access (df['os']) instead.Error handling
When the Stats Engine encounters an internal failure — such as a code generation or translation error — it returns a generic"Internal verification error" message. Sensitive details like file paths, credentials, or stack traces are never included in the API response.
If you receive this error, check the server-side logs for diagnostic details. The engine logs the exception type for debugging while keeping the client response opaque.
Direct operations
For simple operations, bypass code generation:compute_statistics() and get_sandbox_info() are utilities, not claim-verification boundaries, and deliberately keep their existing dict return shape (SUCCESS / ERROR). Only verify_stats() returns a DiagnosticResult.Fail-closed validation
compute_statistics returns SUCCESS only when the result is clearly defined and safely verifiable. It returns ERROR in the following cases:
Empty series and all-NaN columns are caught by the NaN result check — if the underlying pandas operation returns
NaN, the method returns an ERROR status rather than propagating the undefined value.