Skip to content

DEVELOPER DOCUMENTATION · RUNTIME REFERENCE

Ten real snippets from the
Thunderline spine.

Grounded in the actual Elixir/OTP modules and SQL tables powering Thunderline. Private platform modules are marked source: private — contact for access; this site's source is public at github.com/Thunderblok/Landing.

01 · Provisioning a ThunderBlock (mix new & Blockstrap)

lib/mix/tasks/thunderblock.init.ex · source: private — contact for access

Public SDK scaffolding via mix thunderblock.new is in beta cutover; during the current design-partner window, new ThunderBlock workspaces are provisioned through the hosted Blockstrap flow and bound to a local workspace config.

# Local workspace bootstrap (CLI preview —coming soon for general SDK)
# Provision a managed cell today at https://thunderline.net/blockstrap
$ mix new my_governed_hive --sup
$ cd my_governed_hive

# config/runtime.exs — bind your ThunderBlock cell & operator grant
import Config

config :thunderblock, :cell,
  block_id: System.fetch_env!("THUNDERBLOCK_ID"),
  blockstrap_endpoint: "https://thunderline.net/blockstrap",
  default_capability_floor: :read,
  require_receipt_seal: true

02 · Crown Action Definition (4 Capability Tiers)

apps/thundercrown/lib/thundercrown/actions/catalog/manual/case_packet_commit.ex · source: private — contact for access

Every governed operation in Thundercrown.Actions.Catalog.Manual.* declares an explicit capability tier (:read, :mutate, :admin, or :execute) so the policy gate can reject unauthorized calls before any executor code runs.

defmodule Thundercrown.Actions.Catalog.Manual.CasePacketCommit do
  @moduledoc """
  Commits a reviewed case packet to workspace state.
  Capability tier must be one of :read | :mutate | :admin | :execute.
  """
  alias Thundercrown.Actions.Action

  @spec definition() :: Action.t()
  def definition do
    %Action{
      id: "crown.manual.case_packet_commit",
      domain: :acorn,
      capability: :mutate,
      executor: Thundercrown.Executors.CasePacketExecutor,
      require_evidence_refs?: true,
      seal_receipt?: true,
      schema: %{
        mission_id: {:required, :uuid},
        packet_sha256: {:required, :string},
        disposition: {:required, {:enum, [:approved, :escalated, :rejected]}}
      }
    }
  end
end

03 · PolicyGate.validate_and_execute/2

apps/thundercrown/lib/thundercrown/policy_gate.ex · source: private — contact for access

PolicyGate.validate_and_execute/2 resolves the authorization actor (kept strictly distinct from telemetry actor claims), checks capability grants against the action tier, and seals both the ActionReceipt and LineageEdge before returning.

alias Thundercrown.PolicyGate
alias Thundercrown.Actions.Catalog.Manual.CasePacketCommit

action = CasePacketCommit.definition()

context = %{
  authz_actor: %{principal_id: "op_91a4", role: :operator, capabilities: [:read, :mutate]},
  telemetry_actor: %{agent_id: "agt_synthesis_02"},
  mission_id: "c3a8d104-7d4b-4e11-9c20-8a1f00e44b19",
  saga_run_id: "f1208b6e-2c91-44a8-8b10-0019c871d2e0",
  step_id: "step_04_commit",
  evidence_refs: [
    %{kind: :source_bundle, sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}
  ],
  inputs: %{
    mission_id: "c3a8d104-7d4b-4e11-9c20-8a1f00e44b19",
    packet_sha256: "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    disposition: :approved
  }
}

case PolicyGate.validate_and_execute(action, context) do
  {:ok, %{result: result, receipt: receipt, lineage_edge: edge}} ->
    {:ok, receipt.receipt_id, receipt.seal_hash, edge.edge_id, result}

  {:error, {:policy_denied, reason, denial_receipt}} ->
    {:denied, reason, denial_receipt.receipt_id}
end

04 · ActionReceipt 17-Field @seal_fields Doctrine

apps/thundercrown/lib/thundercrown/receipts/action_receipt.ex · source: private — contact for access

Every ActionReceipt computes its deterministic hash across 17 canonical @seal_fields in fixed order. Three operational columns (governance, output_redacted, input_redacted) sit outside the seal and are protected via the lineage-edge cross-check.

