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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
43 changes: 41 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,40 @@ jobs:
shell: bash
working-directory: backend/cli

# Windows Job Objects are the runtime ownership boundary for terminals,
# commands, compute, kernels, LSPs, and local MCP servers. The source-level
# structure contracts run everywhere; this leg exercises the real Kernel32
# handles, descendant inheritance, named-job reopen, and verified teardown.
windows-runtime:
name: Windows runtime ownership
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/setup-bun
- run: bun test test/process/windows-job.test.ts test/global/data-root.test.ts
shell: bash
working-directory: backend/cli

# macOS responsibility IDs are the kernel-backed ownership boundary for
# processes that setsid/double-fork away from their original PID and process
# group. Exercise the native private ABI and both durable ledgers on an
# actual macOS runner; source-contract tests on Linux cannot prove teardown.
macos-runtime:
name: macOS runtime ownership
runs-on: macos-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/setup-bun
- run: >-
bun test
test/process/darwin-responsibility.test.ts
test/credentials/process-ledger.test.ts
test/project/authority-process-ledger.test.ts
shell: bash
working-directory: backend/cli

test:
name: Test
runs-on: ubuntu-latest
Expand All @@ -66,10 +100,10 @@ jobs:
git config --global user.email "ci@openscience.dev"
git config --global user.name "OpenScience CI"
git config --global init.defaultBranch main
- name: Install and verify Linux sandbox
- name: Install and verify Linux sandbox and SSH fixture
run: |
sudo apt-get update
sudo apt-get install --yes bubblewrap
sudo apt-get install --yes bubblewrap openssh-server
# Ubuntu 24.04's host-wide AppArmor policy blocks unprivileged user
# namespaces on the hosted runner before bubblewrap can apply our
# stricter per-process profile. This runner is disposable; enable
Expand All @@ -78,6 +112,11 @@ jobs:
echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
fi
bwrap --ro-bind / / --dev /dev --proc /proc --unshare-pid --die-with-parent -- true
sudo install -d -m 0755 /run/sshd
test -x /usr/sbin/sshd
- name: Exercise real OpenSSH dispatch and recovery
run: bun test test/compute/ssh-integration.test.ts
working-directory: backend/cli
- name: Build embedded web assets for server tests
run: |
bun run --cwd frontend/workspace build
Expand Down
147 changes: 7 additions & 140 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ jobs:
- name: linux
host: ubuntu-latest
playwright: bunx playwright install --with-deps
workdir: .
# Only the browser suite. The backend unit suite already runs in
# ci.yml on every push/PR; re-running it here fails (this job never
# configures a git identity) and reads the seeded XDG dirs as its
# global config.
command: |
bun turbo test --filter=@synsci/workspace
workdir: frontend/workspace
# Use the same hermetic runner developers use locally. It owns a
# disposable data root, fake model, backend, Vite server, ports,
# and teardown. Prestarting any of those here creates two distinct
# test systems and makes failures depend on which one the browser
# reaches.
command: bun run test:e2e
# Windows e2e:local skipped — chronically failing since
# 2026-04-28 across all PRs from all authors. Root cause is
# the in-process Playwright/bun/Vite spawn chain not
Expand Down Expand Up @@ -66,144 +66,13 @@ jobs:
working-directory: frontend/workspace
run: ${{ matrix.settings.playwright }}

- name: Pin openscience server Basic-Auth password
if: matrix.settings.name != 'windows'
run: |
# Pin OPENSCIENCE_SERVER_PASSWORD to a known value before booting
# the in-process server, and broadcast the same value to
# VITE_OPENSCIENCE_SERVER_PASSWORD so Playwright (via the
# extraHTTPHeaders branch in playwright.config.ts) attaches a
# matching Basic-Auth header on every browser request.
# Without this, the auto-generated password is unknown to
# Playwright and all browser-driven /session, /event, /provider
# calls get 401 → home.spec "Open project" never appears.
PASS="ci-$(openssl rand -hex 16)"
{
echo "OPENSCIENCE_SERVER_PASSWORD=$PASS"
echo "VITE_OPENSCIENCE_SERVER_PASSWORD=$PASS"
} >> "$GITHUB_ENV"
# Vite reads VITE_* from .env.local at build time — the Playwright
# webServer env-pass doesn't reach the bundle. Mirror e2e-local.ts
# by writing the same .env.local frontend/workspace expects.
cat > frontend/workspace/.env.local <<ENV
VITE_OPENSCIENCE_SERVER_HOST=127.0.0.1
VITE_OPENSCIENCE_SERVER_PORT=4096
VITE_OPENSCIENCE_SERVER_USERNAME=openscience
VITE_OPENSCIENCE_SERVER_PASSWORD=$PASS
ENV

