Packaging

Status: initial Burrito/Tinfoil packaging path in place. Release v0.0.11 adds the Lustre workbench migration, framework-adapter recipe smokes, and local agent-adapter install/probe support.

Wardwright is a BEAM application with a Phoenix/Lustre operator UI and Gleam decision cores. The packaging goal is a user-facing binary that does not require Erlang, Elixir, or Gleam on the target machine.

Chosen Path

Wardwright uses Burrito to wrap the OTP release and ERTS into a self-extracting executable. Burrito is the best fit for the first Wardwright package because it preserves normal BEAM supervision and Phoenix runtime behavior while removing runtime language-tool dependencies.

Tinfoil sits around Burrito for release automation. It builds per-platform archives, creates the GitHub Release, writes checksums, and updates the existing bglusman/homebrew-tap tap with a generated wardwright formula.

This is intentionally separate from source development. Developers should use mise run check and mise run run:app; users should install published release artifacts.

Release Targets

The configured release matrix is:

The first Homebrew install path should focus on macOS. Linuxbrew support can remain best-effort until there is a real user or staging host that needs it.

Direct Linux Install

Tinfoil also publishes plain Linux tarballs to the GitHub Release. This should be the default non-macOS distribution path before we have a reason to introduce Docker.

The convenience installer supports Linux x86_64 and ARM64:

curl -fsSL https://raw.githubusercontent.com/bglusman/wardwright/main/scripts/install.sh | sh

For a pinned release:

curl -fsSL https://raw.githubusercontent.com/bglusman/wardwright/main/scripts/install.sh | sh -s -- --version v0.0.11

The script downloads the matching release archive, requires checksums-sha256.txt, verifies the archive checksum, and installs wardwright to ~/.local/bin by default. A manual install is equivalent:

curl -fLO https://github.com/bglusman/wardwright/releases/download/v0.0.10/wardwright-0.0.10-x86_64-unknown-linux-musl.tar.gz
curl -fLO https://github.com/bglusman/wardwright/releases/download/v0.0.10/checksums-sha256.txt
sha256sum -c checksums-sha256.txt --ignore-missing
tar -xzf wardwright-0.0.10-x86_64-unknown-linux-musl.tar.gz
install -m 0755 wardwright ~/.local/bin/wardwright

The Linux binary has the same runtime contract as the Homebrew package: set a stable WARDWRIGHT_SECRET_KEY_BASE, optionally set WARDWRIGHT_ADMIN_TOKEN, and run the binary as the local HTTP service. Systemd packaging can be added later without changing the release artifact shape.

Local Planning

From app/:

mise exec -- mix tinfoil.plan

The plan should show a GitHub release for bglusman/wardwright and Homebrew tap updates for bglusman/homebrew-tap.

To build locally on macOS, install Burrito's build prerequisites. Burrito currently expects Zig 0.15.2. On macOS 26 / Xcode 26, use Homebrew's patched zig@0.15 formula rather than the upstream Zig archive:

brew install zig@0.15
WARDWRIGHT_SECRET_KEY_BASE="$(openssl rand -base64 64)" mise run package:build:darwin-arm64

Linux builds can use the upstream Zig 0.15.2 archive. Windows targets also need 7z, but Wardwright does not currently publish a Windows package.

The output binary lands in app/burrito_out/. Tinfoil's CI workflow wraps per-target binaries into versioned release archives under _tinfoil/.

Homebrew

The release workflow updates the existing tap. Install on macOS with:

brew tap bglusman/tap
brew install wardwright
wardwright admin

The generated formula:

The same installed binary also exposes small operator/agent helper commands:

wardwright --help
wardwright serve
wardwright admin
wardwright admin access
wardwright tools
wardwright tools --json

wardwright admin opens the operator workbench in the default browser. If the configured bind port is not responding, it starts wardwright serve in the background first. wardwright admin access opens Models & access directly. Homebrew users can still run Wardwright as a service with brew services start wardwright; the admin helper just removes the need to remember the local URL.

wardwright tools prints MCP and policy-authoring API instructions for local agents. The JSON form is generated from the same registry used by the protected /v1/policy-authoring/tools endpoint, so scripts can discover the available authoring surface without scraping the UI. The advertised HTTP surface includes draft model creation, local model activation, draft-only rule-change proposals, validation, projection explanation, simulation, Dune snippet, debugger, harness, and scenario record/import/export/retention tools. The MCP endpoint exposes the core authoring loop plus trace/debugger, Dune snippet, validation, and harness read/export tools; some scenario management endpoints remain HTTP-only. The Agent Authoring Guide explains when an agent should use each tool and which operations are draft-only versus write-capable.

