Wardian docs

How Wardian is built

Wardian is one Rust program and a few pages of plain JavaScript. The Rust side is built in the hexagonal style, also called ports and adapters: the rules sit in the middle and know nothing about disks, networks or browsers. Everything that talks to the outside sits at the edge, behind a small interface called a port.

Think of a wall socket. The lamp does not care which power station feeds it; it only knows the socket. A Wardian use case only knows the port, and an adapter plugs a real disk, a real Splunk or a fake one into it. That is why most of Wardian is tested without a network.

The layers#

src/
  main.rs, config.rs      the composition root: reads settings, builds everything, starts it
  domain/                 the rules, pure: no files, no network, no clock
  ports/                  the interfaces between the layers
  usecases/               what Wardian does, through the ports
  adapters/primary/       what drives Wardian: the web server, the command line
  adapters/secondary/     what Wardian drives: the disk, Drive, Splunk, Claude, SQLite
static/                   the pages and scripts the browser runs
templates/                the starter packages for `wardian new`

Dependencies point inward only. Adapters depend on ports. Use cases depend on ports and the domain. The domain may use the standard library, but not std::fs, std::net, std::process, std::env or std::io, and no other crate but serde and serde_json (ADR-2610071200). hexa analyze checks these rules on every run (below).

src/main.rs is the composition root: the one place that knows every adapter. It reads config.rs, builds each adapter, hands them to the use cases as ports, and then runs either the command line or the web server. config.rs is the one place that reads environment variables.

What each module does#

Domain#

ModuleDoes
check.rs (and check/split.rs)The rules of wardian check: does a package follow the format, and if not, what is wrong, in words. split.rs warns about a suite part that does too much.
components.rsThe component library as data: which components exist, which files each needs, how a suite lists them.
db.rsThe rules and limits of the db capability.
export.rsWhat a .wardian file holds: the package, a manifest, and the data only on request.
grants.rsPermission answers: valid channel names, applying an answer, asking whether something is allowed.
history.rsNumbered app versions, how many are kept, which files to copy, and a line diff.
import_plan.rsWhich apps a zip holds, where each file goes, and which zips to refuse.
jobs.rsWhat a background job is, its states, and how long finished jobs are kept.
package.rsWhat a package is, which names and paths it may use, what the app list shows, the format version.
splunk.rsSplunk settings, the search text, and the table a search returns.
studio.rs"Make an app" as data: one chat with Claude, its events, and what it needs next.
suite.rsSuites: reading suite.json, building each frame, and the frame's security policy.
viewer_state.rsThe rules for layouts, apps' saved data and the latest channel messages.

Ports#

PortBetween the use cases and
service.rsthe web server: Catalog, Builder, Searches, Jobs, Pages, ViewerState, Exports, AppHistory, Tables, gathered in Services
tools.rsthe command line: PackageTools (check, new, add, promote)
assets.rsthe files built into the program
storage.rsthe disk
db.rseach package's SQLite database
drive.rsGoogle Drive
llm.rsClaude, through the Anthropic API or Amazon Bedrock
splunk.rsSplunk's REST API
web.rsfetching a zip from a link
calendar.rsUTC dates from a number of seconds; no clock is read here

Use cases#

Use caseDoes
catalog.rsThe live source of apps (local folder or Drive), settings behind it, removing and restoring apps, imports, permissions, and the server-side capability check.
check.rswardian check: a folder, a folder of apps, or a zip unpacked by the real importer.
db.rsThe db capability, through the database port.
docs.rsThis documentation site: renders the Markdown, builds the search index, writes the static copy.
export.rsExport an app as a .wardian file, and preview or install an import's data.
history.rsEach app's versions in history/<app>/: record, compare, restore.
import.rsCopy apps out of a zip into a folder, refusing hostile entries.
jobs.rsThe job runner: each long call on its own thread; jobs kept in memory.
scaffold.rswardian new and wardian add.
splunk.rsSplunk searches for apps, and loading a search into a table.
studio.rs"Make an app": the chat in which Claude writes an app's files, through a few tools.
viewer_state.rsLayouts, apps' saved data and the latest channel messages, in state/.
workspace.rsThe working folder: first-start seeding, the git notice, the write test, wardian promote.

Primary adapters#

