# Vary Course

## Install Vary with Docker

Get Vary running locally with Docker, verify the compiler, and run a first file from your working directory.

### Docker setup

#### Confirm Docker works

Check that Docker is installed before pulling the Vary image.

```bash
docker --version

```

Run: `docker --version`

Expected output:

```text
^Docker version [0-9]+\.[0-9]+\.[0-9]+, build [0-9a-f]+$
```

#### Create the Vary Docker alias

Create a local `vary` command that runs the compiler container in your current directory.

```bash
alias vary='docker run --rm \
  -u "$(id -u):$(id -g)" \
  -v "$(pwd):/workspace" \
  -w /workspace \
  ghcr.io/ccollicutt/vary:latest'
vary --version

```

Run: `vary --version`

Expected output:

```text
^Vary v[0-9]+-alpha\.[0-9]+ .+$
```

#### Run a Vary file through Docker

Print the working directory and verify it is the container's `/workspace` mount.

```vary
import system

let here: Str = system.cwd().to_str()
print("cwd=" + here)

```

Run: `vary run main.vary`

Expected output:

```text
cwd=/workspace
```

## Vary Introduction

Ten short programs that show what Vary is for: typed code, pure boundaries, contracts, and tests strong enough to survive mutation.

### Why Vary exists

#### Readable code with visible types

Compose a deploy label from typed parts.

```vary
def deploy_label(env: Str, region: Str) -> Str {
    return env + "-" + region
}

print(deploy_label("prod", "us-east-1"))

```

Run: `vary run main.vary`

Expected output:

```text
prod-us-east-1
```

#### Separate calculation from effects

Sum response bytes in a pure helper and print at the edge.

```vary
data Chunk {
    label: Str
    bytes: Int
}

pure def total_bytes(chunks: List[Chunk]) -> Int {
    mut sum = 0
    for chunk in chunks {
        sum = sum + chunk.bytes
    }
    return sum
}

let response = [
    Chunk("headers", 412),
    Chunk("body", 2048),
    Chunk("trailers", 96),
]

print("bytes=" + str(total_bytes(response)))

```

Run: `vary run main.vary`

Expected output:

```text
bytes=2556
```

#### Think about test strength

Compute an exponential backoff so the next lessons have an obvious answer.

```vary
pure def backoff_ms(attempt: Int) -> Int {
    if attempt <= 0 {
        return 100
    }
    if attempt == 1 {
        return 200
    }
    if attempt == 2 {
        return 400
    }
    return 800
}

print("backoff=" + str(backoff_ms(2)) + "ms")

```

Run: `vary run main.vary`

Expected output:

```text
backoff=400ms
```

### Language choices

#### Model a closed choice

Choose a release path with an enum and a top-level match.

```vary
enum ReleaseKind {
    PATCH
    MINOR
    MAJOR
}

let release = ReleaseKind.MINOR

match release {
    case ReleaseKind.PATCH {
        print("release=patch")
    }
    case ReleaseKind.MINOR {
        print("release=minor")
    }
    case ReleaseKind.MAJOR {
        print("release=major")
    }
}

```

Run: `vary run main.vary`

Expected output:

```text
release=minor
```

#### Make mutation explicit

Count rate-limited responses while keeping the response list immutable.

```vary
let responses: List[Int] = [200, 200, 429, 200, 503, 429, 200]
mut throttled = 0

for status in responses {
    if status == 429 {
        throttled = throttled + 1
    }
}

print("throttled=" + str(throttled))

```

Run: `vary run main.vary`

Expected output:

```text
throttled=2
```

#### Target the JVM

Describe a deploy target with typed fields.

```vary
data DeployTarget {
    service: Str
    runtime: Str
    arch: Str
}

let target = DeployTarget("billing-api", "JVM 21", "linux/amd64")
print(target.service + " -> " + target.runtime + " on " + target.arch)

```

Run: `vary run main.vary`

Expected output:

```text
billing-api -> JVM 21 on linux/amd64
```

