# Aussom Server AI Guide

**What this file is:** a briefing for an AI coding assistant that has been
asked to build an app for Aussom Server. Aussom is a small language with
almost no presence in model training data, and Aussom Server adds its own
routing rules, annotations, and module set on top of it. A model writing
from instinct will produce an app that parses cleanly and never receives a
request. This guide covers where the documentation lives, how a URL becomes
a method call, how to test an app without starting a server, and the
specific mistakes that break Aussom Server apps.

Aussom Server apps are written in the same language as the Aussom CLI.
**Read `aussom-ai-guide.md` first** for the core language rules: braced
bodies, no `continue`, `size()` versus `length()`, reserved names, and the
rest. This guide does not repeat them, and they cause most of the errors.

---

## 1. Find the documentation before you write anything

### Server module documentation

The server's own modules are documented in the `docs` directory of the
`aussom-server` source repository, one file per module:

```
aussom-server/docs/
  aussomserver.aus.md   <- HttpReq, WsConn, AppBase, props, cache, api
  app.aus.md            <- the app lifecycle static class
  http.aus.md
  jdbc.aus.md
  lucene.aus.md
  ...
```

These are generated by `build-docs.sh` from the server source. Note that
they are **not** shipped inside the Docker image, so if you are working
against a running container you will need the source repo or the website.

### Core language documentation

The core types (`string`, `list`, `map`, `int`, `c`, `lang`) are part of
the base language, not the server. Those live with an Aussom CLI install:

| Platform | Documentation directory |
| --- | --- |
| Linux | `/opt/aussom/lib/app/docs/` |
| macOS | `/Applications/aussom.app/Contents/app/docs/` |
| Windows | `C:\Program Files\aussom\app\docs\` |

If those paths are missing, run `aussom -jl` to print the CLI jar path; the
`docs` directory is its sibling. Start with `markdown/lang.aus.md`.

### Web fallback

- Aussom Server: https://aussom-lang.com/docsProduct?product=aussom-server
- Aussom CLI (core language): https://aussom-lang.com/docsProduct?product=aussom

### A running server documents itself

If the app is deployed and the endpoints are enabled in `applications.yaml`,
the server renders its own reference:

- `GET /<appName>/doc` returns the app's Aussom doc (needs `hostDocEndpoint: true`)
- `GET /<appName>/api` returns the OpenAPI definition (needs `hostApiEndpoint: true`)

### Where the server itself is installed

Aussom Server runs as a container. The default layout is:

| Location | Holds |
| --- | --- |
| `/srv/aussom/config` | `config.yaml` (host side) |
| `/srv/aussom/apps` | `applications.yaml` and one directory per app |
| `/srv/aussom/lib` | extra runtime jars, such as JDBC drivers |
| `/var/log/aussom-server` | log files |
| `/opt/aussom-server` | the jar and entry scripts, inside the container |

Inside the container the host directories are mounted at
`/var/aussom-server/config`, `/var/aussom-server/apps`, and
`/var/aussom-server/lib`.

---

## 2. How an app is shaped

An app is one class that extends `AppBase`. Public methods become HTTP
routes.

```aussom
include http;
include file;

@Api(
    version = "0.0.1",
    contactName = "Austin Lehman"
)
class helloworld : AppBase {
    /**
     * GET /helloworld/ and /helloworld/index
     */
    public index(req) {
        req.putHeader("content-type", "text/plain");
        req.send("Hello World!");
    }

    /**
     * GET /helloworld/hello
     */
    public hello(req) {
        req.send("Hello!");
    }
}
```

### Routing: this is the part that surprises people

A request path is `/<appName>/<firstSegment>`. The app name comes from
`applications.yaml`, not from the class name or the directory. The **first
path segment after the app name** selects the method. Nothing deeper is
used for dispatch.

| Request | Runs |
| --- | --- |
| `/helloworld/` | `index` |
| `/helloworld/index` | `index` |
| `/helloworld/hello` | `hello` |
| `/helloworld/hello/extra/bits` | `hello` (the rest is not part of dispatch) |
| `/helloworld/nope` | 404 |

To be reachable over HTTP a method must be `public`, must not be the
constructor, and must not carry `@Websocket` or `@Scheduled`. A name that
only has `@Websocket` or `@Scheduled` overloads returns 400 over plain
HTTP, not 404.

Resolution order for a request is:

1. `/doc` and `/api`, when those endpoints are enabled.
2. A matching file under the app's public directory, when `hostResources`
   is on.
3. A handler method.
4. 404.

That order matters. A file in `public/` shadows a handler with the same
path, and enabling the doc endpoint makes a handler named `doc`
unreachable.

### Handlers

Every HTTP handler takes one argument, the request object:

```aussom
public newContact(req) {
    if (req.getReqMethod() == "POST") {
        mp = json.parse(req.getBody());
        // ...
    }
    req.putHeader("content-type", "application/json");
    req.send(resp.toJson());
}
```

There is no separate response object. `req` carries both sides.

Useful `HttpReq` methods, all documented in `aussomserver.aus.md`:

| Purpose | Methods |
| --- | --- |
| Request line | `getReqMethod`, `getReqPath`, `getReqURI`, `getReqURL`, `getQueryString` |
| Parameters | `getQueryParams`, `getPathParams`, `addQueryParam`, `addPathParam` |
| Headers and cookies | `getReqHeaders`, `getReqCookies`, `putHeader`, `setCookie` |
| Body in | `getBody`, `formSubmitted`, `getFormData`, `getStreamingFormData` |
| Body out | `send`, `sendBytes`, `sendChunk`, `setStatusCode` |
| Client | `getSrcAddress`, `getSrcHost`, `getSrcPort` |

### WebSocket handlers

Mark the method `@Websocket`. It receives a connection, not a request. The
usual pattern is a small per-connection class that holds the state.

```aussom
@Websocket
public echo(ws) {
    ctx = new wsEchoCtx();
    ctx.attach(ws);
}