defmodule Thundercrown.Receipts.ActionReceipt do
  use Ecto.Schema

  # Verbatim 17-field seal specification. Order is canonical and immutable.
  # Note: governance, output_redacted, and input_redacted sit outside @seal_fields.
  @seal_fields [
    :receipt_id,
    :envelope_version,
    :action_id,
    :domain,
    :mission_id,
    :saga_run_id,
    :step_id,
    :correlation_id,
    :causation_id,
    :actor,
    :inputs_hash,
    :status,
    :attempt,
    :traceparent,
    :timing,
    :cost,
    :artifacts
  ]

  @spec seal_fields() :: [atom()]
  def seal_fields, do: @seal_fields

  @spec compute_seal_hash(map() | t()) :: String.t()
  def compute_seal_hash(receipt) do
    canonical_payload =
      @seal_fields
      |> Enum.map(fn field -> {Atom.to_string(field), Map.get(receipt, field)} end)
      |> :erlang.term_to_binary([:deterministic])

    :crypto.hash(:sha256, canonical_payload)
    |> Base.encode16(case: :lower)
  end
end

05 · Lineage Edge Persistence (LineageEdge.compute_seal_hash/1)

apps/thundervine/lib/thundervine/resources/lineage_edge.ex · source: private — contact for access

Thundervine.Resources.LineageEdge persists the causal link between parent evidence, the authorization decision, and the resulting ActionReceipt. Because both tables carry independent seal hashes, a single-table UPDATE cannot forge a valid authority correlation.

defmodule Thundervine.Resources.LineageEdge do
  @moduledoc """
  Causal lineage edge binding an ActionReceipt to its parent mission and authority decision.
  """

  @edge_seal_fields [
    :edge_id,
    :mission_id,
    :source_ref,
    :target_receipt_id,
    :target_receipt_seal_hash,
    :relationship,
    :authority_decision_status,
    :recorded_at_unix_us
  ]

  @spec compute_seal_hash(map()) :: String.t()
  def compute_seal_hash(attrs) when is_map(attrs) do
     canonical =
      Enum.map(@edge_seal_fields, fn k ->
        {Atom.to_string(k), Map.get(attrs, k)}
      end)
      |> :erlang.term_to_binary([:deterministic])

    :crypto.hash(:sha256, canonical)
    |> Base.encode16(case: :lower)
  end
end

06 · Receipt Replay Query (SQL Provenance Chain)

apps/thundervine/priv/repo/queries/mission_receipt_replay.sql · source: private — contact for access

When an auditor asks "who authorized this action, what inputs did it see, and does the causal chain hold?", the answer is a deterministic query joining action_receipts to thundervine_lineage_edges ordered by step and causation chain.

-- Replay the full governed receipt + lineage chain for mission $1
WITH RECURSIVE receipt_chain AS (
  SELECT
    r.receipt_id,
    r.causation_id,
    r.step_id,
    r.action_id,
    r.domain,
    r.actor,
    r.inputs_hash,
    r.status,
    r.seal_hash,
    e.edge_id,
    e.authority_decision_status,
    e.edge_seal_hash,
    1 AS chain_depth
  FROM action_receipts r
  INNER JOIN thundervine_lineage_edges e
    ON e.target_receipt_id = r.receipt_id
   AND e.target_receipt_seal_hash = r.seal_hash
  WHERE r.mission_id = $1
    AND r.causation_id IS NULL

  UNION ALL

  SELECT
    child.receipt_id,
    child.causation_id,
    child.step_id,
    child.action_id,
    child.domain,
    child.actor,
    child.inputs_hash,
    child.status,
    child.seal_hash,
    ce.edge_id,
    ce.authority_decision_status,
    ce.edge_seal_hash,
    parent.chain_depth + 1
  FROM action_receipts child
  INNER JOIN receipt_chain parent
    ON child.causation_id = parent.receipt_id
  INNER JOIN thundervine_lineage_edges ce
    ON ce.target_receipt_id = child.receipt_id
   AND ce.target_receipt_seal_hash = child.seal_hash
  WHERE child.mission_id = $1
)
SELECT * FROM receipt_chain ORDER BY chain_depth ASC, step_id ASC;

07 · K1 WASM Hermetic Cell (K1WasmCell.execute/3)

apps/thunderblock/lib/thunderblock/runtime/k1_wasm_cell.ex · source: private — contact for access

Untrusted or synthesized skill modules run inside Thunderblock.Runtime.K1WasmCell.execute/3 via a wasmtime NIF that rejects any WASM binary with host imports (zero-import verification) and enforces strict instruction fuel and wall-clock budgets.

alias Thunderblock.Runtime.K1WasmCell

wasm_bytes = File.read!("priv/skills/normalize_field_intake.wasm")