#### Name assumptions

Return the first non-empty message and declare both ends of the contract.

```vary
pure def first_message(messages: List[Str]) -> Str {
    in {
        len(messages) > 0
    }
    out (value) {
        len(value) > 0
    }
    for message in messages {
        if len(message) > 0 {
            return message
        }
    }
    return "unknown"
}

let errors = ["", "timeout after 30s", "connection refused"]
print("first=" + first_message(errors))

```

Run: `vary run main.vary`

Expected output:

```text
first=timeout after 30s
```

### Workflow

#### Check before running

Define a typed record and a pure predicate suitable for `vary check`.

```vary
data Response {
    endpoint: Str
    status: Int
}

pure def is_2xx(response: Response) -> Bool {
    return response.status >= 200 and response.status < 300
}

let response = Response("/api/users", 200)

if is_2xx(response) {
    print("checked: " + response.endpoint)
}

```

Run: `vary run main.vary`

Expected output:

```text
checked: /api/users
```

#### Prefer observable outputs

Compute a health verdict in a pure helper that a future test can assert against.

```vary
pure def health(failing: Int) -> Str {
    if failing == 0 {
        return "green"
    }
    if failing <= 2 {
        return "amber"
    }
    return "red"
}

let probe_results = [True, True, False, True]
mut failing = 0

for ok in probe_results {
    if not ok {
        failing = failing + 1
    }
}

print(health(failing))

```

Run: `vary run main.vary`

Expected output:

```text
amber
```

#### The confidence loop

Sum the timeout budget across a typed list of pipeline stages.

```vary
data Stage {
    name: Str
    timeout_s: Int
}

let pipeline = [
    Stage("compile", 60),
    Stage("unit", 30),
    Stage("integration", 120),
    Stage("deploy", 90),
]

mut budget = 0
for stage in pipeline {
    budget = budget + stage.timeout_s
}

print("pipeline budget=" + str(budget) + "s")

```

Run: `vary run main.vary`

Expected output:

```text
pipeline budget=300s
```

## Vary Language Tour

Twelve runnable lessons covering typed values, functions, data, state, enums, dictionaries, nullable values, modules, and tests.

### Values, functions, control flow

#### Values and types

Bind typed values and print a release string.

```vary
let project: Str = "acme-billing"
let major: Int = 2
let minor: Int = 14

print(project + " v" + str(major) + "." + str(minor))

```

Run: `vary run main.vary`

Expected output:

```text
acme-billing v2.14
```

#### Functions and branches

Classify an HTTP status with a pure helper.

```vary
pure def class_of(status: Int) -> Str {
    if status >= 500 {
        return "5xx"
    }
    if status >= 400 {
        return "4xx"
    }
    if status >= 200 {
        return "2xx"
    }
    return "other"
}

print("404 -> " + class_of(404))

```

Run: `vary run main.vary`

Expected output:

```text
404 -> 4xx
```

#### Lists and loops

Count how many requests exceeded a latency budget.

```vary
let durations_ms: List[Int] = [42, 180, 95, 240, 33]
mut slow: Int = 0

for d in durations_ms {
    if d > 100 {
        slow = slow + 1
    }
}

print("slow=" + str(slow))

```

Run: `vary run main.vary`

Expected output:

```text
slow=2
```

### Modeling data

#### Data types

Define an immutable record and read its generated `toString`.

```vary
data Response {
    status: Int
    bytes: Int
}

let resp = Response(200, 1024)
print(str(resp))

```

Run: `vary run main.vary`

Expected output:

```text
Response(status=200, bytes=1024)
```

#### Classes and state

Track remaining retries behind a small stateful class.

```vary
class RetryBudget(max: Int) {
    mut remaining: Int = max

    def consume(self) -> None {
        self.remaining = self.remaining - 1
    }

    def left(self) -> Int {
        return self.remaining
    }
}

let budget = RetryBudget(3)
budget.consume()
print("remaining=" + str(budget.left()))

```

