#!/usr/bin/env bash # Builds the sandbox for the agent run (tests/agent/README.md): an onset-server # with two people, and one workspace per task with its situation in place: a # real conflict, a real waiting land, an unreachable host, a flagged secret. # Usage: tests/agent/setup.sh [path/to/target/debug] # The server keeps running; tests/agent/grade.sh checks the results and stops it. set -euo pipefail SB=$1 ROOT=$(cd "$(dirname "$0")/../.." && pwd) BIN=${2:-$ROOT/target/debug} rm -rf "$SB" mkdir -p "$SB/home" "$SB/bin" SB=$(cd "$SB" && pwd) PORT=$((20000 + RANDOM % 20000)) H="127.0.0.1:$PORT" echo "$PORT" > "$SB/port" serve() { # data, address; prints the server's PID "$BIN/onset-server" serve --data "$1" --listen "$2" --threads 8 >/dev/null 2>>"$SB/server.log" & echo $! } "$BIN/onset-server" user add alice --name Alice --email alice@acme.test --data "$SB/server" > "$SB/alice.token" "$BIN/onset-server" user add sam --name Sam --email sam@acme.test --data "$SB/server" > "$SB/sam.token" serve "$SB/server" "$H" > "$SB/server.pid" for _ in $(seq 50); do curl -s "http://$H/.well-known/onset.json" >/dev/null && break; sleep 0.1; done # The agent's on: Alice on this machine. cat > "$SB/bin/on" </dev/null; } laptop() { ONSET_HOME="$SB/laptop" ONSET_SAVER=0 ONSET_NAME=Alice ONSET_EMAIL=alice@acme.test "$BIN/on" "$@" >/dev/null; } sam() { ONSET_HOME="$SB/sam" ONSET_SAVER=0 ONSET_NAME=Sam ONSET_EMAIL=sam@acme.test "$BIN/on" "$@" >/dev/null; } alice login "$H" --token "$(cat "$SB/alice.token")" laptop login "$H" --token "$(cat "$SB/alice.token")" sam login "$H" --token "$(cat "$SB/sam.token")" # acme/shop: Alice owns it, Sam lands too. cd "$SB" alice init seed mkdir -p seed/src printf '# Shop\n\nWelcom to the shop.\n' > seed/README.md printf 'export const LIMIT = 10;\n' > seed/src/limits.ts printf 'export function total(items) {\n return items.reduce((s, i) => s + i.price, 0);\n}\n' > seed/src/cart.ts printf 'export const stripeKey = process.env.STRIPE_KEY;\n' > seed/src/config.ts alice -C seed land -m "First version" alice -C seed publish "$H/acme/shop" "$BIN/onset-server" member add acme/shop sam --role member --data "$SB/server" printf 'Sale!\n' > seed/banner.txt alice -C seed land -m "Add banner" BANNER=$("$SB/bin/on" --json -C seed log -n 1 | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])') printf 'footer\n' > seed/footer.txt alice -C seed land -m "Add footer" rm -rf seed # T1, T5, T9: the main workspace. alice get "$H/acme/shop" home/shop # T14: a workspace from before Sam's change. alice get "$H/acme/shop" home/shop-old # T2: a conflict. Alice raised the limit; Sam lands a different one first. alice get "$H/acme/shop" home/shop-limits printf 'export const LIMIT = 50;\n' > home/shop-limits/src/limits.ts alice -C home/shop-limits status sam get "$H/acme/shop" sam-shop printf 'export const LIMIT = 20; // Sam: raised for the beta\n' > sam-shop/src/limits.ts sam -C sam-shop land -m "Raise the limit for the beta" # T4, T12, T13: plain workspaces with work in them. alice get "$H/acme/shop" home/shop-notes alice get "$H/acme/shop" home/shop-docs printf 'How to run the shop.\n' > home/shop-docs/docs.txt alice -C home/shop-docs status alice get "$H/acme/shop" home/shop-junk printf 'junk\n' > home/shop-junk/junk.txt alice -C home/shop-junk status # T6: a change started on the laptop yesterday. laptop get "$H/acme/shop" laptop-shop printf 'draft\n' > laptop-shop/draft.txt laptop -C laptop-shop describe "Draft feature" DRAFT=$(ONSET_HOME="$SB/laptop" ONSET_SAVER=0 "$BIN/on" --json -C laptop-shop status | python3 -c 'import json,sys; print(json.load(sys.stdin)["change"]["id"])') # T8: a live key in the config. alice get "$H/acme/shop" home/shop-config printf 'export const stripeKey = process.env.STRIPE_KEY;\nexport const fallbackKey = "sk_live_%s";\n' "$(head -c 18 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 24)" > home/shop-config/src/config.ts alice -C home/shop-config status # T7: sam/parser is Sam's; Alice can read it but not land. sam init parser-seed printf 'parse()\n' > parser-seed/parser.txt sam -C parser-seed land -m "Parser" sam -C parser-seed publish "$H/sam/parser" "$BIN/onset-server" member add sam/parser alice --role reader --data "$SB/server" alice get "$H/sam/parser" home/parser printf 'parse() // handles empty input\n' > home/parser/parser.txt alice -C home/parser status # T10: a project on a host that's down by the time the agent starts. H2="127.0.0.1:$((PORT + 1))" "$BIN/onset-server" user add alice --name Alice --email alice@acme.test --data "$SB/server2" > "$SB/alice2.token" PID2=$(serve "$SB/server2" "$H2") for _ in $(seq 50); do curl -s "http://$H2/.well-known/onset.json" >/dev/null && break; sleep 0.1; done alice login "$H2" --token "$(cat "$SB/alice2.token")" alice init search-seed printf 'search v1\n' > search-seed/search.txt alice -C search-seed land -m "Search" alice -C search-seed publish "$H2/alice/search" alice get "$H2/alice/search" home/search printf 'search v2, faster\n' > home/search/search.txt alice -C home/search status kill "$PID2" # T11: a git repository to bring in. g() { git -c user.name=Ada -c user.email=ada@example.com -c init.defaultBranch=main -c commit.gpgsign=false "$@"; } g init -q home/legacy-git (cd home/legacy-git && printf 'v1\n' > app.txt && g add -A && g commit -qm "Start" && printf 'v2\n' > app.txt && g add -A && g commit -qm "Second" && printf 'v3\n' > app.txt && g add -A && g commit -qm "Third") # T3: src/cart.ts worked two minutes ago, then broke. alice get "$H/acme/shop" home/shop-cart printf 'export function total(items) {\n // v2: skips free items\n return items.filter((i) => i.price > 0).reduce((s, i) => s + i.price, 0);\n}\n' > home/shop-cart/src/cart.ts alice -C home/shop-cart status sleep 130 printf 'export function total(items) {\n return items.reduce((s, i) => s + i.prce, 0) BROKEN\n}\n' > home/shop-cart/src/cart.ts printf 'keep me\n' > home/shop-cart/notes.txt alice -C home/shop-cart status cat > "$SB/facts" <