opts = [
  max_fuel: 2_500_000,
  wall_clock_timeout_ms: 150,
  allow_host_imports: false,
  memory_limit_bytes: 16 * 1024 * 1024
]

case K1WasmCell.execute(wasm_bytes, %{notes: raw_notes}, opts) do
  {:ok, %{output: normalized, fuel_consumed: fuel, module_sha256: mod_hash}} ->
    {:ok, normalized, %{fuel: fuel, wasm_sha256: mod_hash}}

  {:error, :non_zero_imports_rejected} ->
    {:error, :hermetic_violation}

  {:error, :fuel_exhausted} ->
    {:error, :budget_exceeded}
end

08 · Thunderflow Mnesia-Backed EventBus.publish/3

apps/thunderflow/lib/thunderflow/event_bus.ex · source: private — contact for access

Intra-workspace events travel over Thunderflow.EventBus, which persists envelopes to a local Mnesia transaction log and rejects unsealed or capability-mismatched publications at the bus gate.

defmodule Thunderflow.EventBus do
  @moduledoc """
  Mnesia-backed event bus with pre-publish receipt & capability gate.
  """

  @spec publish(atom(), map(), keyword()) :: {:ok, reference()} | {:error, term()}
  def publish(topic, %Messages.Envelope{} = envelope, opts \\ []) do
    with :ok <- verify_message_seal(envelope),
         :ok <- verify_topic_capability(envelope.actor, topic) do
      :mnesia.transaction(fn ->
        :mnesia.write({:thunderflow_bus_log, envelope.message_id, topic, envelope, System.monotonic_time()})
      end)
      |> case do
        {:atomic, :ok} ->
          Phoenix.PubSub.broadcast(Thunderflow.PubSub, Atom.to_string(topic), {:bus_event, envelope})
          {:ok, make_ref()}

        {:aborted, reason} ->
          {:error, {:mnesia_abort, reason}}
      end
    end
  end
end

09 · Per-Message Sealing (MessageReceipt.seal/2)

apps/thunderlink/lib/thunderline/thunderlink/message_receipt.ex · source: private — contact for access

Before an inter-agent message crosses a link boundary, Thunderline.Thunderlink.MessageReceipt.seal/2 binds the message payload to its exact step_id, domain, and action_id so messages cannot be replayed into a different domain or saga step.

alias Thunderline.Thunderlink.MessageReceipt

envelope_meta = %{
  step_id: "step_02_evidence_gather",
  domain: :acorn,
  action_id: "crown.manual.evidence_collect",
  sender_actor: "agt_researcher_01",
  correlation_id: "corr_88412a"
}

payload = %{
  findings: ["tower_sector_b_azimuth_drift", "ground_lug_corrosion_photo_04"],
  source_count: 2
}

{:ok, sealed_msg} = MessageReceipt.seal(payload, envelope_meta)
# => %MessageReceipt{
#      step_id: "step_02_evidence_gather",
#      domain: :acorn,
#      action_id: "crown.manual.evidence_collect",
#      payload_hash: "4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a",
#      message_seal: "a91f03c8...e721"
#    }

10 · Honest Non-Claim Classification (AcornReceiptSealClassification.classify/1)

apps/acorn/lib/acorn/receipt_seal_classification.ex · source: private — contact for access

Rather than silently treating legacy or unverified rows as valid, AcornReceiptSealClassification.classify/1 returns one of four explicit atoms—distinguishing current verified 17-field seals from legacy v0 encodings and invalid/tampered records.

defmodule AcornReceiptSealClassification do
  @moduledoc """
  Classifies persisted ActionReceipt rows without inflating legacy records to current validity.
  """

  @type classification ::
          :current_valid
          | :legacy_v0_consistent
          | :legacy_k1_v0_consistent
          | :invalid

  @spec classify(ActionReceipt.t()) :: classification()
  def classify(%ActionReceipt{envelope_version: 2} = receipt) do
    expected = ActionReceipt.compute_seal_hash(receipt)

    if Plug.Crypto.secure_compare(expected, receipt.seal_hash || "") do
      :current_valid
    else
      :invalid
    end
  end

  def classify(%ActionReceipt{envelope_version: 1, domain: :k1_wasm} = receipt) do
    if LegacyK1V0Verifier.consistent?(receipt), do: :legacy_k1_v0_consistent, else: :invalid
  end

  def classify(%ActionReceipt{envelope_version: 1} = receipt) do
    if LegacyV0Verifier.consistent?(receipt), do: :legacy_v0_consistent, else: :invalid
  end

  def classify(_), do: :invalid
end