Run: `vary run main.vary`

Expected output:

```text
remaining=2
```

#### Enums and exhaustive match

Advance a build state machine with exhaustive matching.

```vary
enum BuildState {
    QUEUED
    RUNNING
    PASSED
    FAILED
}

pure def advance(state: BuildState) -> BuildState {
    match state {
        case BuildState.QUEUED {
            return BuildState.RUNNING
        }
        case BuildState.RUNNING {
            return BuildState.PASSED
        }
        case BuildState.PASSED {
            return BuildState.PASSED
        }
        case BuildState.FAILED {
            return BuildState.FAILED
        }
    }
}

print(str(advance(BuildState.QUEUED)))

```

Run: `vary run main.vary`

Expected output:

```text
RUNNING
```

#### Nullable values

Fall back to a default when a configured value is missing.

```vary
let configured: Int? = None
mut port: Int = 8080

if configured is not None {
    port = configured
}

print("port=" + str(port))

```

Run: `vary run main.vary`

Expected output:

```text
port=8080
```

### Lookups and modules

#### Typed maps

Look up a service timeout from a config map.

```vary
let timeouts: Dict[Str, Int] = {"db": 30, "http": 10, "cache": 60}

print("http=" + str(timeouts["http"]) + "s")

```

Run: `vary run main.vary`

Expected output:

```text
http=10s
```

#### Module-ready helpers

Format an artifact coordinate with a pure helper.

```vary
pure def coord(group: Str, name: Str, version: Str) -> Str {
    return group + ":" + name + ":" + version
}

print(coord("acme", "billing", "1.4.2"))

```

Run: `vary run main.vary`

Expected output:

```text
acme:billing:1.4.2
```

### Tests

#### Shape helpers for assertion

Print the inputs and outputs the next lesson will assert on.

```vary
pure def class_of(status: Int) -> Str {
    if status >= 500 {
        return "5xx"
    }
    if status >= 400 {
        return "4xx"
    }
    return "2xx"
}

print(class_of(503) + "," + class_of(200))

```

Run: `vary run main.vary`

Expected output:

```text
5xx,2xx
```

#### Run passing assertions

Run two `observe` assertions against the helper.

```vary
pure def class_of(status: Int) -> Str {
    if status >= 500 {
        return "5xx"
    }
    if status >= 400 {
        return "4xx"
    }
    return "2xx"
}

test "server errors classify as 5xx" {
    observe class_of(503) == "5xx"
}

test "client errors classify as 4xx" {
    observe class_of(404) == "4xx"
}

```

Run: `vary test tests/grade_test.vary`

Expected output:

```text
Results: 2 passed, 0 failed
```

#### Read a failing test

Run a file with one intentional boundary mistake and find the summary line.

```vary
pure def class_of(status: Int) -> Str {
    if status >= 500 {
        return "5xx"
    }
    if status >= 400 {
        return "4xx"
    }
    return "2xx"
}

test "server errors classify as 5xx" {
    observe class_of(503) == "5xx"
}

test "boundary 500 should be 4xx" {
    observe class_of(500) == "4xx"
}

```

Run: `vary test tests/grade_test.vary`

Expected output:

```text
Results: 1 passed, 1 failed
```

## Vary Testing

Seven runnable lessons covering `test`, `observe`, edge cases, error paths, output, and naming.

### First tests

#### The smallest test

Verify one fact about one function.

```vary
pure def is_even(n: Int) -> Bool {
    return n % 2 == 0
}

test "4 is even" {
    observe is_even(4) == True
}

```

Run: `vary test tests/even_test.vary`

Expected output:

```text
Results: 1 passed, 0 failed
```

#### Multiple observations per test

Group several related facts under one named behaviour.

```vary
pure def normalize_path(path: Str) -> Str {
    if path.startswith("/") {
        return path
    }
    return "/" + path
}

test "normalize_path enforces a single leading slash" {
    observe normalize_path("api") == "/api"
    observe normalize_path("/api") == "/api"
    observe normalize_path("") == "/"
}

```

