Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
01566d2
chore(repo): remove the dead self-host Docker path, fix CI path filte…
Makisuo Nov 10, 2026
77a836e
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
96ac60e
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
91ca524
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
c1faf2a
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
119db83
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
5989d4c
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
44e4f0d
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
6168670
ci: keep every app's alchemy entrypoint in the ts filter
Makisuo Nov 10, 2026
7569e34
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
4b1f852
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
f59d5f6
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
265cdcb
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
22ffa7e
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
21e1b39
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
df9d128
test(domain): drop getSpan and getLog from the iOS operation allowlist
Makisuo Nov 10, 2026
2c4f06c
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
bcde532
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
30b73be
Merge branch 'fix/audit-frontend-fixes' into chore/audit-selfhost-ci
Makisuo Nov 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 21 additions & 31 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,39 +55,42 @@ jobs:
- '.github/workflows/ci.yml'
- '.github/actions/**'
- 'scripts/ci/**'
# Patterns are OR-ed, so a negated one ('!apps/ingest/**') would match
# every file outside that path and make this always true. The extglob
# is a positive pattern: every app except ingest (Rust) and ios (Swift,
# ios.yml's). It excludes by prefix, so an app named ingest* or ios*
# needs its own line.
ts:
- *base
- 'alchemy.run.ts'
- 'knip.json'
- 'bunfig.toml'
- 'patches/**'
- 'test/**'
- 'apps/**'
- 'apps/!(ingest|ios)/**'
Comment thread
Makisuo marked this conversation as resolved.
Resolved
- 'examples/**'
- 'packages/**'
- 'lib/**'
- 'scripts/**'
- 'skills/**'
- '!apps/ingest/**'
# Read by @maple/domain's tests (turbo.json test inputs).
- 'apps/ingest/src/ai_session/**'
- 'apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json'
# Swift is ios.yml's; ios:openapi:check below still covers the contract.
- '!apps/ios/**'
# The workspace closure of apps/cli + apps/local-ui. Update it when
# their package.json workspace deps change.
# Generated; clickhouse:schema:check verifies it.
- 'apps/ingest/src/clickhouse_insert_mappings.rs'
# Typechecked by `tsrs -p tsconfig.alchemy.json`; ingest's is the one the extglob drops.
- 'apps/*/alchemy.run.ts'
- 'apps/ingest/package.json'
# What apps/cli + apps/local-ui are built from. Their workspace closure
# is 21 of packages/ and 6 of lib/, so both are listed whole, as in `workers`.
cli:
- *base
- 'apps/cli/**'
- 'packages/local-server/**'
- 'apps/local-ui/**'
# local-ui renders apps/web's source modules and the shared kit.
# local-ui renders apps/web's source modules.
- 'apps/web/src/**'
- 'packages/views/**'
- 'packages/effect-sdk/**'
- 'packages/domain/**'
- 'packages/query-engine/**'
- 'packages/infra/**'
- 'packages/ui/**'
- 'packages/browser/**'
- 'packages/browser-session/**'
- 'packages/**'
- 'lib/**'
- 'scripts/build-local-binary.sh'
- 'scripts/gen-ui-embed.ts'
# The archive probes test the CLI's own on-disk format, which only
Expand Down Expand Up @@ -183,21 +186,8 @@ jobs:
# shellcheck is preinstalled; a runner of its own would be ~90% setup.
- name: shellcheck
if: ${{ steps.filter.outputs.shell == 'true' }}
run: >-
shellcheck scripts/install.sh scripts/uninstall.sh scripts/build-local-binary.sh
apps/cli/test/native-checkpoint-smoke.sh
apps/cli/test/native-checkpoint-refresh-probe.sh
apps/cli/test/native-local-store-migration.sh
apps/cli/test/native-archive-smoke.sh
apps/cli/test/native-archive-crash-recovery-probe.sh
apps/cli/test/native-archive-adversarial-probe.sh
apps/cli/test/native-archive-gc-probe.sh
apps/cli/test/native-archive-merge-probe.sh
apps/cli/test/native-retention-probe.sh
apps/cli/test/native-remote-auth-smoke.sh
apps/cli/docker/entrypoint.sh
apps/cli/docker/build-image.sh
apps/cli/docker/image-smoke.sh
# The same globs as the `shell` filter, so a new script is linted without a list edit.
run: shellcheck scripts/*.sh apps/cli/test/*.sh apps/cli/docker/*.sh

# The installers claim POSIX /bin/sh; check them against it.
- name: sh -n
Expand Down
36 changes: 18 additions & 18 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ CLICKHOUSE_USER=maple
CLICKHOUSE_PASSWORD=maple
CLICKHOUSE_DATABASE=default

# App database (`alchemy dev` binds MAPLE_DB to the docker Postgres on 5499)
# MAPLE_DB_URL is intentionally blank for the Workers; see db:migrate:local below.
# App database: nothing to set. `alchemy dev` binds MAPLE_DB to the docker Postgres on 5499;
# see db:migrate:local below.

# Auth — self-hosted mode (no Clerk account required)
MAPLE_AUTH_MODE=self_hosted
Expand Down Expand Up @@ -269,18 +269,18 @@ Default health port: `3475`

Loads `../../.env.local` via `bun --env-file`.

### 5. AI chat and triage (in `apps/api`)
### 5. AI chat and triage (`apps/ai`)

`/chat` and `/investigations/*` are served by `apps/api` itself: the `ChatSession` Durable
Object owns each transcript and the agent turn runs in-process on `@opencode-ai/ai` against the
Workers AI `AI` binding. There is no second worker to start — but the chat routes only work
when `apps/api` is running **under `alchemy dev`** (`bun dev api`), because a plain
`bun` process has neither the Durable Object namespace nor the `AI` binding.
`/chat` and `/investigations/*` are forwarded by `apps/api` to the `apps/ai` Worker over a service
binding: the `ChatSession` Durable Object there owns each transcript and runs the agent turn.
Run both under `alchemy dev` (`bun dev api ai`), because a plain `bun` process has no Durable
Object namespace.

The `AI` binding needs a Cloudflare account with Workers AI, through an alchemy profile
(`bunx alchemy login`). Confirm the default model id in `apps/ai/src/platform/Llm.ts` is still in
the catalog — `cd apps/api && bunx wrangler ai models list`; a retired id returns `410`.
Override it with `MAPLE_TRIAGE_MODEL`.
The agent runs on OpenRouter by default and needs `OPENROUTER_API_KEY`. Set
`MAPLE_LLM_PROVIDER=workers-ai` to run it on the Workers AI `AI` binding instead, which needs a
Cloudflare account with Workers AI through an alchemy profile (`bunx alchemy login`). The default
model ids live in `apps/ai/src/platform/Llm.ts`. Override them per provider with
`MAPLE_TRIAGE_MODEL_OPENROUTER` or `MAPLE_TRIAGE_MODEL_WORKERS_AI`.

> **`MAPLE_ORG_ID_OVERRIDE` breaks the investigation chat.** It pins every API request to
> one org, but the browser addresses the session as `<clerk org>:<tab>`. With the
Expand All @@ -294,11 +294,11 @@ sets the opt-in for you; set it yourself when you run the API outside the dev st

When an autonomous investigation fails to start, the `investigations.error` column names it:

| `error` value | Cause |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `agent_unavailable: …` | `apps/api` isn't running under `alchemy dev`, so there is no `CHAT_SESSION` binding |
| `start_failed: a turn is already running for this investigation` | The session is busy; retry |
| `diagnosis_timeout: …` | The turn ran but never called `submit_diagnosis` within 15 minutes |
| `error` value | Cause |
| ---------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `agent_unavailable: …` | No `ChatSession` binding: `apps/ai` isn't running under `alchemy dev` with the API |
| `start_failed: a turn is already running for this investigation` | The session is busy; retry |
| `diagnosis_timeout: …` | The turn ran but never called `submit_diagnosis` within 15 minutes |

### All at once

Expand Down Expand Up @@ -331,7 +331,7 @@ Health checks:
## Tests and quality

```bash
bun test
bun run test # Vitest via turbo, not `bun test`
bun typecheck
bun run format # oxfmt + oxlint --fix
```
Expand Down
28 changes: 6 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ claude mcp add --transport http maple https://api.maple.dev/mcp

| Path | What lives there |
| --------------------------- | ------------------------------------------------------------------------ |
| `apps/web` | Dashboard (Foldkit, Vite) |
| `apps/web` | Dashboard (Foldkit, Vite) |
| `apps/api` | Effect HTTP API, auth and OAuth |
| `apps/ai` | MCP server, chat agent and investigations |
| `apps/ingest` | OTLP ingest gateway: key auth, org enrichment, forwarding |
Expand Down Expand Up @@ -121,21 +121,6 @@ bun run build
bun run test
```

## Docker (Local)

Run the local multi-service stack (API + web + ingest + otel collector):

```bash
docker compose -f docker-compose.yml up --build
```

Services:

- API: `http://localhost:3472`
- Web: `http://localhost:3471`
- Ingest: `http://localhost:3474`
- OTEL collector: `4317` (gRPC), `4318` (HTTP), `13133` (health/extensions)

## Cloudflare Deploy (Alchemy)

Deployments run on **Alchemy v2** (Effect-based): the root `alchemy.run.ts` exports a
Expand All @@ -153,8 +138,8 @@ Stage grammar is `prd` / `pr-<number>` / dev names, resolved via
`resolveHyperdriveRefId`, `resolveMapleProfile`). prd binds the
dashboard-managed Hyperdrive by config ID (`resolveHyperdriveRefId`) — origin credentials
never touch a deploy. `MAPLE_PG_URL` is only needed for dev stages, whose Hyperdrive alchemy
manages itself. PR previews bind **no database at all** (their profile's `database` is `"none"`):
DB-backed routes 500, everything else in the preview works.
manages itself. PR previews get their own Neon branch, migrated by the deploy and deleted on
teardown (`docs/pr-previews.md`).

Run locally:

Expand Down Expand Up @@ -239,14 +224,13 @@ For ingest + key auth, set these at minimum in your root `.env` when running the
- `INGEST_REPLAY_MAX_SESSION_BYTES` (optional; ceiling on the decompressed rrweb
payload one replay session may record, default 1 GiB, `0` disables)

## Persistence (PostgreSQL / PGlite)
## Persistence (PostgreSQL)

Maple persists application state in PostgreSQL:

- Production uses PlanetScale Postgres through Cloudflare Hyperdrive.
- Wrangler development uses the Docker Postgres started by `bun db:up`.
- Non-Worker local entrypoints use embedded PGlite under `apps/api/.data/pglite`; set
`MAPLE_DB_URL=memory://` for an ephemeral database.
- Local development (`bun dev`) uses the Docker Postgres started by `bun db:up`, on port 5499.
- Tests use embedded PGlite (`createTestDb()`); they need no Docker Postgres.

Migration commands:

Expand Down
30 changes: 0 additions & 30 deletions apps/api/Dockerfile

This file was deleted.

Loading
Loading