This document defines the first full end-to-end integration profile for rove and the optional gates that extend it to real providers and MCP. The goal is to prove the product's own runtime loop before adding external-service variability.
| Profile | Required by first round | External dependencies | Purpose |
|---|---|---|---|
local-full |
Yes | No | Proves fake provider, real API, built-in tools, persistent state, the default / product shell, and one bounded advanced /dev/workbench smoke. |
provider-smoke |
No | Provider key, network, or local Ollama | Proves a configured real provider can answer and perform one native tool-use round trip. |
provider-integration |
No | Provider key/network, except local Ollama | Proves a real OpenAI, OpenAI Responses, Anthropic, or Ollama provider through inventory, provider smoke, API jobs, an optional exact product-shell job, and saved evidence. The updated external-provider browser gate has not been run for C3. |
external-tools |
No | MCP server configuration | Proves configured external tools can be discovered, called, and shown in API/Web records. |
stress |
No | Depends on selected profile | Later profile for concurrent runs, long-running jobs, repeated resume, and restart recovery. |
The first integration baseline is local-full. Real model providers, MCP servers, and stress tests are gated follow-ups, not first-round blockers.
Use these templates as the starting point:
.rove/config.example.toml.rove/mcp_servers.example.json.env.integration.example
Copy them to local-only files before running a manual integration pass:
Copy-Item .rove/config.example.toml .rove/config.toml
Copy-Item .rove/mcp_servers.example.json .rove/mcp_servers.json
Copy-Item .env.integration.example .env.integrationDo not commit .rove/config.toml, .rove/mcp_servers.json, .env.integration, real API keys, SQLite state, logs, screenshots, traces, or generated run artifacts.
The local-full profile must not write into the normal .rove runtime state. By default, the runner uses %TEMP%/rove-integration on Windows. Prefer an absolute integration root outside the repository; if the workspace lives under this Git repo, workspace detection can walk upward to the repo root.
| Artifact | Path |
|---|---|
| Runtime state | <integration-root>/workspace/.rove-integration-state |
| SQLite index | <integration-root>/workspace/.rove-integration-state/state.sqlite |
| Memory | <integration-root>/workspace/.rove-integration-state/memory |
| Test workspace | <integration-root>/workspace |
| Logs, screenshots, traces | <integration-root>/artifacts |
The runner removes or recreates <integration-root>/workspace at the start of a clean run, then preserves <integration-root>/artifacts for debugging.
local-full runs against:
- provider
fake - model
fakeorfake-rawdepending on the scenario - real
rove-api - real
apps/web - built-in tools from the runtime registry
- local filesystem workspace rooted under
<integration-root>/workspace
It must prove this flow:
Web/API request
-> API job creation
-> Engine run
-> fake provider event stream
-> built-in tool call or pending interaction
-> approval/input resolution
-> run completion or expected failure
-> trace/report/task state persisted
-> exact product-session transcript/report binding is verified
-> bounded /dev/workbench direct-run smoke remains available
| Scenario | Request shape | Expected evidence |
|---|---|---|
| Plain run | {"message":"local-full plain run","model":"fake","approval":"auto"} |
Job completes and /runs includes the exact done run. |
| Tool run | {"message":"{\"tool\":\"echo\",\"args\":{\"message\":\"hello local-full\"}}","model":"fake-raw","approval":"auto"} |
Tool lifecycle events appear, final text includes the echo result, run detail/report records the tool step. |
| Approval approved | {"message":"{\"tool\":\"write_file\",\"args\":{\"path\":\"approved.txt\",\"content\":\"ok\"}}","model":"fake-raw","approval":"ask","max_steps":1} |
Job exposes one pending write_file approval, approve resumes the run, file is written inside the integration workspace, /runs reports done. |
| Approval rejected | Same as approval approved with rejected.txt, then reject |
Job records the rejected tool decision, no file is written, terminal state is visible and explainable in API/Web. |
| Input resume | {"message":"{\"tool\":\"request_input\",\"args\":{\"prompt\":\"Which branch should I use?\"}}","model":"fake-raw","approval":"auto","max_steps":1} |
Job exposes pending input, answer submission resumes the run, pending input clears, final output includes or reflects the supplied answer. |
| Failure record | A request that triggers a known tool failure, such as an invalid filesystem path inside the isolated workspace policy | API state and Web detail show the failed tool result or failed run status with diagnostic text. |
| History consistency | After all scenarios | /runs, /runs/{run_id}/report, product transcript bindings, and Web assertions agree on exact run ids, statuses, and important tool/input/approval records. |
| Product migration | Seed safe M1 browser state, then open a legacy deep route | Migration completes before product catalog reads, imports no raw key, remaps the route, and does not replay after refresh. |
| Exact product continuity | Interleave turns in product sessions A and B, refresh A, then continue A | A resumes its own exact prior run rather than B's workspace-global latest; transcript, approval, input, cancellation, Settings, and deep routes remain usable. |
| Project Trust | Open an unknown workspace, inspect it, then grant selected capabilities, deny, and revoke through Settings | No workspace-owned process starts before MCP/process trust; requests contain only the workspace ID; digest changes invalidate only the affected grant; revocation cancels matching work and blocks new activation. |
| Execution Environment | Run file/search/Shell and deterministic stdio MCP through local adapters, then run adapter conformance tests | Tool names, schemas, approval, output, timeout, and cancellation remain compatible; traversal/symlink escape, missing capabilities, output limits, and child cleanup fail closed; runtime info contains only a redacted digest. |
| Product Fork | Complete a parent ProductSession turn, click Fork in the live shell, continue the child, refresh, then switch back to the parent | API returns one idempotent child with fresh runtime identities; child transcript has an inherited read-only parent segment followed by its local segment; branch tree and parent/child isolation survive refresh. |
These commands are useful before a runner exists. Start the API in one terminal:
$env:ROVE_PROVIDER = "fake"
$env:ROVE_MODEL = "fake"
$root = Join-Path $env:TEMP "rove-integration"
$workspace = Join-Path $root "workspace"
$state = Join-Path $workspace ".rove-integration-state"
$env:ROVE_STATE_DIR = $state
$env:ROVE_STATE_SQLITE = Join-Path $state "state.sqlite"
$env:ROVE_MEMORY_SESSION_DIR = Join-Path $state "memory/sessions"
$env:ROVE_MEMORY_DURABLE_DIR = Join-Path $state "memory"
cargo run -p rove-api -- --addr 127.0.0.1:8787 -C $workspaceCreate a plain job:
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8787/jobs `
-ContentType application/json `
-Body '{"message":"local-full plain run","model":"fake","approval":"auto"}'List historical runs:
Invoke-RestMethod http://127.0.0.1:8787/runsFetch the generated OpenAPI document:
Invoke-RestMethod http://127.0.0.1:8787/api/openapi.jsonOpen Swagger UI in a browser:
http://127.0.0.1:8787/swagger-ui
If api.token_auth is configured, use Swagger UI's authorize control or pass
Authorization: Bearer <token> for business API calls.
For pending approval and input scenarios, poll job state:
Invoke-RestMethod http://127.0.0.1:8787/jobs/<job_id>/stateThen submit a decision or answer:
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8787/jobs/<job_id>/approvals/<call_id> `
-ContentType application/json `
-Body '{"decision":"approve"}'
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8787/jobs/<job_id>/inputs/<input_id> `
-ContentType application/json `
-Body '{"answer":"main"}'Start the Web application in another terminal:
cd apps/web
$env:ROVE_API_BASE = "http://127.0.0.1:8787"
pnpm devOpen http://localhost:3000 for the default M1 product shell. Open
http://localhost:3000/dev/workbench only for the advanced direct-run/history
surface. The product-shell manual pass criteria are:
- creating a job from the UI works against the real API, not a mocked API;
- live status updates appear from the SSE stream;
- pending approvals and inputs are visible and actionable;
- the selected product session and Inspector show completed, interrupted, rejected, and failed states as applicable;
- the Inspector shows tool names, arguments or summaries, and final status;
- Playwright screenshots and traces are retained under
<integration-root>/artifactswhen automated.
scripts/integration-smoke.ps1 runs the local-full profile:
powershell -ExecutionPolicy Bypass -File scripts/integration-smoke.ps1Run the same local-full profile on non-default ports when 8787 or 3000 is
busy:
$root = Join-Path $env:TEMP "rove-integration-custom"
powershell -ExecutionPolicy Bypass -File scripts/integration-smoke.ps1 `
-ApiAddr "127.0.0.1:18788" `
-WebPort "13000" `
-IntegrationRoot $rootUseful focused modes:
powershell -ExecutionPolicy Bypass -File scripts/integration-smoke.ps1 -SkipWebE2E
powershell -ExecutionPolicy Bypass -File scripts/integration-smoke.ps1 -SkipApiSmoke
powershell -ExecutionPolicy Bypass -File scripts/integration-smoke.ps1 -KeepStateThe runner:
- Loads
.env.integrationwhen present and falls back to safelocal-fulldefaults. - Verifies required commands are available:
cargoandpnpm. - Creates
<integration-root>/workspace, explicit isolated runtime state and user config under that workspace,<integration-root>/data-root, and<integration-root>/artifacts; it sets bothROVE_CONFIG_ROOTandROVE_DATA_ROOTso neither ProductStore/default contract state nor user configuration can read or rewrite the operator's real profile. - Starts
cargo run -p rove-api -- --addr <addr> -C <workspace>with integration env. - Waits until
GET /runssucceeds. - Runs API smoke scenarios and saves JSON responses under
<integration-root>/artifacts/api. - Starts
pnpm exec next dev --port <port>inapps/webwithROVE_API_BASEpointing to the API. - Runs the gated real-API Playwright suite with
ROVE_REAL_API_E2E=1,ROVE_REAL_API_WORKBENCH_SMOKE=1,ROVE_WEB_PORT=<port>, andPLAYWRIGHT_BASE_URL=http://127.0.0.1:<port>. - Stops API and Web processes even when a check fails.
- Prints run ids and artifact paths.
The runner does not run provider-smoke, external-tools, or stress; those remain explicit follow-up gates.
The current local-full run passes all five real-api.spec.ts cases against
the live Rust API:
- M1 migration runs before product catalog boot and does not replay on refresh;
- the default product shell proves exact interleaved A/B continuation, refresh, approval, input, cancellation, Settings, and durable deep routes;
- one queued message is promoted and another revoked through the unified message lifecycle during an active run;
- a completed session forks at its verified boundary and the child continues independently;
/dev/workbenchremains available through one bounded direct-run smoke.
scripts/provider-integration.ps1 is the provider gate for
OpenAI APIs, Anthropic, and local Ollama. It is intentionally not
tied to one vendor: the provider, base URL, API-key environment variable, model
id, ports, stress counts, restart recovery, long-soak settings, and model-list
endpoint are parameters.
The API and both Web surfaces support provider profiles at runtime. Browser
code submits provider_type, api_base, optional display name, and
api_key_env when a key is required. It cannot submit wire_protocol; the
system maps that diagnostic identity from provider_type. The Rust API reads
the named environment variable, while the raw key never enters the browser or
request payload. POST /providers/test verifies model inventory before a run,
and POST /jobs may include the same profile to route that job through OpenAI,
OpenAI Responses, Anthropic, Ollama, or Fake. Relay/gateway APIs use the
openai type with their own base URL.
The provider runner's optional browser step now creates or reuses an API-backed
provider profile, product workspace, and product session, persists the exact
selection, and navigates to that session in the default shell. It captures
job_id, run_id, and resumed_from_run_id from the browser's
POST /api/jobs response and verifies that exact report and transcript binding;
it does not guess a latest run by sorting IDs. This path is implemented in the
integrated C3 code but has not been executed against an external provider.
Fast provider/API-only OpenAI gate:
$env:OPENAI_API_KEY = "<secret>"
powershell -ExecutionPolicy Bypass -File scripts/provider-integration.ps1 `
-Provider openai `
-ApiBase "https://api.openai.com/v1" `
-ApiKeyEnv OPENAI_API_KEY `
-Model "gpt-4.1-mini" `
-SkipWebSmokeRelay or gateway provider/API-only gate:
$env:OPENAI_API_KEY = "<relay-secret>"
powershell -ExecutionPolicy Bypass -File scripts/provider-integration.ps1 `
-Provider openai `
-ApiBase "https://<gateway-host>/v1" `
-ApiKeyEnv OPENAI_API_KEY `
-Model "<provider/model-id>" `
-SkipWebSmokeAnthropic gate:
$env:ANTHROPIC_API_KEY = "<secret>"
powershell -ExecutionPolicy Bypass -File scripts/provider-integration.ps1 `
-Provider anthropic `
-ApiBase "https://api.anthropic.com" `
-ApiKeyEnv ANTHROPIC_API_KEY `
-Model "claude-3-5-haiku-latest" `
-SkipWebSmokeOllama provider/API-only gate:
powershell -ExecutionPolicy Bypass -File scripts/provider-integration.ps1 `
-Provider ollama `
-ApiBase "http://localhost:11434" `
-Model "llama3.2" `
-SkipWebSmokeOmit -SkipWebSmoke when collecting the external-provider product-shell gate.
That run must preserve the runner's redacted Web result, exact report, and exact
product transcript artifacts. No such external-provider C3 run is currently
claimed.
Release stress with restart:
powershell -ExecutionPolicy Bypass -File scripts/provider-integration.ps1 `
-Provider openai `
-ApiBase "https://<provider-or-gateway>/v1" `
-ApiKeyEnv OPENAI_API_KEY `
-Model "<model-id>" `
-SkipWebSmoke `
-RunStress `
-RunRestartRecovery `
-StressSequentialCount 20 `
-StressConcurrentCount 5Long soak:
powershell -ExecutionPolicy Bypass -File scripts/provider-integration.ps1 `
-Provider openai `
-ApiBase "https://<provider-or-gateway>/v1" `
-ApiKeyEnv OPENAI_API_KEY `
-Model "<model-id>" `
-SkipWebSmoke `
-RunStress `
-RunRestartRecovery `
-RunLongSoak `
-LongSoakCount 100 `
-LongSoakDelayMs 1000The runner:
- Loads
.env.integrationwithout overriding existing shell variables. - Queries the provider-specific model inventory unless
-SkipModelInventoryis set. - Runs the provider-specific smoke unless
-SkipProviderSmokeis set. - Starts an isolated
rove-apiand runs one plain job plus oneechotool job. - Unless
-SkipWebSmokeis set, starts the Web application and attempts a real-providerechorun through the exact API-backed product session, then verifies the browser-returned job/run IDs against report and transcript data. - Runs sequential/concurrent provider stress only when
-RunStressis passed, writes per-job state/report artifacts, and classifies failures. - Restarts the stress API and verifies completed run ids when
-RunRestartRecoveryis passed. - Runs a configurable long sequential soak when
-RunLongSoakis passed. - Runs a configured MCP tool through API/report records only when
-RunExternalMcpis passed. - Writes non-secret evidence under
<integration-root>/artifacts, includingevidence-summary.json.
Keep .env.integration, raw keys, bearer tokens, logs containing secrets, and
runtime state out of git. The runner records only key_present, key env names,
and provider bases, never key values.
shell.spec.ts, continuity.spec.ts, settings.spec.ts, migration.spec.ts,
and polish.spec.ts cover broad default-shell behavior with browser-boundary
mocks; workbench.spec.ts mock-covers the advanced surface. The real-API suite
is apps/web/tests/e2e/real-api.spec.ts and is gated by
ROVE_REAL_API_E2E=1. Its advanced case additionally requires
ROVE_REAL_API_WORKBENCH_SMOKE=1.
The real-API suite does not start the Rust API itself. The runner owns API/Web process lifecycle so failures can preserve both logs. The suite:
- migrates safe M1 browser state before product catalog boot and verifies idempotent refresh behavior;
- opens
/, creates interleaved A/B product sessions, verifies exact resume and refresh restore, and exercises approval, input, cancellation, Settings, and deep routes; - opens
/dev/workbenchonly for a bounded direct-run smoke; - attach screenshots or traces when assertions fail.
Project Trust Settings changes additionally require the normal mocked browser
suite (pnpm test:e2e). tests/e2e/project-trust.spec.ts covers
unknown/restricted/trusted/revoked, capability selection, grant/deny/revoke,
digest invalidation, and asserts that browser requests contain only the
server-owned workspace ID, never a local authority path. Real provider and
official filesystem MCP checks remain optional and must not be reported as
passed when their environment gates are unset.
The targeted Project Trust Playwright case passes against the deterministic mock
API. A full local-full acceptance run is separate evidence; deterministic fake
provider coverage does not substitute for the optional external-provider gate,
which remains unrun.
The TUI PTY gate is opt-in and uses only the local fake model; it never needs
provider credentials or a production endpoint. It builds rove when a binary
is not supplied, launches rove tui --model fake inside an isolated temporary
workspace, and checks a nonblank frame, a bounded resize/redraw, clean
Ctrl+Q exit, PTY termios restoration, and the alternate-screen,
bracketed-paste, and cursor restore sequences.
Run it explicitly from the repository root:
python scripts/tui-pty-smoke.py --runThe harness is currently implemented for Unix PTYs where Python exposes
pty, fcntl, and termios. On Windows it emits a JSON status: "skipped"
with the reason that a native ConPTY runner is not yet included and exits with
code 77; that skip is not interoperability evidence. Missing Unix PTY
modules produce the same typed skip. The gate has bounded build, runtime, and
output limits and strips provider-key-shaped variables from the child
environment. A prebuilt binary can be supplied with --binary; use
--skip-build to make a missing binary an explicit failure.
Use docs/runtime/provider-smoke.md as the source of truth. With no gate enabled,
cargo test -p rove-integration-tests --test provider_smoke should pass by
skipping real calls. Enable one provider at a time:
$env:ROVE_PROVIDER_SMOKE_OPENAI = "1"
$env:OPENAI_API_KEY = "<secret>"
$env:ROVE_PROVIDER_SMOKE_OPENAI_MODEL = "gpt-4.1-mini"
cargo test -p rove-integration-tests --test provider_smoke openai_real_provider_smoke_when_enabled -- --exact --nocapturePassing provider-smoke proves provider reachability, event normalization, and one native tool-use round trip. It does not replace local-full.
The deterministic MCP fixture is tests/fixtures/mcp_mock_server.py; .rove/mcp_servers.example.json points at it. Once copied to .rove/mcp_servers.json, expected tool names include:
mcp__mock_server__echo_remotemcp__mock_server__delete_remote
Default verification remains:
cargo test -p rove-integration-tests --test mcpFirst-wave deterministic environment/trust verification is:
cargo test -p rove-app-bootstrap
cargo test -p rove-runtime --test environment_contract
cargo test -p rove-runtime --test tool_contract
cargo test -p rove-runtime --test mcp_contract
cargo test -p rove-integration-tests --test tool_safety
cargo test -p rove-integration-tests --test mcp
cargo test -p rove-integration-tests --test api
Set-Location apps/web
pnpm test
pnpm typecheck
pnpm build
pnpm test:e2eThe official filesystem MCP smoke remains opt-in:
$env:ROVE_MCP_FILESYSTEM_SMOKE = "1"
cargo test -p rove-integration-tests --test mcp mcp_official_filesystem_server_smoke_when_enabled -- --exact --nocapturescripts/product-acceptance.ps1 and scripts/product-acceptance.sh are two
entries over one shared check table. Both execute every gate and write
PRODUCT_ACCEPTANCE_REPORT.json:
powershell -ExecutionPolicy Bypass -File scripts/product-acceptance.ps1bash scripts/product-acceptance.shFlags: -SkipWeb / --skip-web, -SkipBrowser / --skip-browser, and
-IncludeGated / --include-gated for env-gated checks.
Report contract:
- Every status is derived from a real process exit code. There is no default or
inferred
pass. - A check that does not execute is
not_runand must carry areason. - The verdict is
PASSonly when there are zerofailresults and zero requirednot_runresults. Gated checks may benot_runwithout blocking, but they still appear in the report. - The runner exits non-zero on any verdict other than
PASS, so it is usable as a CI gate. - Provenance records branch, commit, dirty flag, dirty file count, OS, and toolchain versions, plus each check's command, working directory, exit code, duration, and output tail. A report produced from a dirty tree says so.
Per-check logs are written to .rove/acceptance-logs/.
local-full passes only when all required scenarios have evidence from
API/state, the default product shell, and the bounded advanced smoke:
- every expected run has a run id and terminal status;
- approval and input pending records are created, resolved, and no longer pending afterward;
- filesystem writes stay inside
<integration-root>/workspace; /runsand/runs/{run_id}/reportagree with live job state;- migration precedes catalog reads and does not replay after refresh;
- interleaved product sessions resume their own exact run chains after refresh;
- approval, input, cancellation, Settings, and deep routes work through
/; - the bounded
/dev/workbenchdirect-run smoke completes; - logs and artifacts are saved for the run.
If local-full passes but a gated provider or external tool profile fails, the first-round baseline still passes. Track the gated failure separately with its profile name, env configuration, and artifact path.
Passing local-full is the deterministic Web Complete product-shell gate. It is
not external-provider interoperability evidence.
Run the deterministic code gates before a full integration pass:
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cd apps/web
pnpm test
pnpm typecheck
pnpm buildFeature and optional gates can be layered afterward:
cd apps/web
pnpm test:e2e