Run: `vary test tests/path_test.vary`

Expected output:

```text
Results: 1 passed, 0 failed
```

#### Multiple tests per file

Cover distinct behaviours with separate named tests.

```vary
pure def class_of(status: Int) -> Str {
    if status >= 500 {
        return "5xx"
    }
    if status >= 400 {
        return "4xx"
    }
    if status >= 200 {
        return "2xx"
    }
    return "other"
}

test "2xx range" {
    observe class_of(200) == "2xx"
}

test "4xx range" {
    observe class_of(404) == "4xx"
}

test "5xx range" {
    observe class_of(503) == "5xx"
}

test "below 200 is other" {
    observe class_of(100) == "other"
}

```

Run: `vary test tests/status_test.vary`

Expected output:

```text
Results: 4 passed, 0 failed
```

### Edge cases and errors

#### Test the boundaries

Assert both sides of every edge where behaviour changes.

```vary
pure def is_2xx(status: Int) -> Bool {
    return status >= 200 and status < 300
}

test "200 is the lower edge" {
    observe is_2xx(200) == True
    observe is_2xx(199) == False
}

test "299 is the upper edge" {
    observe is_2xx(299) == True
    observe is_2xx(300) == False
}

```

Run: `vary test tests/range_test.vary`

Expected output:

```text
Results: 2 passed, 0 failed
```

#### Test error paths

Use `observe throws` to assert a block raises.

```vary
def parse_port(s: Str) -> Int {
    let n = int(s)
    if n < 1 or n > 65535 {
        raise "port out of range: " + s
    }
    return n
}

test "valid port parses" {
    observe parse_port("8080") == 8080
}

test "out of range raises" {
    observe throws { parse_port("99999") }
}

test "non-numeric raises" {
    observe throws { parse_port("nope") }
}

```

Run: `vary test tests/port_test.vary`

Expected output:

```text
Results: 3 passed, 0 failed
```

### Running and reading

#### Read the test output

Locate the pass/fail summary line and understand the exit code.

```vary
pure def retry_delay_ms(attempt: Int) -> Int {
    if attempt <= 0 {
        return 100
    }
    if attempt >= 5 {
        return 16000
    }
    mut delay: Int = 100
    mut i: Int = 0
    while i < attempt {
        delay = delay * 2
        i = i + 1
    }
    return delay
}

test "first attempt is 100ms" {
    observe retry_delay_ms(0) == 100
}

test "doubles each attempt" {
    observe retry_delay_ms(1) == 200
    observe retry_delay_ms(2) == 400
    observe retry_delay_ms(3) == 800
}

test "saturates at 16000ms" {
    observe retry_delay_ms(5) == 16000
    observe retry_delay_ms(10) == 16000
}

```

Run: `vary test tests/backoff_test.vary`

Expected output:

```text
Results: 3 passed, 0 failed
```

#### Names are documentation

Write test names that read like the spec.

```vary
pure def truncate(s: Str, limit: Int) -> Str {
    if len(s) <= limit {
        return s
    }
    return s[:limit - 1] + "…"
}

test "truncate returns the input when it fits inside the limit" {
    observe truncate("hello", 10) == "hello"
}

test "truncate replaces the last character with an ellipsis when too long" {
    observe truncate("hello world", 5) == "hell…"
}

test "truncate handles the exact-fit boundary without modification" {
    observe truncate("hello", 5) == "hello"
}

```

Run: `vary test tests/truncate_test.vary`

Expected output:

```text
Results: 3 passed, 0 failed
```

## Mutation Testing

Five runnable lessons covering `vary mutate`, scores, survivors, boundary mutations, and the inner-loop workflow.

### First mutations

#### Run your first mutation pass

See that one happy-path test leaves most mutants alive.

```vary
pure def has_free_shipping(order_total: Int) -> Bool {
    return order_total >= 50
}

test "larger order qualifies" {
    observe has_free_shipping(75) == True
}

```