class wsEchoCtx {
    public ws = null;

    public attach(ws) {
        this.ws = ws;
        ws.onMessage(::handleMsg);
    }

    public handleMsg(text) {
        this.ws.send("echo: " + text);
    }
}
```

`WsConn` offers `send`, `sendBytes`, `onMessage`, `onBinary`, `onClose`,
`onError`, `close`, plus `getReqPath`, `getReqHeaders`, `getQueryString`,
and `getSrcAddress`.

Note `::handleMsg`: that is the Aussom callback operator, bound to the
current object. It is how every server callback is registered.

### Scheduled jobs

```aussom
@Scheduled(every = "2s", desc = "fast tick")
public tick() {
    this.tickCount += 1;
}
```

A scheduled method takes no request and is not reachable over HTTP. Any
long-running job must poll `app.isStopRequested()` so a reload or shutdown
can interrupt it:

```aussom
@Scheduled(every = "10s")
public slowLoop() {
    i = 0;
    while (i < 50) {
        if (app.isStopRequested()) { return; }
        i += 1;
    }
}
```

### App lifecycle

The `app` static class handles reload and shutdown:

`isReloadPending`, `isShutdownPending`, `isStopRequested`,
`onBeforeReload`, `onBeforeShutdown`, `registerListener`,
`unregisterListener`, `loadJar`.

```aussom
public registerReload(req) {
    app.onBeforeReload(::onReloadCb);
    req.send("registered");
}
```

Apps hot reload when their files change, so anything holding an external
resource should release it in an `onBeforeReload` callback.

### Properties and secrets

`props` reads YAML config and handles encrypted values:

```aussom
sg = new sendgrid(props.decrypt(this.sendgridEncKey));
```

`props.encrypt`, `props.decrypt`, `props.load`, `props.get`, and
`props.getEnv` are available. The encryption key comes from `key` in
`config.yaml` or from `-Daussom-server.key` at startup. Generate one with
`aussom-server -k`.

### The API annotation

`@Api` on the class supplies the OpenAPI document metadata. `@Api` on a
method describes that route, and `@Api` with `@ApiRequired` on a member
describes a model field.

```aussom
@Api(
    request = "contact",
    response = "contactResponse",
    method = "post"
)
public newContact(req) { ... }
```

Generate the definition without running the server:

```
$ aussom-server -a -cf config.yaml -ad apps
```

---

## 3. Configuration

### applications.yaml

Lives in the apps directory and lists every app. The `name` field sets the
URL prefix; `appDirectory` is where the code lives. They do not have to
match.

```yaml
applications:
- name: helloworld
  appDirectory: helloworld
  enabled: true
  publicHttpDirectory: public
  reloadOnFileChange: true
  hostResources: true
  hostApiEndpoint: true
  hostDocEndpoint: true
  logLevel: info
```

### config.yaml

Holds the server settings, keyed by environment. `env` at the root picks
which block is active, and the block name must match exactly.

```yaml
env: "local"

local:
  server:
    name: "Integration Platform Server"
    host: "0.0.0.0"
    port: 8081
    appDir: "test-apps"
    logDir: "logs"
    logLevel: "info"
```

### App directory layout

```
apps/
  applications.yaml
  helloworld/
    helloworld.aus     <- class name matches the file
    public/            <- static files, served when hostResources is on
    app_data/          <- private data, never served
```

`app_data` is excluded from the include path and is not reachable over
HTTP. Put anything private there.

---

## 4. Verify your app

### Parse check

```
$ aussom-server -d -o /tmp/docout apps/helloworld/helloworld.aus
```

Options come before the file name. A syntax error is reported as
`PARSE_ERROR` with a line and column.

Remember the lesson from the CLI guide: a clean parse is not a working
app. Undefined names, wrong method names, and bad call signatures are all
runtime errors that fire only when the line executes.

### In-process tests, without starting a server

This is the fastest useful feedback loop. `aunitserver` loads an app into
a test engine and drives its handlers with mock request and connection
objects, so there is no port and no HTTP handshake.

```aussom
include aunitserver;

