One who reads and announces markings.
Go web service for classifying PDF documents' security markings using Azure AI Foundry GPT vision models. See _project/README.md for full architecture and roadmap.
- Go 1.26+
- Bun
- ImageMagick 7.0+ with Ghostscript (for PDF rendering)
- Docker and Docker Compose
- Air (for Go hot reload in development)
- mise (optional, for task runner shortcuts)
Development runs the Go server on the host with infrastructure (PostgreSQL, Azurite) in Docker.
Start infrastructure:
docker compose up -dRun database migrations:
go run ./cmd/migrate -upStart the dev server (two terminals):
# Terminal 1 — watch and rebuild the web client
cd app && bun run watch
# Terminal 2 — hot reload the Go server
air
# Terminal 2 — hot reload the Go server with Azure Entra auth
HERALD_ENV=auth airThe web client is available at http://localhost:8080/app.
Run the full stack entirely in Docker (app + PostgreSQL + Azurite):
docker compose -f docker-compose.yml -f compose/app.yml up --buildThis builds the Herald Docker image and starts all services with health-conditioned dependencies. The app loads config.docker.json via the HERALD_ENV=docker overlay to resolve container hostnames.
To stop:
docker compose -f docker-compose.yml -f compose/app.yml downThe web client is available at http://localhost:8080/app.
Herald uses mise as a task runner. All tasks can also be run directly with the underlying commands.
| Task | Command | Description |
|---|---|---|
mise run dev |
go run ./cmd/server |
Run the server in development mode |
mise run build |
go build -o bin/server ./cmd/server |
Build the server binary |
mise run test |
go test ./tests/... |
Run all tests |
mise run vet |
go vet ./... |
Run go vet |
mise run migrate:up |
go run ./cmd/migrate -up |
Run all up migrations |
mise run migrate:down |
go run ./cmd/migrate -down |
Run all down migrations |
mise run migrate:version |
go run ./cmd/migrate -version |
Print current migration version |
mise run web:fmt |
cd app && bunx prettier --write client/ |
Format web client source files |
mise run web:build |
cd app && bun run build |
Build the web client |
mise run web:watch |
cd app && bun run watch |
Watch and rebuild the web client |
Config loading follows a layered overlay pattern:
config.json— base configurationconfig.<HERALD_ENV>.json— environment overlay (e.g.,config.docker.json)secrets.json— gitignored secretsHERALD_*environment variables — final overrides
All environment variables use the HERALD_ prefix (e.g., HERALD_SERVER_PORT, HERALD_DB_HOST).
Azure Entra authentication is opt-in. To enable it locally, create a config.auth.json overlay and run with HERALD_ENV=auth.
App registration setup:
-
Register an app in Azure Entra ID (portal → App registrations → New registration)
- Name:
herald - Supported account types: Single tenant
- Redirect URI: SPA platform →
http://localhost:8080/app/
- Name:
-
Expose an API (left sidebar)
- Set Application ID URI:
api://<client-id>(default) - Add a scope (e.g.,
access) — Admin and users can consent
- Set Application ID URI:
-
API permissions (left sidebar)
- Add your app's scope as a delegated permission (e.g.,
api://<client-id>/access) - Grant admin consent
- Add your app's scope as a delegated permission (e.g.,
-
Note the Directory (tenant) ID and Application (client) ID from the Overview page
Create config.auth.json:
{
"auth": {
"auth_mode": "azure",
"tenant_id": "<tenant-id>",
"client_id": "<client-id>",
"scope": "<scope-name>"
}
}The scope field is the bare scope name (e.g., access). The client composes the full api://<client-id>/<scope> format at runtime. When omitted, defaults to access_as_user.