← thrum blog post / router

Router Docs

This document specifies the public JSON produced by Thrum's Router. It is designed to be programmatically stable for downstream automation (e.g., the Executor) and act as a receipt for credit spend.

This tutorial is written for absolute beginners; there should be no barriers to entry for Web3 engineers and auditors.

The Router produces a deterministic, side-effect-free receipt that:

1) summarizes what Thrum detected about a repository at a high level, 2) returns an allow/deny decision (with debug help) for running a successful Scan, and 3) provides a handoff to subsequent pipeline stages.

This document specifies the public JSON shape intended for paying customers who receive a receipt:

  • CLI users who request JSON output
  • cloud API users who receive a Router Report from upload.thrum.sh

Media Type: application/vnd.thrum.router-report+json

This string is the label for the Router report format. If you fetch a Router receipt over HTTP, you'll see it in the response header: Content-Type: application/vnd.thrum.router-report+json.

  • application/…: this is an application data format.
  • vnd.: "vendor-specific," so Thrum defines this JSON shape, rather than it being a generic internet-wide standard format.
  • thrum.router-report: the thing being returned (a receipt/report).
  • +json: the body is valid JSON, so you can parse it with JSON tooling!

Compatibility and Versioning

Our JSON has a field called schema_version. That number tells you which edition of the Router output format you're looking at. This is useful so downstream tooling (that maybe your team integrates Thrum into) knows what to expect from Thrum.

This report’s schema_version follows Semantic Versioning (SemVer): MAJOR.MINOR.PATCH.

Reference: Semantic Versioning 2.0.0

What each number means

  • MAJOR (e.g., 2.x.x3.0.0)
    Breaking change. Something about the report format changed in a way that could break existing integrations (for example: a field was removed/renamed, or a field’s type changed). If you built tools to parse this report, you should plan to update your parser for a new MAJOR.

  • MINOR (e.g., 2.1.02.2.0) Backward-compatible change. New information may be added (typically as new, optional fields) that won’t break older parsers if ignored.

  • PATCH (e.g., 2.2.12.2.2) Backward-compatible fix/clarification. Small corrections that don’t change the overall contract in a breaking way (for example: fixing a typo in docs, clarifying behavior).


Design goals

  1. Determinism: the same input produces the same plan and billing estimate.
  2. Receipt-first execution: the Router returns a receipt that the user must approve before any paid execution begins and credits are spent.
  3. Minimal disclosure: show what will run, what it needs, what it will cost, and what to do if rejected, without publishing the Router’s algorithm.
  4. Machine-consumable: stable schema with explicit versioning.

Top-level object

The Router returns a single JSON object:

  • On success: RouterPlan
  • On failure: ProblemDetails (RFC 9457)

Content types

  • Success: application/json
  • Error: application/problem+json

RouterPlan schema

Required fields

{
  "schema_version": "1.0",
  "router": { "...": "..." },
  "decision": { "...": "..." },
  "repository": { "...": "..." },
  "project": { "...": "..." },
  "execution": { "...": "..." },
  "billing": { "...": "..." },
  "receipt": { "...": "..." },
  "warnings": [],
  "next_actions": []
}

Field reference

schema_version (string, required)

Semantic version for this public schema (not your internal router version).


router (object, required)

Public metadata about the Router build that produced the output.

Field Type Required Notes
name string yes e.g., "thrum-router"
version string yes public release/version tag
build string no build id / git sha (public-safe)
timestamp string yes ISO-8601

decision (object, required)

Router accept/reject decision for whether execution can proceed under the declared policy.

Field Type Required Notes
status string yes "accept" | "reject"
code string yes stable machine code (e.g., SAFE_EXEC_REQUIRED)
message string yes user-facing, mitigable
exit_code integer yes 0 if accept; non-zero if reject
mitigations string[] yes actionable steps
evidence string[] yes high-level reasons; must not leak proprietary heuristics

repository (object, required)

Repository-level snapshot sufficient for user comprehension and billing.