@Test(name = "MockWsConn end-to-end")
class mockWsTest : AppTest {
    private app = null;

    @Before
    public setUp() {
        this.app = this.loadApp("wstest", "test-apps/wstest");
    }

    @Test(name = "echo replies once per text frame")
    public echoesEachFrame() {
        ws = this.newWs();
        ws.setReqPath("/wstest/echo");

        this.app.echo(ws);

        ws.fireMessage("hello");
        sent = ws.getSentTexts();
        return test.expect(sent[0], "echo: hello");
    }
}
```

For HTTP handlers use `this.newReq()` and read the result back with
`req.getCapturedBody()`.

Run them:

```
$ aussom-server -t  aussom_server_tests/my_test.aus    # one class
$ aussom-server -ta aussom_server_tests/all.aus        # every class loaded
```

Run from the repo root; includes resolve against the working directory.

### Server CLI flags

| Flag | Does |
| --- | --- |
| `-s`, `--start` | start the server |
| `-cf`, `--configfile` | path to `config.yaml` |
| `-ad`, `--appdir` | app directory |
| `-t`, `--test` | run a test file |
| `-ta` | run every test class loaded |
| `-d`, `--doc` | generate Aussom doc (parse check) |
| `-o`, `--outdir` | output directory for `-d` |
| `-a`, `--api` | generate the OpenAPI definition |
| `-k`, `--genKey` | generate a random key |
| `-e`, `--encrypt` | encrypt a string |
| `-i`, `--install` | write default config into the current directory |

---

## 5. Traps

The core language traps are in `aussom-ai-guide.md` and they apply here
unchanged. These are the ones specific to Aussom Server.

### The app name is not the class name

The URL prefix comes from `name` in `applications.yaml`. An app whose
class and directory are both `my-company` but whose `name` is `mycompany`
answers at `/mycompany/`, not `/my-company/`. Check the YAML, not the file
tree.

### Only the first path segment routes

`/app/users/42` calls `users`, and `42` is not passed as an argument. Read
extra path data with `getPathParams` or `getQueryParams`, or parse
`getReqPath` yourself.

### A public file shadows a handler

Static resources are checked before handler dispatch. If `public/status`
exists, a `status` handler never runs. `/doc` and `/api` shadow handlers
of those names too when the endpoints are enabled.

### Handlers must be public and unannotated

A private method is not a route. A method carrying `@Websocket` or
`@Scheduled` is not an HTTP route, and requesting it returns 400 rather
than 404, which reads like a different bug.

### Long jobs must poll for stop

A `@Scheduled` job that loops without checking `app.isStopRequested()`
blocks reload and shutdown.

### Maps from JSON support dot access

`json.parse` returns a map, and its keys can be read either way:

```aussom
mp = json.parse(req.getBody());
first = mp.firstName;        // works
first = mp.get("firstName"); // also works
```

Guard optional keys with `mp.containsKey("phoneNumber")` before reading
them.

### Writing Java extern classes

If you extend the server with Java rather than staying in Aussom:

- Every class bound as `extern class Foo : com.x.FooImpl` needs a
  **public no-arg constructor**. The runtime constructs one reflectively
  before `setExternObject` replaces it, and Java does not synthesize a
  no-arg constructor once you declare a parameterized one. Without it you
  get `Instantiate extern instantiation exception`.
- A setter that should chain must return `env.getClassInstance()`, not
  `env.getCurObj()`. `getCurObj()` is null unless the runtime already
  pushed a current-object frame, so chained calls see `cnull` and fail
  with "no overload matching call signature".
- Any code that configures a server-style engine must go through
  `AussomServerApp.applyAppEngineIncludePaths`. An abbreviated inline
  version silently gives apps a different include-path tier list than
  production.

---

## 6. Module index

Modules available to a server app:

`app`, `aussomserver`, `codec`, `file`, `firebase`, `http`, `jdbc`,
`lucene`, `markdown`, `sendgrid`, `ssh`, `thread`, `xml`, `yaml`, `zip`

`aussomserver` is automatic and supplies `AppBase`, `HttpReq`, `WsConn`,
`props`, `cache`, and `api`. The core language classes (`c`, `lang`,
`string`, `list`, `map`) are always present. Everything else needs an
explicit `include`.

The `cache` class provides `put`, `get`, and `invalidate` for per-app
caching.

---

## 7. Checklist

Before handing over an Aussom Server app:

1. Read `aussom-ai-guide.md` and applied the core language rules.
2. Looked up every method in the server docs rather than assuming.
3. Confirmed the app `name` in `applications.yaml` matches the URL you
   expect.
4. Made every route `public`, with no `@Websocket` or `@Scheduled`.
5. Provided an `index` handler if `/` should answer.
6. Checked no `public/` file shadows a handler path.
7. Polled `app.isStopRequested()` in any long `@Scheduled` job.
8. Ran `aussom-server -d -o <dir> <file>` and got no `PARSE_ERROR`.
9. Wrote `AppTest` tests with `loadApp` and mock request objects, and ran
   them.