- name: Set OS-specific paths
run: |
# shellcheck disable=SC2129
if [ "${{ runner.os }}" = "Windows" ]; then
printf '%s\n' "OPENSCIENCE_E2E_ROOT=${{ runner.temp }}\\openscience-e2e" >> "$GITHUB_ENV"
printf '%s\n' "OPENSCIENCE_TEST_HOME=${{ runner.temp }}\\openscience-e2e\\home" >> "$GITHUB_ENV"
printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}\\openscience-e2e\\share" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}\\openscience-e2e\\cache" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}\\openscience-e2e\\config" >> "$GITHUB_ENV"
printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}\\openscience-e2e\\state" >> "$GITHUB_ENV"
else
printf '%s\n' "OPENSCIENCE_E2E_ROOT=${{ runner.temp }}/openscience-e2e" >> "$GITHUB_ENV"
printf '%s\n' "OPENSCIENCE_TEST_HOME=${{ runner.temp }}/openscience-e2e/home" >> "$GITHUB_ENV"
printf '%s\n' "XDG_DATA_HOME=${{ runner.temp }}/openscience-e2e/share" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CACHE_HOME=${{ runner.temp }}/openscience-e2e/cache" >> "$GITHUB_ENV"
printf '%s\n' "XDG_CONFIG_HOME=${{ runner.temp }}/openscience-e2e/config" >> "$GITHUB_ENV"
printf '%s\n' "XDG_STATE_HOME=${{ runner.temp }}/openscience-e2e/state" >> "$GITHUB_ENV"
fi

- name: Start deterministic E2E model
if: matrix.settings.name != 'windows'
run: |
CONFIG=$(bun frontend/workspace/script/e2e-fake-model.ts --port 4097 --print-config)
{
printf '%s\n' "OPENSCIENCE_CONFIG_CONTENT=$CONFIG"
printf '%s\n' "OPENSCIENCE_E2E_MODEL=e2e/echo"
printf '%s\n' "OPENSCIENCE_E2E_FAKE_MODEL=1"
} >> "$GITHUB_ENV"
bun frontend/workspace/script/e2e-fake-model.ts --port 4097 > "$RUNNER_TEMP/openscience-fake-model.log" 2>&1 &
for _ in $(seq 1 30); do
curl -fsS "http://127.0.0.1:4097/health" > /dev/null && exit 0
sleep 1
done
echo "::error::deterministic E2E model never became healthy on 127.0.0.1:4097"
exit 1

- name: Seed openscience data
if: matrix.settings.name != 'windows'
working-directory: backend/cli
run: bun script/seed-e2e.ts
env:
OPENSCIENCE_DISABLE_SHARE: "true"
OPENSCIENCE_DISABLE_LSP_DOWNLOAD: "true"
OPENSCIENCE_DISABLE_DEFAULT_PLUGINS: "true"
OPENSCIENCE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true"
OPENSCIENCE_TEST_HOME: ${{ env.OPENSCIENCE_TEST_HOME }}
XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }}
XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }}
XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }}
XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }}
OPENSCIENCE_E2E_PROJECT_DIR: ${{ github.workspace }}
OPENSCIENCE_E2E_SESSION_TITLE: "E2E Session"
OPENSCIENCE_E2E_MESSAGE: "Seeded for UI e2e"
OPENSCIENCE_E2E_MODEL: "e2e/echo"

- name: Run openscience server
if: matrix.settings.name != 'windows'
working-directory: backend/cli
# `serve` is loopback-only and rejects --hostname (yargs exits 1 with
# help text), so don't pass one. Keep the log on disk: a backgrounded
# process loses its output once this step ends, which made boot
# failures undiagnosable.
run: bun dev -- --print-logs --log-level WARN serve --port 4096 > "$RUNNER_TEMP/openscience-server.log" 2>&1 &
env:
OPENSCIENCE_DISABLE_SHARE: "true"
OPENSCIENCE_DISABLE_LSP_DOWNLOAD: "true"
OPENSCIENCE_DISABLE_DEFAULT_PLUGINS: "true"
OPENSCIENCE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true"
OPENSCIENCE_TEST_HOME: ${{ env.OPENSCIENCE_TEST_HOME }}
XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }}
XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }}
XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }}
XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }}
OPENSCIENCE_CLIENT: "app"

- name: Wait for openscience server
if: matrix.settings.name != 'windows'
run: |
for _ in $(seq 1 120); do
curl -fsS "http://127.0.0.1:4096/global/health" > /dev/null && exit 0
sleep 1
done
echo "::error::openscience server never became healthy on 127.0.0.1:4096"
exit 1

- name: run
working-directory: ${{ matrix.settings.workdir }}
run: ${{ matrix.settings.command }}
env:
CI: true
OPENSCIENCE_DISABLE_SHARE: "true"
OPENSCIENCE_DISABLE_LSP_DOWNLOAD: "true"
OPENSCIENCE_DISABLE_DEFAULT_PLUGINS: "true"
OPENSCIENCE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true"
OPENSCIENCE_TEST_HOME: ${{ env.OPENSCIENCE_TEST_HOME }}
XDG_DATA_HOME: ${{ env.XDG_DATA_HOME }}
XDG_CACHE_HOME: ${{ env.XDG_CACHE_HOME }}
XDG_CONFIG_HOME: ${{ env.XDG_CONFIG_HOME }}
XDG_STATE_HOME: ${{ env.XDG_STATE_HOME }}
PLAYWRIGHT_SERVER_HOST: "127.0.0.1"
PLAYWRIGHT_SERVER_PORT: "4096"
VITE_OPENSCIENCE_SERVER_HOST: "127.0.0.1"
VITE_OPENSCIENCE_SERVER_PORT: "4096"
OPENSCIENCE_CLIENT: "app"
timeout-minutes: 30

