CONTRIBUTING.md
Rama main · réplica de lectura del repositorio original.
# Contributing to AstraBox
AstraBox is a self-hosted platform for running installed Agent programs in
isolated sandboxes. It provides a web console, APIs, saved Session history,
live output, and approval controls for teams. Thanks for helping improve it.
## Development setup
You need **Python 3.12+**, a working **Docker** daemon (required for the dev stack
and the end-to-end suite), and a PostgreSQL server for the unit tests that use the
store. The repository manages its exact Node.js version from `.nvmrc`; an existing
matching NVM install is reused, otherwise `make` downloads the checksum-pinned
official archive into `.astrabox/toolchains/node`.
Create a virtualenv and install the package editable with the dev tools:
```bash
python -m venv .venv
.venv/bin/pip install -U pip
.venv/bin/pip install -e '.[dev,sandbox-server]'
```
The `[dev]` extra pulls in `pytest`, its async/timeout/xdist support, `ruff`, and
`mypy`; it is enough for the Python gate (`make test-py`), lint, and typecheck.
`[sandbox-server]` adds the bundled
OpenSandbox service used by the default dev and container launch paths. A
deployment configured with an external lifecycle service connects to it instead.
If a required dependency is missing, startup exits with an explicit error.
Use the Makefile target to install
everything required by the complete `make test` gate, including the frontend,
channel gateway, API code generator, and both Playwright projects:
```bash
make install
```
### Run the local dev stack
`scripts/dev.sh` (also `make dev`) boots the two processes you iterate against. It
needs the server image (`make build-server-image` once):
- **Backend** — uvicorn (`astrabox.api.app:create_app`) with `--reload` in the server
container of the maintained Compose stack, published at **:8000**.
`containers/compose.source.yaml` mounts this checkout over the installed package,
and `astrabox.deploy.onebox` starts the bundled OpenSandbox lifecycle server beside
it with access to `/var/run/docker.sock`.
- **Frontend** — the Vite dev server (`frontend/`) at **:5173**, whose `/api` proxies
to the backend on `:8000`.
```bash
make dev # == scripts/dev.sh (Ctrl-C stops both)
```
Then open <http://localhost:5173>. The backend boots without credentials — the UI and
session list work — but a real Agent response additionally needs the agent image
(`make build-agent-image`) and your LLM key. The dev stack and the e2e gate both read
your Anthropic-compatible endpoint and token from a gitignored repo-root `.env`
(`ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN` **or** `ANTHROPIC_API_KEY`, optional
`ANTHROPIC_MODEL`). [`e2e/README.md`](e2e/README.md) covers the browser
happy-path run end to end.
You can run the backend on its own with `ASTRABOX_DEV_BACKEND_ONLY=1 scripts/dev.sh`,
or change the backend port with `ASTRABOX_DEV_BACKEND_PORT=9000 scripts/dev.sh`.
## Which loop your change needs
Use local static checks while editing, then choose verification by the behavior
changed. A live check needs a deployment you control: Docker, the agent image,
and a model key.
| Changed | Check with |
|---|---|
| Documentation or comments | Comment and link checks; for website changes, bilingual build and website browser checks |
| Frontend or backend behavior | Static checks, then the affected real E2E; the repository gate for a coherent release candidate |
| Backend code on an isolated development deployment | `make run-mounted` mounts this tree over a compatible image's package |
| Sandbox control service, Agent image, or deployment configuration | Build the affected images and verify the changed path against a live deployment |
Dependency or bundled frontend changes also require a new server image
(`make build-server-image`), because `make run-mounted` swaps code, not the
environment: it mounts this tree over the package the image installed.
One thing is deliberate, and costs an afternoon when it surprises you: never push a
changed `:latest` to a shared cluster. A Kubernetes node pulls `IfNotPresent`, so a
new `:latest` leaves it serving the OLD image while your source says otherwise.
`make push-images REGISTRY=<host:port>` tags by tree state precisely so a node
cannot do that, and prints the `ASTRABOX_AGENT_IMAGE` line that points a deployment
at what you pushed.
## Running tests
### Unit gate (PostgreSQL, no secret)
Use a Git checkout at the candidate commit, including its Git metadata:
`make check-comments` reads its file list from `git ls-files`, so an extracted
deployment tarball alone cannot run the complete gate.
This is what CI runs on every push and PR. A plain `pytest` run deselects the live
suite by default (`pyproject.toml` sets `addopts = -m 'not e2e'`), so it never touches
Docker or an LLM key. The unit tests are the database races and security boundaries an
end-to-end run cannot show. Tests that use the store need a PostgreSQL server: set
`ASTRABOX_TEST_POSTGRES_URL` to a `postgresql://` URL for a role that may create
databases. Each pytest process creates its own database with every table and drops it
at the end (`tests/_database.py`):
```bash
ASTRABOX_TEST_POSTGRES_URL=postgresql://user:password@127.0.0.1:5432/postgres \
make test-py # unit suite only (the `e2e` mark is deselected)
```
The repository gate combines the local targets below. CI runs the corresponding
checks in separate jobs in [`.github/workflows/ci.yml`](.github/workflows/ci.yml):
```bash
make test # complete repository gate
```
The target includes repository consistency checks, Python lint/typecheck/tests,
the generated API client check, channel-gateway tests, both Playwright typechecks,
and the console typecheck, production build, policy checks, and unit tests. The
Makefile is the current list. During diagnosis, retain the first failure and
prove a fix with the smallest relevant check before rerunning its E2E. Continue
with tests that lack evidence; do not restart an unaffected suite after each
edit. Run the full gate once for a coherent release candidate. Never remove or
weaken an assertion to make a failing change pass.
### End-to-end gate (needs Docker + an LLM key)
The live suite is marked `e2e` and deselected by default. It needs a running
deployment and the appropriate model and credential fixtures. Run only the
selected cases for the behavior being changed; a model reply alone does not
prove file persistence, native resume, child tasks, or approvals.
```bash
ASTRABOX_E2E_BASE_URL=http://127.0.0.1:8088 make e2e-live
```
The development happy-path target builds the images and runs a curl smoke plus
the console browser path. It is not the complete multi-engine campaign:
```bash
make build-agent-image # bake astrabox/sandbox-claude-code:latest (pins + smoke-tests the claude CLI)
make e2e
```
[`tests/e2e/README.md`](tests/e2e/README.md) describes the Python lane's two
phases and how to select exact nodes; [`tests/e2e-ui/README.md`](tests/e2e-ui/README.md)
does the same for the browser suite and lists the deployment fixtures each
group of specs reads. Core lifecycle and native-state recovery must work
without a persistent workspace volume; file retention is tested separately with
persistent storage.
### Frontend build and tests
The console SPA is TypeScript. Run its typecheck and real production build through
the repository's pinned Node.js toolchain:
```bash
make build-web
```
`make test-web` adds the channel gateway, console policy and unit tests, and both
Playwright suite typechecks.
## Repository map
Where to look when you want to change something:
| Path | What lives there |
|---|---|
| `astrabox/seams/` | Versioned plugin interfaces for sandbox, storage, model endpoint, secret store, scheduler, runtime-preparation, messaging, and identity providers. Third-party plugins implement these interfaces and register through the `astrabox.providers.*` Python entry-point groups. Preserve backward compatibility when extending them. |
| `astrabox/providers/` | The built-in providers: `open_sandbox/` (the sandbox backend — creates containers through the OpenSandbox lifecycle API; conversation tenancy isolates each Session, while agent tenancy can share one sandbox), `model.py` (the `litellm` model endpoint provider; no bundled direct passthrough), `secret_store.py` (AES-GCM local vault store), `identity.py` (no-auth defaults) + `identity_sso.py` (`trusted_header`/`jwt` resolvers). |
| `astrabox/deploy/` | Deployment startup: `onebox.py` starts AstraBox and supervises the bundled services selected by configuration; `sandbox_server.py` renders the bundled lifecycle server's config from `ASTRABOX_*`. |
| `astrabox/api/` | The HTTP API: `routes/` contains resource routers for Sessions (with their inputs, answers and output stream), Agents, Assistants, vaults, admin operations, MCP, and related resources; `app.py` contains the FastAPI factory, lifespan, and SPA serving. Routes stay thin — logic belongs in services. |
| `astrabox/web/` | ASGI middleware for browser/API authentication and trusted-host validation. |
| `astrabox/core/service/orchestrator/` | The platform core: `session_runtime/` (the one reader per Session that records what the Agent runtime reports, the output feed, and sending, answering and interrupting), `session_lifecycle/` (Session creation, startup and recovery), `runtime_manager.py` + `runtime/` (sandbox lifecycle and common workspace routing), `engine/` (native Agent-program adapters and their runtime clients), and deployment/assistant services. |
| `astrabox/persistence/` | Data access code: `sql/` holds the PostgreSQL engine, the table declarations (`sql/models/`) and the Alembic schema upgrade that runs at startup (`sql/migrate.py`); `repository/` holds one repository class per domain, each reading and writing its own tables. |
| `astrabox/config/` | Typed settings + the env-var registry (`env_registry.py`) — every env var the code reads must be registered; `scripts/check_env_registry.py` enforces it. |
| `astrabox/testing/` | Reusable compatibility tests for engine, runtime-client, sandbox, storage, channel, and scheduler plugins, plus E2E fault-injection helpers. Plugin authors run these tests against their implementations. Production modules do not import this package. |
| `astrabox/common/` | Logger + small shared utils. |
| `frontend/` | The React console SPA (Vite; `npm run build` runs `tsc --noEmit` first). |
| `containers/` | The server, sandbox and LiteLLM images + compose. |
| `tests/` | Unit tests for database races and security boundaries (file naming: `*_test.py`). `tests/e2e/` is the live lane (`-m e2e`). |
| `e2e/` | The Playwright browser suite against the real console. |
| `tests/e2e-contract/` | What every live suite shares: the frozen lane inventory, the per-engine profiles the specs parametrize on, and the one 180-second test budget each Playwright config reads. |
## Coding conventions
- **Python**: target 3.12, `ruff` for lint, `mypy` for typechecking, line length 100.
Configuration lives in [`pyproject.toml`](pyproject.toml) (`[tool.ruff]`,
`[tool.mypy]`). Run `make lint` and `make typecheck` before opening a PR — both must
stay green. Ruff grandfathers a handful of rule codes tree-wide, and mypy runs
with a documented, grandfathered list of disabled error codes — both with
rationale in `pyproject.toml`. The remaining enabled rules stay enforced,
including in new code, so do not introduce new violations.
- **Errors**: use one explicit supported path. Do not silently switch providers,
weaken security, or discard data when a required operation fails. Return an
error with enough context to diagnose it.
- **Comments**: follow the rules in [Comments](#comments) below.
`make check-comments` runs the mechanical half of them — over every tracked
and untracked source file (`git ls-files`), which includes `.ts`, `.tsx`,
`.css`, `.sh` and `.yaml`, not only Python. A TypeScript-only or test-only
change is inside its scope; "this change is not Python" is not a reason to
skip it, and a violation left behind stays invisible until some unrelated
change runs the gate.
- **TypeScript**: run `make build-web` for console changes and `make test-web` for
the complete web gate; both use the Node.js version pinned by the repository.
- **Tests**: name Python test files `*_test.py`. Test observable product behavior
through real E2E, including native supplier evidence and user-visible results.
Keep a unit test only where an end-to-end run cannot show the property: a
database race (uniqueness, a compare-and-set, a lease or fence under
concurrency) or a security or authorization boundary. Repository consistency
rules belong in the `scripts/check_*.py` gates, not in tests. When moving
an implementation-shaped contract to E2E, preserve its intent, obtain passing
evidence, then remove the replaced contract. Reusable provider checks belong
in `astrabox/testing/`, not copies per backend.
- Keep changes focused; match the style of the surrounding code.
## Comments
Write comments for contributors who know only this repository. A useful comment
explains behaviour they cannot infer from the code and points to files, symbols
or public sources they can inspect. `make check-comments` enforces the
mechanical half against a strict-zero baseline
(`scripts/comment_style_baseline.json`); `make check-comments-report` prints
every current finding grouped by class.
1. **Docstrings carry API behaviour.** Module, class and function docstrings
reach API documentation, editor tooltips and `help()`. Put what callers rely
on there — behaviour, prerequisites, expected errors, limits. An inline
comment is for a local reason: an ordering requirement, a guard whose purpose
its name does not carry, a value taken from a measurement.
2. **Describe the code, not how it got this way.** State the current
requirement and its reason; change history belongs in the commit. Banned in
comments: `used to`, `no longer`, `previously`, `originally`, `anymore`,
`the regression`, `BUG (fixed)`, `moved verbatim`. When a guard prevents a
known failure, describe the unguarded operation and the guard — not the
guarded code as though it were broken.
3. **Every name a comment uses must resolve for the reader.** A code symbol, a
repository path or a public URL. Never a work-plan or phase code (`F3-5`,
`Stage T4`), an issue number in a tracker they cannot open, or a component
that is not in this repository.
4. **State the failure, not where it was seen.** Keep the failure and its
mechanism; do not cite private infrastructure a reader cannot inspect.
5. **The subject is something in the code.** Name the component or caller that
acts. First person hides it — except inside a quoted value or user message.
6. **Explain why, never restate what.** If the code needs a comment to be
readable, rename something or split the function instead.
7. **Neutral register.** No editorialising (`obviously`), no exasperation
(`unfortunately`), no shouting. Emphasis is for a severe, silent failure —
an all-caps `MUST NOT` on a security or data-loss constraint earns its keep.
8. **Section banners are noun phrases**, labelling a region so the file can be
scanned; they are not headlines.
9. **No TODO, FIXME, HACK or commented-out code.** Track unfinished work in an
issue with an owner and acceptance criteria. This tree has zero of these.
10. **Test comments explain the scenario's intent** — what situation is being
built, and what would be wrong without the assertion.
## Pull request flow
1. Branch off `main`.
2. Make your change with focused commits.
3. Record what you ran: the static checks, and the live cases that cover the
behaviour you changed. Run the complete repository gate when the batch is
ready for review.
4. Open a pull request against `main`. CI ([`.github/workflows/ci.yml`](.github/workflows/ci.yml))
runs the unit/lint/typecheck gate on your branch and **must pass** before merge. The
live e2e workflow ([`.github/workflows/e2e.yml`](.github/workflows/e2e.yml)) is
manually dispatched and gated on an LLM-key repo secret.
5. Describe what changed and why; link any related issue.
## License of contributions
AstraBox is licensed under the **Apache License 2.0** (see
[`LICENSE`](LICENSE)). By submitting a contribution you agree that it is licensed under
the same terms and that you have the right to contribute it.