AdapterDoes
http.rsThe routes: Wardian's pages, the JSON API, the apps' files, the docs. Decides who is an admin.
http_server.rsA small HTTP/1.1 server over TCP: one thread per connection, keep-alive with an idle limit.
cli.rsEvery command but serving.
stop_log.rsWrites every start and stop to wardian.log, and catches panics and signals.

Secondary adapters#

AdapterDoes
local_disk.rsThe disk, including private (mode 600) writes.
embedded_assets.rsThe files built into the program (see below).
google_drive.rsDrive through a service account: list, download, cache by checksum, refresh.
link_fetch.rsDownload a zip from a link, refusing internal addresses.
splunk_rest.rsSplunk's REST API, with the account's certificate settings, and readable errors.
sqlite_store.rsOne SQLite file per package, with the authorizer and limits.
anthropic_inference.rsClaude over the Anthropic Messages API.
bedrock_inference.rsClaude over Amazon Bedrock, with Wardian's own SigV4 signing.

Only the two inference adapters name Claude models. A hexa rule refuses a model name anywhere else.

src/tests.rs runs the use cases against real adapters, so it belongs to the composition root. The pure rules are tested next to their code in domain/.

How a request flows#

Take an app in a suite that runs a Splunk search.

  1. The app calls splunk.search(…). The shim in its frame posts a capop message to the kernel.
  2. The kernel checks that the app's contract lists splunk, and asks you for permission the first time. Then it posts to /api/splunk/search with the package, the app and "background": true.
  3. adapters/primary/http.rs decides whether the request is from an admin, reads the JSON body, and calls Catalog::check_host_cap. That reads suite.json and grants.json again on the server.
  4. It asks the Jobs port to start the search. usecases/jobs.rs runs it on its own thread and answers {job: id} at once.
  5. On that thread, usecases/splunk.rs runs the search through the SplunkApi port, which adapters/secondary/splunk_rest.rs implements.
  6. The kernel asks /api/jobs/<id> how it is going, every second and then every two, and gives the app the result when the job is done.

Every route works the same way: http.rs knows only the ports in ports/service.rs, the use cases implement them, and the use cases reach the outside only through the other ports.

The kernel and the frame shim#

The browser side has two parts that matter for security:

The server builds each frame as one document (domain/suite.rs, frame): styles, the view, the scripts, the shim and app.js, all inlined, under the frame policy that blocks the network. See Security model.

The other files in static/:

FileRuns inDoes
index.htmlWardian's main pageThe app list, Settings, Make an app, History, Arrange for page and module apps, Save as web page and its cleaning.
channels.jsthe main page and the kernelChannels between packages and the permission bar.
state.jsthe main page and the kernelLayouts, saved data and channel messages, kept by the server with a copy in the browser.
sdk.jspage apps, as /sdk/wardian.jsChannels for page apps, and <wardian-progress>.
snapshot.js, page-snapshot.jsframes and page appsThe default rendering for Save as web page.
ui/packages that copy itThe component library and its gallery at /ui/.

Embedded assets#

Wardian needs no files beside itself. src/adapters/secondary/embedded_assets.rs builds these into the program with include_str! and include_bytes!:

http.rs also builds in Wardian's own pages and scripts (index.html, kernel.html, the logo, channels.js, state.js, sdk.js, snapshot.js). A change to any of these files needs a rebuild.

How the docs are built#

Each docs page is a Markdown file: docs/site/*.md, plus GUIDE.md, SPEC.md and CHANGELOG.md as they are. The DOCS list in embedded_assets.rs names each page's group, address, title and file, in menu order.

src/usecases/docs.rs renders a page when the browser asks for /docs/<name>. It gives every ## and ### heading an anchor, builds the menu and a search index of every heading, and links each page to its file on GitHub. wardian docs FOLDER writes the same pages as static HTML, with the schemas and the component gallery; the website keeps them in website/. So the program and the website show one text. Contributing says how to add a page.

hexa gates#

hexa is the tool that grades Wardian's structure. Its settings are in .hexa/:

FileHolds
.hexa/project.jsonThe project name, the folders hexa skips (apps, templates, tests, target, data), and which files are the composition root.
.hexa/ADR-rules.tomlWhich path is which layer; the rules that fail the grade (no absolute paths, no model names outside inference, no innerHTML, careful casts); and what the domain may import.

It has two gates:

tests/run-all.sh runs both when hexa is installed. Decisions lists every gate.

This page is docs/site/architecture.md in the repository. Something wrong or missing? Change that file.