The experimental in-page authoring assistant is intentionally disabled unless configured. Service installs should put its settings in /opt/homebrew/etc/wardwright/authoring_agent.env on Apple Silicon Homebrew, /usr/local/etc/wardwright/authoring_agent.env on Intel Homebrew, or ~/.config/wardwright/authoring_agent.env for user-local runs. Use WARDWRIGHT_AUTHORING_AGENT_CONFIG_FILE to point at a different file. This keeps brew services and wardwright admin launches from silently losing the local model/provider selection that was only present in one shell session. For local Gemma dogfooding, register config/local-gemma-authoring.model.json with the local Wardwright server and set WARDWRIGHT_AUTHORING_AGENT_MODEL=local-gemma-authoring in that env file.

WARDWRIGHT_ADMIN_TOKEN remains optional for loopback-only use. For browser access to the operator workbench and protected control APIs beyond loopback, set BASIC_AUTH_PASSWORD; the Basic Auth username is always admin. This protects operator surfaces such as /admin, /mcp, /admin/*, receipts, and policy-authoring and simulation APIs. OpenAI-compatible model endpoints remain governed by model access configuration. Generated model API keys and the active model definition are stored in the SQLite database at ~/.local/share/wardwright/wardwright.sqlite3 unless XDG_DATA_HOME or WARDWRIGHT_SQLITE_STORE points somewhere else. Keep WARDWRIGHT_SECRET_KEY_BASE stable, or set WARDWRIGHT_MODEL_API_KEY_HASH_SECRET explicitly, so stored keys remain verifiable across restarts. To encrypt the store, set WARDWRIGHT_SQLITE_KEY or WARDWRIGHT_SQLITE_KEY_FNOX and ship an exqlite build linked against SQLCipher; Wardwright checks PRAGMA cipher_version and refuses to start with a configured key when SQLCipher is unavailable. For foreground testing without brew services, run:

WARDWRIGHT_SECRET_KEY_BASE="$(cat "$(brew --prefix)/etc/wardwright/secret_key_base")" \
WARDWRIGHT_BIND=127.0.0.1:8787 \
wardwright serve

SQLite Encryption in Packaged Builds

exqlite can link its NIF against SQLCipher by setting EXQLITE_USE_SYSTEM=1, EXQLITE_SYSTEM_CFLAGS, and EXQLITE_SYSTEM_LDFLAGS during compilation. Burrito can rebuild NIFs per target with :nif_env/:nif_cflags qualifiers, and Tinfoil will package the resulting Burrito binaries. That means encrypted SQLite can be made available in packaged builds, but it must be solved in the release build matrix, not at runtime.

The conservative release path is:

  1. install or vendor SQLCipher headers and libraries for each target;
  2. compile exqlite with EXQLITE_USE_SYSTEM=1 and SQLCipher include/linker flags;
  3. run a packaged-binary smoke test with WARDWRIGHT_SQLITE_KEY set and assert the app starts and PRAGMA cipher_version is present.

Until that matrix is wired, WARDWRIGHT_SQLITE_KEY and WARDWRIGHT_SQLITE_KEY_FNOX are opt-in and fail closed when the binary was not built with SQLCipher.

Provider Credentials

The package does not install fnox. If a configured provider target uses credential_fnox_key, the host running Wardwright must already have a working fnox command on PATH; Wardwright resolves the value with fnox get KEY when it needs to call the provider. Environment-variable credentials via credential_env are also supported for local development and live smoke tests. Database-backed provider credentials should wait until SQLCipher-enabled packaged builds are part of the release matrix. Once encrypted SQLite is guaranteed, Wardwright can remove fnox as a required secret-store dependency by storing provider credentials in the same encrypted local database and failing closed whenever credentials are configured but the database is not encrypted.

Credential storage and service authentication are separate. Fnox keeps raw provider keys out of artifacts and logs, but it does not decide who may call a Wardwright model. Do not configure real provider credentials on a Wardwright instance reachable by untrusted users unless the service is bound behind a trusted authentication boundary. See Provider Credentials.

Release Workflow

The root workflow .github/workflows/wardwright-release.yml is adapted from Tinfoil's generated workflow because this repository keeps the Mix app under app/.

Tagging a stable v* release should:

  1. Build Burrito binaries for each configured target.
  2. Upload archives and checksums to a GitHub Release.
  3. Publish provenance attestations.
  4. Update Formula/wardwright.rb in bglusman/homebrew-tap for stable tags.

The Homebrew update job needs a HOMEBREW_TAP_TOKEN repository secret with write access to bglusman/homebrew-tap. Tinfoil also supports deploy-key auth, which is preferable once release automation is no longer experimental.

Dev tags such as v0.0.11-dev are published as GitHub prereleases but do not update the Homebrew tap. The v0.0.11 stable tag updates the tap after release artifacts and checksums publish successfully.

Known Gaps