Field Type Required Notes
repo_fingerprint string yes stable id for the uploaded content (opaque)
input object yes upload/source metadata (public-safe)
size object yes file/LOC/bytes totals used for estimation
languages object[] no aggregate only (no file listing)

repository.input

Field Type Required Notes
source string yes "upload" | "git" | "archive"
label string no user-provided name (e.g., repo name)
commit string no if source is git
subdir string no if user selected a subdirectory

repository.size

Field Type Required Notes
files_total integer yes
bytes_total integer yes
loc_total integer yes
loc_code integer yes preferred for pricing basis
loc_comments integer no
loc_blank integer no

project (object, required)

Public summary of what the Router inferred as the primary project.

Field Type Required Notes
kind string yes e.g., "evm", "zk", "mixed"
detected_toolchains string[] yes e.g., ["foundry","hardhat"]
selected_root string yes relative path (no absolute FS paths)
package_layout string no e.g., "single", "monorepo"
notes string[] no public-safe hints

execution (object, required)

What will happen if the user approves the receipt.

Field Type Required Notes
mode string yes "hermetic" | "safe_exec" | "networked"
requirements object yes permissions required to proceed
plan object[] yes ordered stages (names + public parameters)
artifacts object no where outputs will be written/returned

execution.requirements

Field Type Required Notes
network string yes "none" | "optional" | "required"
build_scripts string yes "blocked" | "allowed" | "required"
elevated_fs_access boolean yes typically false
estimated_runtime_seconds object no { "p50": ..., "p95": ... }

execution.plan[] stage object

Field Type Required Notes
stage string yes e.g., "compile", "static_detectors", "dynamic", "triage"
description string yes user-readable
inputs string[] no e.g., "repository"
outputs string[] no e.g., "build_artifacts", "findings"

billing (object, required)

The user must be able to review the estimate and approve a receipt pre-execution.

Field Type Required Notes
unit string yes "credits"
estimate object yes total + bounds
breakdown object[] yes line items by stage
pricing_version string yes public pricing schedule/version id
notes string[] no disclaimers, rounding, etc.

billing.estimate

Field Type Required Notes
total integer yes point estimate
low integer no optional lower bound
high integer no optional upper bound
basis object yes what measurements were used (public-safe)

billing.estimate.basis

Allowed fields should be limited to measurements users can reason about (e.g., LOC basis), but not internal heuristics.

{
  "loc_code": 48231,
  "toolchains": ["foundry"],
  "execution_mode": "safe_exec"
}

billing.breakdown[]

Field Type Required Notes
item string yes e.g., "compile", "static_detectors"
credits integer yes
details object no public-safe, non-proprietary

receipt (object, required)

This is the object the UI asks the user to approve.

Field Type Required Notes
receipt_id string yes opaque
status string yes "requires_approval" | "approved" | "expired"
expires_at string yes ISO-8601
line_items object[] yes mirrors billing breakdown
total_credits integer yes must equal billing.estimate.total
approval object yes what the user must do next

receipt.approval

Field Type Required Notes
required boolean yes true for paid execution
method string yes e.g., "POST /v1/receipts/{id}/approve"
token string no optional approval token (short-lived)

warnings (array, required)

Non-fatal issues to show users, e.g., “Multiple toolchains detected; selected foundry root.”


next_actions (array, required)

User-facing action prompts, e.g.:

  • “Approve receipt to begin execution.”
  • “Enable network access or switch to hermetic mode.”

Error format (RFC 9457)

When the Router cannot produce a plan (bad input, invalid archive, internal failure), return Problem Details for HTTP APIs (RFC 9457). At minimum: type, title, status, detail, instance.

Example:

{
  "type": "https://thrum.sh/problems/invalid-archive",
  "title": "Invalid repository archive",
  "status": 400,
  "detail": "The uploaded file could not be unpacked as a supported archive format.",
  "instance": "req_01JFD...Z9"
}

Examples

Example A: Accepted