Run: `vary mutate free_shipping.vary --tests free_shipping.vary --quick`

Expected output:

```text
(?m)Mutation score: \d{1,2}%
```

#### Pin down the survivors

Add a boundary and an opposite-branch test until the score hits 100%.

```vary
pure def has_free_shipping(order_total: Int) -> Bool {
    return order_total >= 50
}

test "larger order qualifies" {
    observe has_free_shipping(75) == True
}

test "smaller order does not qualify" {
    observe has_free_shipping(20) == False
}

test "exactly 50 qualifies" {
    observe has_free_shipping(50) == True
}

test "49 does not qualify" {
    observe has_free_shipping(49) == False
}

```

Run: `vary mutate free_shipping.vary --tests free_shipping.vary --quick`

Expected output:

```text
(?m)Mutation score: 100%
```

### Common survivors

#### Boundary mutants survive without edge tests

See how range predicates leak mutants when tests skip the edges.

```vary
pure def is_2xx(status: Int) -> Bool {
    return status >= 200 and status < 300
}

test "200 is 2xx" {
    observe is_2xx(200) == True
}

test "404 is not 2xx" {
    observe is_2xx(404) == False
}

```

Run: `vary mutate is_2xx.vary --tests is_2xx.vary --quick`

Expected output:

```text
(?m)Mutation score: \d{1,2}%
```

#### Lock in every edge

Cover all four boundaries of a range predicate.

```vary
pure def is_2xx(status: Int) -> Bool {
    return status >= 200 and status < 300
}

test "200 is the lower edge" {
    observe is_2xx(200) == True
    observe is_2xx(199) == False
}

test "299 is the upper edge" {
    observe is_2xx(299) == True
    observe is_2xx(300) == False
}

test "404 is not 2xx" {
    observe is_2xx(404) == False
}

```

Run: `vary mutate is_2xx.vary --tests is_2xx.vary --quick`

Expected output:

```text
(?m)Mutation score: 100%
```

### Practical use

#### Mutation in the inner loop

Hit 100% on a multi-branch classifier with one edge pair per threshold.

```vary
pure def class_of(status: Int) -> Str {
    if status >= 500 {
        return "5xx"
    }
    if status >= 400 {
        return "4xx"
    }
    if status >= 200 {
        return "2xx"
    }
    return "other"
}

test "200 is the 2xx lower edge" {
    observe class_of(200) == "2xx"
    observe class_of(199) == "other"
}

test "400 is the 4xx lower edge" {
    observe class_of(400) == "4xx"
    observe class_of(399) == "2xx"
}

test "500 is the 5xx lower edge" {
    observe class_of(500) == "5xx"
    observe class_of(499) == "4xx"
}

test "deep 5xx still classifies as 5xx" {
    observe class_of(503) == "5xx"
}

```

Run: `vary mutate class_of.vary --tests class_of.vary --quick`

Expected output:

```text
(?m)Mutation score: 100%
```

## Install and Start Via Server

Prepare an Ubuntu host, confirm Vary and Via are installed, install Via prerequisites, initialize the control plane, and verify the Via systemd services.

### Install

#### Verify Vary and Via commands

Confirm the host already has the Vary compiler and Via server CLI on PATH.

```bash
# Confirm the Vary compiler is installed and available on PATH.
command -v vary
vary --version

# Confirm the Via server CLI is installed and available on PATH.
command -v via
via --version

```

Run: `via --version`

Expected output:

```text
(?m)^Via\s+.+$
```

#### Install host prerequisites

Install Ubuntu packages for Git, Docker, Caddy, and the Via Docker network.

```bash
# Install Git, Docker, Caddy, and the Via Docker network.
via install --print-prereq-script | sudo bash

# Create Via users, directories, systemd units, and admin group access.
via install

```

Run: `via install`

Expected output:

```text
(?m)^\s*Installed changes applied$|^\s*Installed no changes \(already installed\)$
```

