Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,9 @@ dmypy.txt
# Keys
*.pem
tenants.json

# Local SQLite / Temporal dev databases
*.db
*.db-wal
*.db-shm
*.db-journal
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

## [Unreleased]

## [0.5.0] - 2026-08-13

### Features
- `orcha run` runs the complete local stack: migrations, a Temporal dev server, the API, and a worker, with prefixed logs and shared teardown
- Requires the `temporal` CLI; `--reset` deletes `orcha.db`/`temporal.db` first, a normal shutdown preserves them
- SQLite is the local-development default database; PostgreSQL remains the production default
- `DB_URL` overrides everything; otherwise `DB_DIALECT` picks between `DB_PATH` (sqlite) and the `DB_USER`/`DB_PASSWORD`/`DB_HOST`/`DB_PORT`/`DB_NAME` fields (postgresql)
- **Breaking:** the `PGUSER`/`PGPASSWORD`/`PGHOST`/`PGPORT`/`PGDATABASE` settings are removed; use the `DB_*` fields instead
- *(auth)* Add `DEV_MODE`, which runs the API with authentication off and serves every request as the `dev` tenant
- `orcha run` turns it on, so a local InvenioRDM needs no keys; `AUTH_DISABLED` overrides it either way for exercising real tenant tokens locally
- **Breaking:** `AUTH_DISABLED` now defaults to unset and follows `DEV_MODE`; the tenant it stands in for is `dev` rather than `dev-tenant`
- *(cli)* Add `orcha tenants` for the tenant registry
- `add` registers a tenant's public key, `list` shows what is registered
- `token` signs a token for a tenant from its private key
- Add evals pipeline to Orcha

## [0.4.0] - 2026-07-30

### Features
Expand Down Expand Up @@ -105,7 +121,8 @@
## [0.0.1] - 2026-06-09
_First release._

[unreleased]: https://github.com/inveniosoftware/orcha/compare/v0.4.0...HEAD
[unreleased]: https://github.com/inveniosoftware/orcha/compare/v0.5.0...HEAD
[0.5.0]: https://github.com/inveniosoftware/orcha/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/inveniosoftware/orcha/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/inveniosoftware/orcha/compare/v0.2.2...v0.3.0
[0.2.2]: https://github.com/inveniosoftware/orcha/compare/v0.2.1...v0.2.2
Expand Down
127 changes: 59 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,37 @@ Orcha AI extraction.
uv sync
```

### 2. Start infrastructure (PostgreSQL + Temporal)
### 2. Run the complete local stack

SQLite is the local-development default, so no Docker services are required.
`orcha run` applies migrations against a local `orcha.db` file, starts a
Temporal dev server backed by `temporal.db`, then the API and a worker:

```bash
uv run orcha services start
uv run orcha run
```

### 3. Apply database migrations
This requires the [`temporal` CLI](https://docs.temporal.io/cli#install) to
be installed. Stopping it (Ctrl-C) preserves both database files; pass
`--reset` to delete them first and start from a clean state:

```bash
uv run orcha migrate
uv run orcha run --reset
```

### 4. Start the application
### Running against PostgreSQL

```bash
# Start both server and worker
uv run orcha run
To use PostgreSQL instead (e.g. to match production), start the Dockerized
services, point the app at them, and run the API and worker as separate
processes:

