dev: gate dogfood restarts with isolated smoke
This commit is contained in:
@@ -19,12 +19,6 @@ Workerの状態から純粋に再現可能で、且つ揮発性の無い操作
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 実際のセッションを読んでデバッグする
|
|
||||||
|
|
||||||
`~/.yoi/sessions`にすべてのセッションがある。jsonlなので、いい感じにBashで読むこと。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 検証
|
## 検証
|
||||||
|
|
||||||
開発中は、変更した契約を証明する最小の target / filter から実行する。
|
開発中は、変更した契約を証明する最小の target / filter から実行する。
|
||||||
@@ -50,7 +44,6 @@ workspace全体、E2E、Nix/Docker buildなどの重い検証は、変更した
|
|||||||
|
|
||||||
Yoi Worker で作業する場合、Ticket の authority・ライフサイクル・操作方法は Yoi
|
Yoi Worker で作業する場合、Ticket の authority・ライフサイクル・操作方法は Yoi
|
||||||
system instructions と、その Worker に提供された typed Ticket tools に従うこと。
|
system instructions と、その Worker に提供された typed Ticket tools に従うこと。
|
||||||
backend や CLI の具体的な手順はこのリポジトリの `AGENTS.md` では重複して定義しない。
|
|
||||||
|
|
||||||
Codex など typed Ticket tools が提供されていないクライアントでは、`yoi ticket`
|
Codex など typed Ticket tools が提供されていないクライアントでは、`yoi ticket`
|
||||||
CLI や保存先の直接操作で Ticket tools を代替しないこと。Ticket の作成・更新は
|
CLI や保存先の直接操作で Ticket tools を代替しないこと。Ticket の作成・更新は
|
||||||
@@ -59,3 +52,8 @@ Yoi Worker に委ねる。
|
|||||||
---
|
---
|
||||||
|
|
||||||
YoiでYoiを開発している際、AI自身のフィードバックを元に改善を回すために `docs/report/`ディレクトリに感じた障壁や改善案等を書き残す形にした。 明確に力不足な点/ツールの問題があった場合や、ユーザーからの指示があった際に作ること。
|
YoiでYoiを開発している際、AI自身のフィードバックを元に改善を回すために `docs/report/`ディレクトリに感じた障壁や改善案等を書き残す形にした。 明確に力不足な点/ツールの問題があった場合や、ユーザーからの指示があった際に作ること。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
絶対に自身が動作しているプロセスを止めないこと。
|
||||||
|
マージ後のドッグフーディング環境の更新は必ずユーザーの操作で行う。
|
||||||
|
|||||||
@@ -451,6 +451,11 @@ impl ResolvedWorkspaceBackendConfig {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_backend_base_url(mut self, base_url: impl Into<String>) -> Self {
|
||||||
|
self.server.backend_base_url = Some(base_url.into().trim_end_matches('/').to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_listen(mut self, listen: SocketAddr) -> Self {
|
pub fn with_listen(mut self, listen: SocketAddr) -> Self {
|
||||||
self.listen = listen;
|
self.listen = listen;
|
||||||
self
|
self
|
||||||
@@ -579,6 +584,24 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn backend_base_url_is_explicit_and_normalized() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let listen = "127.0.0.1:48787".parse().unwrap();
|
||||||
|
let resolved = WorkspaceBackendConfigFile::load_for_workspace(dir.path())
|
||||||
|
.unwrap()
|
||||||
|
.resolve(dir.path(), identity())
|
||||||
|
.unwrap()
|
||||||
|
.with_listen(listen)
|
||||||
|
.with_backend_base_url("http://127.0.0.1:48787/");
|
||||||
|
|
||||||
|
assert_eq!(resolved.listen, listen);
|
||||||
|
assert_eq!(
|
||||||
|
resolved.server.backend_base_url.as_deref(),
|
||||||
|
Some("http://127.0.0.1:48787")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_unknown_fields() {
|
fn rejects_unknown_fields() {
|
||||||
let error = WorkspaceBackendConfigFile::parse_str("[server]\nunknown = true\n", "test")
|
let error = WorkspaceBackendConfigFile::parse_str("[server]\nunknown = true\n", "test")
|
||||||
|
|||||||
@@ -571,11 +571,15 @@ async fn run_serve(options: ServeOptions) -> Result<(), Box<dyn std::error::Erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
let listener = TcpListener::bind(resolved.listen).await?;
|
let listener = TcpListener::bind(resolved.listen).await?;
|
||||||
|
let local_addr = listener.local_addr()?;
|
||||||
|
if resolved.server.backend_base_url.is_none() {
|
||||||
|
resolved = resolved.with_backend_base_url(format!("http://{local_addr}"));
|
||||||
|
}
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"yoi-server: serving workspace `{}` from server DB `{}` on http://{}",
|
"yoi-server: serving workspace `{}` from server DB `{}` on http://{}",
|
||||||
workspace.workspace_id,
|
workspace.workspace_id,
|
||||||
database_path.display(),
|
database_path.display(),
|
||||||
listener.local_addr()?
|
local_addr
|
||||||
);
|
);
|
||||||
serve(resolved.server, store, listener).await?;
|
serve(resolved.server, store, listener).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -2,6 +2,38 @@
|
|||||||
|
|
||||||
This repository is developed with Yoi itself. Dogfooding is valuable because it exposes orchestration, memory, TUI, and workflow problems under real use.
|
This repository is developed with Yoi itself. Dogfooding is valuable because it exposes orchestration, memory, TUI, and workflow problems under real use.
|
||||||
|
|
||||||
|
## Pre-restart gate
|
||||||
|
|
||||||
|
Never use the live dogfood Server or Runtime as the first startup test for a new
|
||||||
|
binary. A dogfood restart is allowed only after this sequence succeeds:
|
||||||
|
|
||||||
|
1. Build the production entrypoints:
|
||||||
|
`cargo build -p worker-runtime --bin yoi-runtime -p yoi-workspace-server --bin yoi-server`.
|
||||||
|
2. Run the focused and dependent tests for the changed contracts, followed by
|
||||||
|
`cargo fmt --all -- --check` and `git diff --check HEAD`.
|
||||||
|
3. Run `scripts/isolated-startup-smoke.sh` from an external shell/process.
|
||||||
|
4. Inspect any failed run's retained `/tmp/yoi-isolated-startup-smoke.*` logs;
|
||||||
|
do not restart dogfood until the cause is fixed and the smoke passes.
|
||||||
|
5. Have an external supervisor or operator restart Server and Runtime. A Worker
|
||||||
|
hosted by the target Runtime must never terminate its own Runtime.
|
||||||
|
6. Verify post-restart readiness through the Workspace Runtime projection and a
|
||||||
|
real restored Worker operation before treating the environment as healthy.
|
||||||
|
|
||||||
|
The smoke harness runs the normal `yoi-server` and `yoi-runtime` binaries using
|
||||||
|
separate `HOME`, `XDG_DATA_HOME`, `XDG_CONFIG_HOME`, temporary Git repository,
|
||||||
|
Server database, Runtime fs store, identity/trust material, and non-dogfood
|
||||||
|
ports. It fails if either port is already occupied, if state escapes the
|
||||||
|
temporary root, if a process exits unexpectedly, if Runtime readiness is not
|
||||||
|
visible through Server, or if startup logs contain a panic, migration collision,
|
||||||
|
or Worker execution restore failure. It also proves that a listening Server
|
||||||
|
without its configured Runtime is not readiness and restarts the isolated
|
||||||
|
Runtime once to exercise persistence reopen.
|
||||||
|
|
||||||
|
Override `YOI_SMOKE_SERVER_BIN`, `YOI_SMOKE_RUNTIME_BIN`,
|
||||||
|
`YOI_SMOKE_SERVER_PORT`, or `YOI_SMOKE_RUNTIME_PORT` only when a separate build
|
||||||
|
or port is intentionally under test. Set `YOI_SMOKE_KEEP=1` to retain successful
|
||||||
|
artifacts. Failed artifacts are retained automatically.
|
||||||
|
|
||||||
## What to record
|
## What to record
|
||||||
|
|
||||||
When a tool limitation, workflow obstacle, or model-facing policy problem blocks work, record it under `docs/report/` or a work item artifact. Do not turn every minor annoyance into a maintained design doc.
|
When a tool limitation, workflow obstacle, or model-facing policy problem blocks work, record it under `docs/report/` or a work item artifact. Do not turn every minor annoyance into a maintained design doc.
|
||||||
|
|||||||
Executable
+301
@@ -0,0 +1,301 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Starts production Server/Runtime binaries against disposable state and ports.
|
||||||
|
# This script must never read or write the caller's Yoi data/config directories.
|
||||||
|
|
||||||
|
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
server_bin=${YOI_SMOKE_SERVER_BIN:-"$repo_root/target/debug/yoi-server"}
|
||||||
|
runtime_bin=${YOI_SMOKE_RUNTIME_BIN:-"$repo_root/target/debug/yoi-runtime"}
|
||||||
|
server_port=${YOI_SMOKE_SERVER_PORT:-48787}
|
||||||
|
runtime_port=${YOI_SMOKE_RUNTIME_PORT:-48800}
|
||||||
|
keep=${YOI_SMOKE_KEEP:-0}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'isolated-startup-smoke: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for command in curl git node ss; do
|
||||||
|
command -v "$command" >/dev/null || fail "required command is unavailable: $command"
|
||||||
|
done
|
||||||
|
[[ -x "$server_bin" ]] || fail "Server binary is not executable: $server_bin"
|
||||||
|
[[ -x "$runtime_bin" ]] || fail "Runtime binary is not executable: $runtime_bin"
|
||||||
|
[[ "$server_port" =~ ^[0-9]+$ ]] || fail "invalid Server port: $server_port"
|
||||||
|
[[ "$runtime_port" =~ ^[0-9]+$ ]] || fail "invalid Runtime port: $runtime_port"
|
||||||
|
[[ "$server_port" != "$runtime_port" ]] || fail "Server and Runtime ports must differ"
|
||||||
|
|
||||||
|
port_is_listening() {
|
||||||
|
local port=$1
|
||||||
|
ss -H -ltn "sport = :$port" | grep -q .
|
||||||
|
}
|
||||||
|
|
||||||
|
port_is_listening "$server_port" && fail "Server smoke port is already in use: $server_port"
|
||||||
|
port_is_listening "$runtime_port" && fail "Runtime smoke port is already in use: $runtime_port"
|
||||||
|
|
||||||
|
root=$(mktemp -d "${TMPDIR:-/tmp}/yoi-isolated-startup-smoke.XXXXXX")
|
||||||
|
server_pid=
|
||||||
|
runtime_pid=
|
||||||
|
|
||||||
|
stop_pid() {
|
||||||
|
local pid=${1:-}
|
||||||
|
[[ -n "$pid" ]] || return 0
|
||||||
|
kill -TERM "$pid" 2>/dev/null || true
|
||||||
|
for _ in $(seq 1 50); do
|
||||||
|
kill -0 "$pid" 2>/dev/null || break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
kill -KILL "$pid" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
wait "$pid" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
local status=$?
|
||||||
|
trap - EXIT INT TERM
|
||||||
|
stop_pid "$runtime_pid"
|
||||||
|
stop_pid "$server_pid"
|
||||||
|
if [[ "$keep" == 1 ]]; then
|
||||||
|
printf 'isolated-startup-smoke: kept artifacts at %s\n' "$root" >&2
|
||||||
|
elif [[ $status -eq 0 ]]; then
|
||||||
|
rm -rf "$root"
|
||||||
|
else
|
||||||
|
printf 'isolated-startup-smoke: failed; artifacts kept at %s\n' "$root" >&2
|
||||||
|
fi
|
||||||
|
exit "$status"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
mkdir -p "$root/home" "$root/data" "$root/config" "$root/repository" "$root/logs"
|
||||||
|
export HOME="$root/home"
|
||||||
|
export XDG_DATA_HOME="$root/data"
|
||||||
|
export XDG_CONFIG_HOME="$root/config"
|
||||||
|
unset YOI_DATA_DIR YOI_CONFIG_HOME
|
||||||
|
|
||||||
|
# Fail closed if isolation variables no longer point below the disposable root.
|
||||||
|
case "$HOME:$XDG_DATA_HOME:$XDG_CONFIG_HOME" in
|
||||||
|
"$root"/*:"$root"/*:"$root"/*) ;;
|
||||||
|
*) fail "HOME/XDG isolation guard failed" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
server_url="http://127.0.0.1:$server_port"
|
||||||
|
runtime_url="http://127.0.0.1:$runtime_port"
|
||||||
|
server_id=isolated-smoke-server
|
||||||
|
runtime_id=isolated-smoke-runtime
|
||||||
|
|
||||||
|
git -C "$root/repository" init -q
|
||||||
|
git -C "$root/repository" config user.email smoke@example.invalid
|
||||||
|
git -C "$root/repository" config user.name 'Yoi isolated smoke'
|
||||||
|
printf '# isolated smoke\n' >"$root/repository/README.md"
|
||||||
|
git -C "$root/repository" add README.md
|
||||||
|
git -C "$root/repository" commit -qm 'test: initialize isolated smoke repository'
|
||||||
|
|
||||||
|
"$server_bin" identity init --server-id "$server_id" >"$root/logs/server-identity-init.log" 2>&1
|
||||||
|
"$runtime_bin" identity init --runtime-id "$runtime_id" >"$root/logs/runtime-identity-init.log" 2>&1
|
||||||
|
server_identity=$("$server_bin" identity show --json)
|
||||||
|
runtime_identity=$("$runtime_bin" identity show --json)
|
||||||
|
server_key=$(printf '%s' "$server_identity" | node -e 'const fs=require("fs"); process.stdout.write(JSON.parse(fs.readFileSync(0,"utf8")).public_key)')
|
||||||
|
runtime_key=$(printf '%s' "$runtime_identity" | node -e 'const fs=require("fs"); process.stdout.write(JSON.parse(fs.readFileSync(0,"utf8")).public_key)')
|
||||||
|
|
||||||
|
"$server_bin" trust-runtime add \
|
||||||
|
--runtime-id "$runtime_id" \
|
||||||
|
--public-key "$runtime_key" \
|
||||||
|
--base-url "$runtime_url" >"$root/logs/server-trust-runtime.log" 2>&1
|
||||||
|
"$runtime_bin" trust-server add \
|
||||||
|
--server-id "$server_id" \
|
||||||
|
--public-key "$server_key" >"$root/logs/runtime-trust-server.log" 2>&1
|
||||||
|
|
||||||
|
"$server_bin" init --workspace "$root/repository" >"$root/logs/server-init.log" 2>&1
|
||||||
|
workspace_id=$(sed -n 's/^workspace_id = "\([^"]*\)"/\1/p' "$root/repository/.yoi/workspace.toml")
|
||||||
|
[[ -n "$workspace_id" ]] || fail "workspace init did not write workspace_id"
|
||||||
|
# Runtime Git materialization requires a clean source repository. Commit both
|
||||||
|
# local bootstrap markers inside this disposable repository.
|
||||||
|
git -C "$root/repository" add .yoi/workspace.toml .yoi/workspace-backend.local.toml
|
||||||
|
git -C "$root/repository" commit -qm 'test: record isolated Yoi workspace markers'
|
||||||
|
|
||||||
|
runtime_store="$XDG_DATA_HOME/yoi/runtime"
|
||||||
|
mkdir -p "$runtime_store/workers"
|
||||||
|
cat >"$runtime_store/runtime.json" <<'JSON'
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"display_name": "isolated startup smoke",
|
||||||
|
"backend": "fs_store",
|
||||||
|
"status": "running",
|
||||||
|
"next_worker_sequence": 1,
|
||||||
|
"next_diagnostic_id": 1,
|
||||||
|
"config_bundles": {},
|
||||||
|
"workspace_owners": {},
|
||||||
|
"diagnostics": []
|
||||||
|
}
|
||||||
|
JSON
|
||||||
|
|
||||||
|
start_server() {
|
||||||
|
: >"$root/logs/server.log"
|
||||||
|
"$server_bin" serve --listen "127.0.0.1:$server_port" >"$root/logs/server.log" 2>&1 &
|
||||||
|
server_pid=$!
|
||||||
|
}
|
||||||
|
|
||||||
|
start_runtime() {
|
||||||
|
: >"$root/logs/runtime.log"
|
||||||
|
"$runtime_bin" --bind "127.0.0.1:$runtime_port" >"$root/logs/runtime.log" 2>&1 &
|
||||||
|
runtime_pid=$!
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_listener() {
|
||||||
|
local pid=$1
|
||||||
|
local port=$2
|
||||||
|
local name=$3
|
||||||
|
for _ in $(seq 1 150); do
|
||||||
|
kill -0 "$pid" 2>/dev/null || fail "$name exited before listening; inspect $root/logs"
|
||||||
|
port_is_listening "$port" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
fail "$name did not listen on port $port within 15 seconds"
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_projection() {
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
"$server_url/api/w/$workspace_id/runtimes"
|
||||||
|
}
|
||||||
|
|
||||||
|
projection_is_ready() {
|
||||||
|
node -e '
|
||||||
|
const fs = require("fs");
|
||||||
|
const runtimeId = process.argv[1];
|
||||||
|
const body = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||||
|
const runtime = body.items.find((item) => item.runtime_id === runtimeId);
|
||||||
|
if (!runtime || runtime.status !== "running") process.exit(1);
|
||||||
|
if (!runtime.capabilities?.can_list_workers) process.exit(1);
|
||||||
|
if ((runtime.diagnostics ?? []).length !== 0) process.exit(1);
|
||||||
|
if ((body.diagnostics ?? []).length !== 0) process.exit(1);
|
||||||
|
' "$runtime_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_projection_state() {
|
||||||
|
local expected=$1
|
||||||
|
local body=
|
||||||
|
for _ in $(seq 1 150); do
|
||||||
|
kill -0 "$server_pid" 2>/dev/null || fail "Server exited during readiness check"
|
||||||
|
body=$(runtime_projection 2>/dev/null || true)
|
||||||
|
if [[ -n "$body" ]]; then
|
||||||
|
if printf '%s' "$body" | projection_is_ready 2>/dev/null; then
|
||||||
|
[[ "$expected" == ready ]] && return 0
|
||||||
|
else
|
||||||
|
[[ "$expected" == not-ready ]] && return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
printf '%s\n' "$body" >"$root/logs/last-runtime-projection.json"
|
||||||
|
fail "Runtime projection did not become $expected within 15 seconds"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_clean_logs() {
|
||||||
|
if grep -Eiq 'panicked at|thread .* panicked|UNIQUE constraint failed|worker_execution_restore_failed' \
|
||||||
|
"$root/logs/server.log" "$root/logs/runtime.log"; then
|
||||||
|
fail "panic, migration collision, or restore failure found in startup logs"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_server
|
||||||
|
wait_for_listener "$server_pid" "$server_port" Server
|
||||||
|
|
||||||
|
# Negative control: a listening Server is not readiness. The configured remote
|
||||||
|
# Runtime must be rejected while it is absent.
|
||||||
|
wait_for_projection_state not-ready
|
||||||
|
|
||||||
|
start_runtime
|
||||||
|
wait_for_listener "$runtime_pid" "$runtime_port" Runtime
|
||||||
|
wait_for_projection_state ready
|
||||||
|
assert_clean_logs
|
||||||
|
|
||||||
|
# Listener/catalog readiness is insufficient. Materialize a real Workdir and
|
||||||
|
# require the normal Server -> Runtime Worker spawn path to create a persisted
|
||||||
|
# Worker with an execution handle. This catches adapter panics that startup
|
||||||
|
# alone cannot observe.
|
||||||
|
repositories=$(curl --fail --silent --show-error \
|
||||||
|
"$server_url/api/w/$workspace_id/repositories")
|
||||||
|
repository_id=$(printf '%s' "$repositories" | node -e '
|
||||||
|
const fs = require("fs");
|
||||||
|
const body = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||||
|
if (body.items.length !== 1) process.exit(1);
|
||||||
|
process.stdout.write(body.items[0].id);
|
||||||
|
')
|
||||||
|
workdir_response=$(curl --fail --silent --show-error \
|
||||||
|
--request POST \
|
||||||
|
--header 'content-type: application/json' \
|
||||||
|
--data "{\"runtime_id\":\"$runtime_id\",\"repository_id\":\"$repository_id\"}" \
|
||||||
|
"$server_url/api/w/$workspace_id/runtimes/$runtime_id/working-directories") || \
|
||||||
|
fail "isolated Workdir materialization failed"
|
||||||
|
working_directory_id=$(printf '%s' "$workdir_response" | node -e '
|
||||||
|
const fs = require("fs");
|
||||||
|
const body = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||||
|
if (body.item?.status !== "active" || body.item?.cleanliness !== "clean") process.exit(1);
|
||||||
|
process.stdout.write(body.item.working_directory_id);
|
||||||
|
')
|
||||||
|
|
||||||
|
cat >"$root/worker-create.json" <<JSON
|
||||||
|
{
|
||||||
|
"runtime_id": "$runtime_id",
|
||||||
|
"display_name": "isolated restore smoke",
|
||||||
|
"profile": "builtin:companion",
|
||||||
|
"initial_submit": [],
|
||||||
|
"working_directory": {
|
||||||
|
"working_directory_id": "$working_directory_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSON
|
||||||
|
worker_response=$(curl --fail --silent --show-error \
|
||||||
|
--request POST \
|
||||||
|
--header 'content-type: application/json' \
|
||||||
|
--data @"$root/worker-create.json" \
|
||||||
|
"$server_url/api/w/$workspace_id/workers") || \
|
||||||
|
fail "isolated Worker spawn failed; listener readiness is not sufficient"
|
||||||
|
worker_id=$(printf '%s' "$worker_response" | node -e '
|
||||||
|
const fs = require("fs");
|
||||||
|
const body = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||||
|
if (body.runtime_id !== process.argv[1] || !body.worker_id) process.exit(1);
|
||||||
|
process.stdout.write(String(body.worker_id));
|
||||||
|
' "$runtime_id")
|
||||||
|
node -e '
|
||||||
|
const fs = require("fs");
|
||||||
|
const record = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
||||||
|
const expectedBase = process.argv[2];
|
||||||
|
const profileUrl = record.request?.profile_source?.location?.url;
|
||||||
|
const workspaceUrl = record.request?.workspace_api?.base_url;
|
||||||
|
if (!profileUrl?.startsWith(`${expectedBase}/`)) {
|
||||||
|
console.error(`profile callback escaped isolated Server: ${profileUrl}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (workspaceUrl !== expectedBase) {
|
||||||
|
console.error(`Workspace API escaped isolated Server: ${workspaceUrl}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
' "$runtime_store/workers/$worker_id/worker.json" "$server_url" || \
|
||||||
|
fail "persisted Worker callback URLs are not isolated"
|
||||||
|
assert_clean_logs
|
||||||
|
|
||||||
|
# Exercise persistence reopen with a real persisted Worker and require the
|
||||||
|
# Server projection to recover. The Worker record must remain addressable after
|
||||||
|
# Runtime restart; restore failures and adapter panics are rejected by log scan.
|
||||||
|
stop_pid "$runtime_pid"
|
||||||
|
runtime_pid=
|
||||||
|
wait_for_projection_state not-ready
|
||||||
|
start_runtime
|
||||||
|
wait_for_listener "$runtime_pid" "$runtime_port" Runtime
|
||||||
|
wait_for_projection_state ready
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
"$server_url/api/w/$workspace_id/runtimes/$runtime_id/workers/$worker_id" \
|
||||||
|
>"$root/logs/restored-worker.json" || fail "persisted Worker is unavailable after Runtime restart"
|
||||||
|
assert_clean_logs
|
||||||
|
|
||||||
|
# Prove that this run used only disposable state paths.
|
||||||
|
grep -Fq "$root/data/yoi/server/server.db" "$root/logs/server.log" || \
|
||||||
|
fail "Server log does not identify the isolated database"
|
||||||
|
if grep -Fq '/home/hare/.local/share/yoi' "$root/logs/server.log" "$root/logs/runtime.log"; then
|
||||||
|
fail "startup logs reference a non-isolated Yoi data path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'isolated-startup-smoke: PASS (workspace=%s, server=%s, runtime=%s)\n' \
|
||||||
|
"$workspace_id" "$server_url" "$runtime_url"
|
||||||
Reference in New Issue
Block a user