#### Initialize the control plane

Write server config, create the database, and mint signing keys.

```bash
# Write config, create the database, mint keys, and start Via services.
via init --domain vary.example.com

# Confirm the host layout, services, Docker, Git, and proxy are healthy.
via doctor

```

Run: `via doctor`

Expected output:

```text
(?m)^.*PASS.*$
```

### Verify

#### Verify Via services

Confirm the control plane, builder, runner, config server, test runner, and managed Caddy service are active.

```bash
# Show every Via service and whether it is active.
via status

# Re-run the full host and runtime health checks after startup.
via doctor

```

Run: `via status`

Expected output:

```text
(?m)\bactive\b
```

## Push and Test an App on Via

Create the first operator session, connect a local Vary project, test it, push a deploy, and inspect deploy output.

### Provision

#### Create an operator and app on the server

Mint a one-time admin token and create the app record that will receive source pushes.

```bash
# Create a one-time admin token for the first operator.
via admin create alice

# Create the server-side app record and bare Git repo.
via app ensure my-api --owner alice

```

Run: `via app ensure my-api --owner alice`

Expected output:

```text
(?i)created|exists|ensured|my-api
```

#### Log in from your workstation

Exchange the one-time admin token for workstation credentials.

```bash
# Exchange the one-time admin token for workstation credentials.
vary login https://vary.example.com --name alice --token-stdin

```

Run: `vary login https://vary.example.com --name alice --token-stdin`

Expected output:

```text
(?i)session|credentials|logged in|expires
```

#### Connect a local project

Record the Via server and app id in the project's `vary.toml`.

```bash
# Work from the local Vary project you want to deploy.
cd my-api

# Record the Via server URL and app name in vary.toml.
vary app init --server https://vary.example.com --app my-api

# Confirm only the deployment config changed.
git status --short

# Commit the config so deploys can push a clean tree.
git add vary.toml
git commit -m "Configure Via deployment"

```

Run: `vary app init --server https://vary.example.com --app my-api`

Expected output:

```text
(?m)^.*vary\.toml.*$
```

### Test and push

#### Test locally and deploy committed source

Run the project's tests, push the current Git commit to Via, and wait for the deploy result.

```bash
# Run local tests before pushing source to Via.
vary test

# Via deploys expect committed source by default.
git status --short

# Push the current commit and wait for the deploy result.
vary app deploy

# Confirm the app's current deploy and runtime state.
vary app status

```

Run: `vary app deploy`

Expected output:

```text
(?i)\brunning\b
```

#### Inspect build output from your workstation

Find the build id, inspect the build detail, and stream test logs for the deploy.

```bash
# List recent server builds for the app.
vary app builds --app my-api

# Inspect one build record in detail.
vary app build <build-id> --app my-api

# Stream the test logs when a result needs detail.
vary app logs --tests <build-id> --app my-api

```

Run: `vary app builds --app my-api`

Expected output:

```text
(?i)build|running|passed|failed
```

#### Read the test result on the server

Confirm the server saw the same test result by reading Via's local state directory.

```bash
# Read the server-side test result directly from the Via state directory.
vary app tests <build-id> --app my-api --local --state-dir /var/lib/via

```

Run: `vary app tests <build-id> --app my-api --local --state-dir /var/lib/via`

Expected output:

```text
(?i)passed|failed|tests
```

### Route and observe

#### Attach traffic and read logs

Request a hostname, verify ownership, add a route, and stream runtime logs.

```bash
# Ask Via to create a domain ownership challenge.
vary app domain request my-api my-api.apps.example.com --target prod

# Confirm the required DNS record is visible.
vary app domain verify my-api my-api.apps.example.com --target prod

# Attach the verified hostname to the running app target.
vary app route add my-api my-api.apps.example.com --target prod --port 8080

# Stream runtime logs from the active container.
vary app logs --runtime

```

Run: `vary app logs --runtime`

Expected output:

```text
(?m)^.*my-api.*$
```