{
  "schema_version": "1.0",
  "router": {
    "name": "thrum-router",
    "version": "0.9.3",
    "timestamp": "2025-12-16T05:58:12Z"
  },
  "decision": {
    "status": "accept",
    "code": "ACCEPT",
    "message": "Ready to execute under safe execution mode.",
    "exit_code": 0,
    "mitigations": [],
    "evidence": ["Primary project root selected: contracts/"]
  },
  "repository": {
    "repo_fingerprint": "repo_9f2c...8a",
    "input": { "source": "upload", "label": "my-protocol" },
    "size": {
      "files_total": 1249,
      "bytes_total": 18392012,
      "loc_total": 61210,
      "loc_code": 48231
    }
  },
  "project": {
    "kind": "evm",
    "detected_toolchains": ["foundry"],
    "selected_root": "contracts/",
    "package_layout": "single"
  },
  "execution": {
    "mode": "safe_exec",
    "requirements": {
      "network": "optional",
      "build_scripts": "blocked",
      "elevated_fs_access": false
    },
    "plan": [
      { "stage": "compile", "description": "Build project artifacts for analysis." },
      { "stage": "static_detectors", "description": "Run static detectors over sources and artifacts." },
      { "stage": "triage", "description": "Normalize and package findings." }
    ]
  },
  "billing": {
    "unit": "credits",
    "pricing_version": "2025-12-public-01",
    "estimate": {
      "total": 120,
      "low": 110,
      "high": 160,
      "basis": { "loc_code": 48231, "toolchains": ["foundry"], "execution_mode": "safe_exec" }
    },
    "breakdown": [
      { "item": "compile", "credits": 25 },
      { "item": "static_detectors", "credits": 85 },
      { "item": "triage", "credits": 10 }
    ],
    "notes": ["High bound may apply if compilation requires additional passes."]
  },
  "receipt": {
    "receipt_id": "rcpt_01JFD...K2",
    "status": "requires_approval",
    "expires_at": "2025-12-16T06:28:12Z",
    "line_items": [
      { "item": "compile", "credits": 25 },
      { "item": "static_detectors", "credits": 85 },
      { "item": "triage", "credits": 10 }
    ],
    "total_credits": 120,
    "approval": { "required": true, "method": "POST /v1/receipts/rcpt_01JFD...K2/approve" }
  },
  "warnings": [],
  "next_actions": ["Approve receipt to begin execution."]
}

Example B: Rejected

{
  "schema_version": "1.0",
  "router": {
    "name": "thrum-router",
    "version": "0.9.3",
    "timestamp": "2025-12-16T06:02:12Z"
  },
  "decision": {
    "status": "reject",
    "code": "NETWORK_REQUIRED",
    "message": "This project requires network access to install dependencies before compilation.",
    "exit_code": 21,
    "mitigations": [
      "Re-run with network enabled, or",
      "Upload a vendored dependency tree, or",
      "Provide a prebuilt artifact bundle if supported"
    ],
    "evidence": ["Dependency installation required for compilation."]
  },
  "repository": {
    "repo_fingerprint": "repo_1b7a...91",
    "input": { "source": "upload", "label": "my-protocol" },
    "size": { "files_total": 980, "bytes_total": 12912001, "loc_total": 42110, "loc_code": 31880 }
  },
  "project": {
    "kind": "evm",
    "detected_toolchains": ["hardhat"],
    "selected_root": ".",
    "package_layout": "single"
  },
  "execution": {
    "mode": "hermetic",
    "requirements": { "network": "required", "build_scripts": "required", "elevated_fs_access": false },
    "plan": []
  },
  "billing": {
    "unit": "credits",
    "pricing_version": "2025-12-public-01",
    "estimate": { "total": 0, "basis": { "loc_code": 31880, "toolchains": ["hardhat"], "execution_mode": "hermetic" } },
    "breakdown": []
  },
  "receipt": {
    "receipt_id": "rcpt_none",
    "status": "expired",
    "expires_at": "2025-12-16T06:02:12Z",
    "line_items": [],
    "total_credits": 0,
    "approval": { "required": false, "method": "" }
  },
  "warnings": [],
  "next_actions": ["Enable network/build scripts or adjust inputs, then re-run Router."]
}

security / research inquiries → vlad@usatii.com