Onset: version control where nobody waits and nothing gets lost. The on command, its core and onset-server.
| 1 | #!/usr/bin/env bash |
| 2 | # Builds the sandbox for the agent run (tests/agent/README.md): an onset-server |
| 3 | # with two people, and one workspace per task with its situation in place: a |
| 4 | # real conflict, a real waiting land, an unreachable host, a flagged secret. |
| 5 | # Usage: tests/agent/setup.sh <sandbox-dir> [path/to/target/debug] |
| 6 | # The server keeps running; tests/agent/grade.sh checks the results and stops it. |
| 7 | set -euo pipefail |
| 8 |
| 9 | SB=$1 |
| 10 | ROOT=$(cd "$(dirname "$0")/../.." && pwd) |
| 11 | BIN=${2:-$ROOT/target/debug} |
| 12 | rm -rf "$SB" |
| 13 | mkdir -p "$SB/home" "$SB/bin" |
| 14 | SB=$(cd "$SB" && pwd) |
| 15 | PORT=$((20000 + RANDOM % 20000)) |
| 16 | H="127.0.0.1:$PORT" |
| 17 | echo "$PORT" > "$SB/port" |
| 18 |
| 19 | serve() { # data, address; prints the server's PID |
| 20 | "$BIN/onset-server" serve --data "$1" --listen "$2" --threads 8 >/dev/null 2>>"$SB/server.log" & |
| 21 | echo $! |
| 22 | } |
| 23 | "$BIN/onset-server" user add alice --name Alice --email alice@acme.test --data "$SB/server" > "$SB/alice.token" |
| 24 | "$BIN/onset-server" user add sam --name Sam --email sam@acme.test --data "$SB/server" > "$SB/sam.token" |
| 25 | serve "$SB/server" "$H" > "$SB/server.pid" |
| 26 | for _ in $(seq 50); do curl -s "http://$H/.well-known/onset.json" >/dev/null && break; sleep 0.1; done |
| 27 |
| 28 | # The agent's on: Alice on this machine. |
| 29 | cat > "$SB/bin/on" <<EOF |
| 30 | #!/bin/sh |
| 31 | ONSET_HOME="$SB/onsethome" ONSET_SAVER=0 ONSET_NAME=Alice ONSET_EMAIL=alice@acme.test exec "$BIN/on" "\$@" |
| 32 | EOF |
| 33 | chmod +x "$SB/bin/on" |
| 34 | alice() { "$SB/bin/on" "$@" >/dev/null; } |
| 35 | laptop() { ONSET_HOME="$SB/laptop" ONSET_SAVER=0 ONSET_NAME=Alice ONSET_EMAIL=alice@acme.test "$BIN/on" "$@" >/dev/null; } |
| 36 | sam() { ONSET_HOME="$SB/sam" ONSET_SAVER=0 ONSET_NAME=Sam ONSET_EMAIL=sam@acme.test "$BIN/on" "$@" >/dev/null; } |
| 37 | alice login "$H" --token "$(cat "$SB/alice.token")" |
| 38 | laptop login "$H" --token "$(cat "$SB/alice.token")" |
| 39 | sam login "$H" --token "$(cat "$SB/sam.token")" |
| 40 |
| 41 | # acme/shop: Alice owns it, Sam lands too. |
| 42 | cd "$SB" |
| 43 | alice init seed |
| 44 | mkdir -p seed/src |
| 45 | printf '# Shop\n\nWelcom to the shop.\n' > seed/README.md |
| 46 | printf 'export const LIMIT = 10;\n' > seed/src/limits.ts |
| 47 | printf 'export function total(items) {\n return items.reduce((s, i) => s + i.price, 0);\n}\n' > seed/src/cart.ts |
| 48 | printf 'export const stripeKey = process.env.STRIPE_KEY;\n' > seed/src/config.ts |
| 49 | alice -C seed land -m "First version" |
| 50 | alice -C seed publish "$H/acme/shop" |
| 51 | "$BIN/onset-server" member add acme/shop sam --role member --data "$SB/server" |
| 52 | printf 'Sale!\n' > seed/banner.txt |
| 53 | alice -C seed land -m "Add banner" |
| 54 | BANNER=$("$SB/bin/on" --json -C seed log -n 1 | python3 -c 'import json,sys; print(json.load(sys.stdin)["versions"][0]["n"])') |
| 55 | printf 'footer\n' > seed/footer.txt |
| 56 | alice -C seed land -m "Add footer" |
| 57 | rm -rf seed |
| 58 |
| 59 | # T1, T5, T9: the main workspace. |
| 60 | alice get "$H/acme/shop" home/shop |
| 61 | # T14: a workspace from before Sam's change. |
| 62 | alice get "$H/acme/shop" home/shop-old |
| 63 | # T2: a conflict. Alice raised the limit; Sam lands a different one first. |
| 64 | alice get "$H/acme/shop" home/shop-limits |
| 65 | printf 'export const LIMIT = 50;\n' > home/shop-limits/src/limits.ts |
| 66 | alice -C home/shop-limits status |
| 67 | sam get "$H/acme/shop" sam-shop |
| 68 | printf 'export const LIMIT = 20; // Sam: raised for the beta\n' > sam-shop/src/limits.ts |
| 69 | sam -C sam-shop land -m "Raise the limit for the beta" |
| 70 | # T4, T12, T13: plain workspaces with work in them. |
| 71 | alice get "$H/acme/shop" home/shop-notes |
| 72 | alice get "$H/acme/shop" home/shop-docs |
| 73 | printf 'How to run the shop.\n' > home/shop-docs/docs.txt |
| 74 | alice -C home/shop-docs status |
| 75 | alice get "$H/acme/shop" home/shop-junk |
| 76 | printf 'junk\n' > home/shop-junk/junk.txt |
| 77 | alice -C home/shop-junk status |
| 78 | # T6: a change started on the laptop yesterday. |
| 79 | laptop get "$H/acme/shop" laptop-shop |
| 80 | printf 'draft\n' > laptop-shop/draft.txt |
| 81 | laptop -C laptop-shop describe "Draft feature" |
| 82 | 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"])') |
| 83 | # T8: a live key in the config. |
| 84 | alice get "$H/acme/shop" home/shop-config |
| 85 | 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 |
| 86 | alice -C home/shop-config status |
| 87 | # T7: sam/parser is Sam's; Alice can read it but not land. |
| 88 | sam init parser-seed |
| 89 | printf 'parse()\n' > parser-seed/parser.txt |
| 90 | sam -C parser-seed land -m "Parser" |
| 91 | sam -C parser-seed publish "$H/sam/parser" |
| 92 | "$BIN/onset-server" member add sam/parser alice --role reader --data "$SB/server" |
| 93 | alice get "$H/sam/parser" home/parser |
| 94 | printf 'parse() // handles empty input\n' > home/parser/parser.txt |
| 95 | alice -C home/parser status |
| 96 | # T10: a project on a host that's down by the time the agent starts. |
| 97 | H2="127.0.0.1:$((PORT + 1))" |
| 98 | "$BIN/onset-server" user add alice --name Alice --email alice@acme.test --data "$SB/server2" > "$SB/alice2.token" |
| 99 | PID2=$(serve "$SB/server2" "$H2") |
| 100 | for _ in $(seq 50); do curl -s "http://$H2/.well-known/onset.json" >/dev/null && break; sleep 0.1; done |
| 101 | alice login "$H2" --token "$(cat "$SB/alice2.token")" |
| 102 | alice init search-seed |
| 103 | printf 'search v1\n' > search-seed/search.txt |
| 104 | alice -C search-seed land -m "Search" |
| 105 | alice -C search-seed publish "$H2/alice/search" |
| 106 | alice get "$H2/alice/search" home/search |
| 107 | printf 'search v2, faster\n' > home/search/search.txt |
| 108 | alice -C home/search status |
| 109 | kill "$PID2" |
| 110 | # T11: a git repository to bring in. |
| 111 | g() { git -c user.name=Ada -c user.email=ada@example.com -c init.defaultBranch=main -c commit.gpgsign=false "$@"; } |
| 112 | g init -q home/legacy-git |
| 113 | (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") |
| 114 | # T3: src/cart.ts worked two minutes ago, then broke. |
| 115 | alice get "$H/acme/shop" home/shop-cart |
| 116 | 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 |
| 117 | alice -C home/shop-cart status |
| 118 | sleep 130 |
| 119 | printf 'export function total(items) {\n return items.reduce((s, i) => s + i.prce, 0) BROKEN\n}\n' > home/shop-cart/src/cart.ts |
| 120 | printf 'keep me\n' > home/shop-cart/notes.txt |
| 121 | alice -C home/shop-cart status |
| 122 |
| 123 | cat > "$SB/facts" <<EOF |
| 124 | H=$H |
| 125 | H2=$H2 |
| 126 | BANNER=$BANNER |
| 127 | DRAFT=$DRAFT |
| 128 | EOF |
| 129 | echo "sandbox ready: $SB (host $H)" |
| 130 |