# Or start them individually
uv run orcha run server # FastAPI dev server
uv run orcha run workers # Temporal worker for the default queue
```bash
uv run orcha services start
export DB_DIALECT=postgresql # DB_USER/DB_PASSWORD/DB_HOST/DB_PORT/DB_NAME
# default to the docker-compose values
uv run orcha migrate
uv run orcha run server --dev # FastAPI dev server
uv run orcha run workers # Temporal worker for the default queue

# Run a worker for a specific queue
uv run orcha run workers --task-queue low-priority
Expand All @@ -48,9 +58,27 @@ The API uses **multi-tenant RS256 (asymmetric) JWT authentication**. Each tenant

Tenants are identified by the `iss` (issuer) claim in the JWT. To support zero-downtime key rotation, the server allows multiple public keys per tenant. In token headers, tenants must include a Key ID (`kid`) that matches one of their defined keys in the configuration.

### Local development

`orcha run` sets `DEV_MODE`, which turns authentication off: no token is
required and every request runs as the `dev` tenant, so tenant scoping behaves
the same as it does anywhere else.

```bash
uv run orcha run
curl http://localhost:8000/
```

`AUTH_DISABLED` overrides that either way, so `AUTH_DISABLED=0 uv run orcha run`
exercises real tenant tokens against the local stack.

See [Running against a local InvenioRDM](docs/invenio.md) for pointing an
instance at a local Orcha.

### Tenant Configuration

Create a `tenants.json` file at the project root:
Real tenants live in `tenants.json` (override with `TENANTS_CONFIG_PATH`), keyed
by the `iss` claim their tokens carry:

```json
{
Expand All @@ -59,75 +87,33 @@ Create a `tenants.json` file at the project root:
"public_keys": {
"kid-1": "-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----"
}
},
"tenant-b": {
"name": "Tenant B",
"public_keys": {
"kid-1": "-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----"
}
}
}
```

Each key in the JSON must match the `iss` claim the tenant will use in their JWTs.

> ⚠️ **Never commit `tenants.json` or `.pem` files** — they are already in `.gitignore`.

### Generating RSA Keys (Tenant-Side)

Each tenant generates their own key pair and sends you **only the public key**:
A tenant generates its own key pair and sends you the public half:

```bash
# Generate a 2048-bit RSA private key (tenant keeps this secret)
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048

# Extract the public key (send this to the server operator)
openssl rsa -pubout -in private_key.pem -out public_key.pem
uv run orcha tenants add tenant-a ./their_public_key.pem
uv run orcha tenants list
```

Pass `--kid` to register a second key alongside the first, and `--force` to
replace one. `orcha tenants token tenant-a ./private_key.pem` signs a token from
the tenant side, with `--kid` to pick the key, `--workflow-id` to scope it to
one workflow and `--expires-in` to set its lifetime in seconds.

> ⚠️ **Never commit `tenants.json` or `.pem` files** — they are already in `.gitignore`.

### Configuration

| Variable | Description | Required |
| --------------------- | ---------------------------------------- | ----------- |
| `JWT_ALGORITHM` | Signing algorithm (default: RS256) | No |
| `AUTH_DISABLED` | Set to `true` to skip auth | Development |
| `DEV_MODE` | Run as the `dev` tenant with auth off | Development |
| `AUTH_DISABLED` | Override the auth switch either way | Development |
| `TENANTS_CONFIG_PATH` | Path to tenants JSON (default: tenants.json) | Production |

**Local development** — bypass authentication entirely:

```bash
export AUTH_DISABLED=true
```

### Creating a Test Token (Tenant-Side)

Tokens **must** include the `iss` claim matching the tenant ID. Optionally include `workflow_id` to scope access.

```python
import jwt
from datetime import datetime, timedelta, timezone

private_key = open("private_key.pem").read()

token = jwt.encode(
{
"iss": "tenant-a", # Required: must match tenants.json key
"workflow_id": "YOUR_WORKFLOW_ID", # Optional: scope to a specific workflow
"exp": datetime.now(timezone.utc) + timedelta(hours=1)
},
private_key,
algorithm="RS256",
headers={"kid": "kid-1"} # Required: must match kid in tenants.json public_keys
)
print(token)
```

Use the token:

```bash
curl -H "Authorization: Bearer <token>" http://localhost:8000/workflows/<YOUR_WORKFLOW_ID>
```

### LLM Configuration

The service supports multiple LLM backends. Configure a single setting, `LLM`,
Expand Down Expand Up @@ -161,10 +147,15 @@ export OLLAMA_BASE_URL="http://localhost:11434/v1"
| `orcha services start` | Start PostgreSQL + Temporal via Docker |
| `orcha services stop` | Stop all Docker services |
| `orcha migrate` | Apply all database migrations |
| `orcha run` | Start server and default-queue worker |
| `orcha run server` | Start FastAPI dev server only |
| `orcha run` | Migrate, then start Temporal, API, and worker (SQLite) |
| `orcha run --reset` | Same, after deleting `orcha.db` and `temporal.db` |
| `orcha run server` | Start the FastAPI server only |
| `orcha run server --dev` | Start the FastAPI server with hot reload |
| `orcha run workers` | Start Temporal worker for default queue |
| `orcha run workers --task-queue Q` | Start Temporal worker for a specific queue |
| `orcha tenants list` | List registered tenants and their key IDs |
| `orcha tenants add T KEY.pem` | Register a tenant's public key |
| `orcha tenants token T KEY.pem` | Sign a token for a tenant |

## Database Migrations

Expand Down
Loading
Loading