- name: Print server log
if: failure() && matrix.settings.name != 'windows'
run: |
cat "$RUNNER_TEMP/openscience-server.log" || true
cat "$RUNNER_TEMP/openscience-fake-model.log" || true

- name: Upload Playwright artifacts
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand All @@ -214,5 +83,3 @@ jobs:
path: |
frontend/workspace/e2e/test-results
frontend/workspace/e2e/playwright-report
${{ runner.temp }}/openscience-server.log
${{ runner.temp }}/openscience-fake-model.log
17 changes: 16 additions & 1 deletion .github/workflows/npm-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,23 @@ permissions:
id-token: write

jobs:
test-source:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- name: Require the protected default branch
shell: bash
run: |
set -euo pipefail
if [[ "$GITHUB_REPOSITORY" != "synthetic-sciences/OpenScience" || "$GITHUB_REF" != "refs/heads/main" ]]; then
echo "::error::npm test publishing uses registry credentials and must be dispatched from synthetic-sciences/OpenScience main; received $GITHUB_REPOSITORY at $GITHUB_REF."
exit 1
fi

version:
if: github.repository == 'synthetic-sciences/OpenScience'
needs: test-source
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
Expand Down
20 changes: 19 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,30 @@ concurrency: ${{ github.workflow }}
permissions:
id-token: write
contents: write
# publish.ts falls back to a release PR when branch protection rejects the
# automated version-bump commit on main.
pull-requests: write

jobs:
release-source:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- name: Require the protected default branch
shell: bash
run: |
set -euo pipefail
if [[ "$GITHUB_REPOSITORY" != "synthetic-sciences/OpenScience" || "$GITHUB_REF" != "refs/heads/main" ]]; then
echo "::error::Production releases must be dispatched from synthetic-sciences/OpenScience main; received $GITHUB_REPOSITORY at $GITHUB_REF."
exit 1
fi

version:
needs: release-source
runs-on: ubuntu-latest
timeout-minutes: 15
if: github.repository == 'synthetic-sciences/OpenScience'
steps:
- name: Require a bump or version input
if: ${{ !inputs.bump && !inputs.version }}
Expand Down
34 changes: 0 additions & 34 deletions .openscience/agent/docs.md

This file was deleted.

6 changes: 3 additions & 3 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ The backend is a Bun and TypeScript application compiled to a single native bina

- `src/index.ts` registers the CLI commands and boots the process. Running `openscience` with no subcommand opens the workspace (`src/cli/cmd/web.ts`).
- `src/server` is a Hono server. It serves the embedded workspace UI, exposes the session and tool APIs, and streams events back to the browser over SSE.
- `src/session` is the agent runtime: the message loop, tool dispatch, compaction, provenance, and an optional blind reviewer gate that runs at finalize.
- `src/agent` holds the agent registry and prompts. The default agent is `research`; `biology`, `physics`, and `ml` are specialists; `plan` is a read-only mode.
- `src/session` is the agent runtime: the message loop, tool dispatch, compaction, provenance, durable runtime events, and explicit read-only review passes for sessions or immutable artifact versions.
- `src/agent` holds the agent registry and prompts. `research` is the single user-facing agent; it loads domain knowledge through skills and may delegate bounded Explore, Execute, or Review work internally. Domain and legacy helper profiles remain hidden compatibility aliases; `plan` is a read-only mode.
- `src/provider` routes each request to a model. Model definitions come from [models.dev](https://models.dev), cached locally with a bundled snapshot as a fallback.
- `src/tool` and `src/science` implement the tools the agent can call, including the shell, editor, LSP bridge, MCP client, and the scientific database connectors.
- `src/openscience` is the Atlas client. It is optional; the base install and every bring-your-own-key flow work without it.
Expand All @@ -72,7 +72,7 @@ Skills are instruction bundles the agent loads on demand (`src/skill`). The cano

## Configuration and state

Global config lives in `~/.config/openscience/openscience.json`; project config in `openscience.json` or a `.openscience/` directory at the repo root. On-disk state (sessions, auth, caches) lives under the XDG data, config, cache, and state directories, resolved in `src/global/index.ts`. Installs made before the OpenScience rename migrate automatically from the legacy `synsc` directories on first run.
Global config lives in `~/.config/openscience/openscience.json`; project config in `openscience.json` or a `.openscience/` directory at the repo root. Persistent application data (sessions, auth, credentials, binaries, and logs) defaults to the stable `~/.openscience` data root and can be relocated; config, cache, and state use their resolved XDG directories. `src/global/index.ts` owns those paths. Installs made before the OpenScience rename import or migrate the legacy `synsc` directories on first run.

## Atlas integration

Expand Down
Loading
Loading