255 lines
14 KiB
Bash
Executable file
255 lines
14 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Driver of the cold run «door to file»: one real book, three chapters, zh→ru, from the intake door
|
|
# to a file a human opens. Every step is a subcommand so that a stop rule can stop the run between
|
|
# two of them — the five stops of this pack are a human's call and a ping, never a script's.
|
|
#
|
|
# It refuses rather than warns wherever the refusal is about money or about which code is running:
|
|
# * a binary that is not from the freeze (three conditions, not one: the frozen revision, a clean
|
|
# tree, and the stamp EXISTING — a binary built in a linked git worktree carries no vcs.* lines
|
|
# at all, and a gate that only forbids `vcs.modified=true` passes it every time);
|
|
# * an order whose size is not the pre-registered one;
|
|
# * an order whose bond is unfunded (`term_consistency_funded: false`), which buys a pipeline with
|
|
# its terminology consolidation silently switched off;
|
|
# * a run that has paused for any reason at all.
|
|
#
|
|
# Usage: drive.sh <step> [args] (see `drive.sh help`)
|
|
set -u -o pipefail
|
|
|
|
W=${W:-/home/ubuntu-26/tm-coldrun-a}
|
|
STATE=$W/stand/run-state.env
|
|
[ -f "$STATE" ] && . "$STATE"
|
|
FREEZE_SHA=${FREEZE_SHA:-b0f5d8915c8553993f42e6e3a9d6fbd18adfa68e}
|
|
ADDR=${ADDR:-127.0.0.1:8097}
|
|
DSN=${DSN:-postgres://postgres@/tm_coldrun_door?host=/tmp&port=55433&sslmode=disable}
|
|
PSQL=${PSQL:-$HOME/.local/pgsql/bin/psql}
|
|
JAR=${JAR:-$W/stand/cookies-paid.txt}
|
|
# The pack's money stop, in micro-USD. Reaching it is a STOP and a ping, not a decision of this script.
|
|
STOP_AT_MICRO=${STOP_AT_MICRO:-1000000}
|
|
# Which phase this stand is in, because the SAME fact means opposite things in the two. In the smoke
|
|
# a listener on the local provider's address is the instrument; in the paid phase it is a hazard —
|
|
# a pipeline that still reached a local model would be served fake prose at $0 and the run would
|
|
# measure nothing while looking perfectly green.
|
|
PHASE=${PHASE:-paid}
|
|
export TM_PLATFORM_DSN=${TM_PLATFORM_DSN:-$DSN}
|
|
|
|
say() { printf '%s\n' "$*"; }
|
|
die() { printf '⛔ %s\n' "$*" >&2; exit 1; }
|
|
sql() { "$PSQL" "$DSN" -At -F $'\t' -c "$1"; }
|
|
api() { curl -s --noproxy '*' -b "$JAR" -H 'X-TM-Client: coldrun-driver' "$@"; }
|
|
remember() { printf '%s=%s\n' "$1" "$2" >> "$STATE"; say "remembered $1=$2"; }
|
|
|
|
step_gate() {
|
|
local bad=0
|
|
say "=== binaries: the frozen revision, a clean tree, and a stamp that EXISTS ==="
|
|
for b in tmctl tmplatformd tmplatformctl; do
|
|
local out rev mod n
|
|
out=$(go version -m "$W/bin/$b" 2>/dev/null) || die "$b is not readable"
|
|
n=$(printf '%s\n' "$out" | grep -c 'vcs\.') || true
|
|
rev=$(printf '%s\n' "$out" | sed -n 's/.*vcs\.revision=//p')
|
|
mod=$(printf '%s\n' "$out" | sed -n 's/.*vcs\.modified=//p')
|
|
printf ' %-15s revision=%.12s modified=%s vcs_lines=%s (control: build lines %s)\n' \
|
|
"$b" "${rev:-none}" "${mod:-none}" "$n" "$(printf '%s\n' "$out" | wc -l)"
|
|
[ "$n" -gt 0 ] || { say " ⛔ no vcs.* lines: this gate would be vacuous"; bad=1; }
|
|
[ "$rev" = "$FREEZE_SHA" ] || { say " ⛔ not the frozen revision"; bad=1; }
|
|
[ "$mod" = "false" ] || { say " ⛔ built from a dirty tree"; bad=1; }
|
|
done
|
|
say "=== the stand is MINE: pid on each port, not a 200 from someone else's process ==="
|
|
local line
|
|
line=$(ss -ltnp "sport = :${ADDR##*:}" 2>/dev/null | tail -n +2)
|
|
if [ -n "$line" ]; then
|
|
say " port ${ADDR##*:}: $(printf '%s' "$line" | sed 's/.*users://')"
|
|
else
|
|
say " port ${ADDR##*:}: ⛔ nothing is listening — there is no stand"; bad=1
|
|
fi
|
|
line=$(ss -ltnp "sport = :11434" 2>/dev/null | tail -n +2)
|
|
case "$PHASE" in
|
|
smoke)
|
|
if [ -n "$line" ]; then say " port 11434 (the \$0 stub, required in this phase): $(printf '%s' "$line" | sed 's/.*users://')"
|
|
else say " port 11434: ⛔ the smoke has no stub to answer the local provider"; bad=1; fi ;;
|
|
*)
|
|
if [ -n "$line" ]; then
|
|
say " port 11434: ⛔ something is LISTENING on the local provider's address during a paid phase:"
|
|
say " $(printf '%s' "$line" | sed 's/.*users://')"
|
|
say " a configuration that still reached a local model would be served free prose and the run would measure nothing"
|
|
bad=1
|
|
else
|
|
say " port 11434: nothing listens (control: $(ss -ltn | tail -n +2 | wc -l) listeners on this host) — a local model would now fail LOUDLY"
|
|
fi ;;
|
|
esac
|
|
say "=== what will be bought: the pipeline, its contrast artefact, the template ==="
|
|
# Printed AND checked against the pre-registration when it is given: printing alone leaves the
|
|
# comparison to a reader who has the other number somewhere else.
|
|
local f expect got
|
|
for f in "pipeline-c1.yaml:$W/mirror/cfg/pipeline-c1.yaml:${EXPECT_PIPELINE_SHA:-}" \
|
|
"mining-contrast.zh.txt:$W/mirror/cfg/mining-contrast.zh.txt:${EXPECT_CONTRAST_SHA:-}" \
|
|
"book-template-c1.yaml:$W/book-template-c1.yaml:${EXPECT_TEMPLATE_SHA:-}" \
|
|
"source slice:${SOURCE_FILE:-/dev/null}:${EXPECT_SOURCE_SHA:-}"; do
|
|
local name path
|
|
name=${f%%:*}; path=$(printf '%s' "$f" | cut -d: -f2); expect=${f##*:}
|
|
[ -f "$path" ] || { say " $name: ABSENT at $path"; [ -n "$expect" ] && bad=1; continue; }
|
|
got=$(sha256sum "$path" | cut -d' ' -f1)
|
|
if [ -n "$expect" ] && [ "$expect" != "$got" ]; then
|
|
say " $name: $got ⛔ the pre-registration says $expect"; bad=1
|
|
else
|
|
say " $name: $got${expect:+ (matches the pre-registration)}"
|
|
fi
|
|
done
|
|
cmp -s "$W/mirror/cfg/pipeline-c1.yaml" "$W/freeze/backend/configs/pipeline-c1.yaml" \
|
|
&& say " the pipeline is byte-identical to the freeze's pipeline-c1.yaml" \
|
|
|| { say " ⛔ the pipeline is NOT the freeze's c1"; bad=1; }
|
|
say "=== the freeze tree itself ==="
|
|
say " HEAD $(git -C "$W/freeze" rev-parse HEAD) dirty lines: $(git -C "$W/freeze" status --porcelain | wc -l)"
|
|
[ -e /tmp/.git ] && { say " ⛔ /tmp/.git exists: a build here would lose its stamp"; bad=1; }
|
|
[ "$bad" = 0 ] || die "the gate refused; nothing was bought"
|
|
say "GATE PASSED"
|
|
}
|
|
|
|
step_login() { api -c "$JAR" -X POST "$ADDR/auth/dev-login" -o /dev/null -w 'dev-login HTTP %{http_code}\n'; }
|
|
|
|
step_grant() { # $1 = user id, $2 = usd
|
|
"$W/bin/tmplatformctl" grant --user "$1" --usd "$2" --note "cold run door-to-file"
|
|
"$W/bin/tmplatformctl" balance --user "$1"
|
|
}
|
|
|
|
step_intake() { # $1 = path to the source, $2 = title
|
|
local out id
|
|
out=$(api -F "title=$2" -F 'source_lang=zh' -F 'target_lang=ru' -F "file=@$1" "$ADDR/v0/books")
|
|
say "$out"
|
|
id=$(printf '%s' "$out" | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])') || die "no book id in the answer"
|
|
remember BOOK "$id"
|
|
say "source sha256: $(sha256sum "$1" | cut -d' ' -f1)"
|
|
}
|
|
|
|
step_options() { # refuses unless the size and the bond are what the pre-registration says
|
|
local want_chapters=${1:-3} want_units=${2:-}
|
|
local out
|
|
for _ in $(seq 1 15); do
|
|
out=$(api "$ADDR/v0/books/$BOOK/run-options")
|
|
printf '%s' "$out" | grep -q '"order"' && break
|
|
sleep 3
|
|
done
|
|
printf '%s' "$out" | python3 -m json.tool
|
|
local ch funded
|
|
ch=$(printf '%s' "$out" | python3 -c 'import sys,json;print(json.load(sys.stdin)["order"]["chapters_left"])')
|
|
funded=$(printf '%s' "$out" | python3 -c 'import sys,json;print(json.load(sys.stdin)["order"]["term_consistency_funded"])')
|
|
say "--- the book as the platform materialised it ---"
|
|
sql "select c.number, count(u.id) as units from chapters c left join units u on u.chapter_id=c.id
|
|
where c.book_id='$BOOK' group by 1 order by 1" | sed 's/^/ chapter /'
|
|
local units; units=$(sql "select count(*) from units u join chapters c on c.id=u.chapter_id where c.book_id='$BOOK'")
|
|
say " units_total = $units chapters = $ch"
|
|
[ "$ch" = "$want_chapters" ] || die "the slice is not the pre-registered one: $ch chapters, expected $want_chapters — STOP before paying"
|
|
[ -z "$want_units" ] || [ "$units" = "$want_units" ] || die "units_total is $units, the pre-registration says $want_units — STOP before paying"
|
|
[ "$funded" = "True" ] || die "term_consistency_funded is $funded: the hold does not carry the book bond, and the run would quietly lose its terminology consolidation — STOP before paying"
|
|
say "OPTIONS ACCEPTED: $ch chapters, $units units, the bond is funded"
|
|
}
|
|
|
|
step_start() {
|
|
local out
|
|
out=$(api -H 'Content-Type: application/json' -d '{"stop_for_signing": true, "chapters": 3}' "$ADDR/v0/books/$BOOK/runs")
|
|
say "$out"
|
|
local id; id=$(printf '%s' "$out" | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])') || die "no run id"
|
|
remember RUN "$id"
|
|
}
|
|
|
|
# watch prints the two numbers the pack asks for on two DIFFERENT axes — the engine's own request log
|
|
# and the platform's materialised journal — plus the money and the systemd unit. A growing log is not
|
|
# work, so neither number is read alone.
|
|
step_watch() {
|
|
local dir; dir=$(sql "select workdir from books where id='$BOOK'")
|
|
local rl=0 hits=0 cost=0
|
|
if [ -f "$dir/project.db" ]; then
|
|
read -r rl hits cost <<<"$(python3 - "$dir/project.db" <<'PY'
|
|
import sqlite3,sys
|
|
c=sqlite3.connect(f"file:{sys.argv[1]}?mode=ro",uri=True)
|
|
print(c.execute("select count(*) from request_log").fetchone()[0],
|
|
c.execute("select count(*) from request_log where tm_hit=1").fetchone()[0],
|
|
round(c.execute("select coalesce(sum(cost_usd),0) from request_log").fetchone()[0]*1e6))
|
|
PY
|
|
)"
|
|
fi
|
|
local seq ev st paused
|
|
seq=$(sql "select coalesce(max(last_seq),0) from run_attempts where run_id='$RUN'")
|
|
ev=$(sql "select count(*) from book_events where book_id='$BOOK'")
|
|
st=$(sql "select status from runs where id='$RUN'")
|
|
paused=$(sql "select coalesce(paused_reason,'') from runs where id='$RUN'")
|
|
local unit; unit=$(sql "select unit_name from run_attempts where run_id='$RUN' order by attempt_no desc limit 1")
|
|
local active; active=$(systemctl --user show "$unit.service" -p ActiveState --value 2>/dev/null || echo "unknown")
|
|
say "$(date +%H:%M:%S) alive: request_log=$rl (tm_hit=$hits) · run_attempts.last_seq=$seq · book_events=$ev · status=$st · unit=$active · spend≈${cost}µUSD"
|
|
if [ -n "$paused" ]; then die "paused_reason=$paused — STOP and ping the orchestrator"; fi
|
|
if [ "${cost:-0}" -ge "$STOP_AT_MICRO" ]; then die "spend reached ${cost}µUSD ≥ ${STOP_AT_MICRO}µUSD — STOP and ping the orchestrator"; fi
|
|
}
|
|
|
|
# evidence takes the copy the resume would destroy: `exportBank` is called again at the start of the
|
|
# next attempt and overwrites `.bank.json` atomically, so the proposals a signing screen was opened
|
|
# on survive only as a copy taken here. `.bank-stop.txt` is written NON-atomically (truncate first),
|
|
# so it is only taken while the run stands still.
|
|
step_evidence() { # $1 = label
|
|
local dir; dir=$(sql "select workdir from books where id='$BOOK'")
|
|
local out=$W/evidence/${1:-evidence}
|
|
mkdir -p "$out"
|
|
local st; st=$(sql "select status from runs where id='$RUN'")
|
|
say "run status while the copy is taken: $st"
|
|
[ "$st" = "translating" ] && die "the run is still moving: .bank-stop.txt is truncated before it is rewritten and would be read half-written"
|
|
local n=0
|
|
for f in project.db.bank.json project.db.mined-signature.yaml project.db.bank-stop.txt project.db.auto-bank.yaml project.db.manifest.json events.jsonl; do
|
|
if [ -f "$dir/$f" ]; then cp "$dir/$f" "$out/"; sha256sum "$dir/$f" | sed 's/^/ /'; n=$((n+1)); fi
|
|
done
|
|
say " copied $n artefacts (control: the book directory holds $(ls -1 "$dir" | wc -l) entries)"
|
|
api "$ADDR/v0/books/$BOOK/bank" | head -c 400; echo
|
|
sql "select count(*) all_rows, count(*) filter (where status='approved') approved from bank_terms where book_id='$BOOK'" | sed 's/^/ bank_terms: /'
|
|
}
|
|
|
|
step_sign() { # $1 = json document of corrections
|
|
api -H 'Content-Type: application/json' --data-binary "@$1" "$ADDR/v0/books/$BOOK/bank/corrections" -w '\nHTTP %{http_code}\n'
|
|
}
|
|
|
|
step_resume() { api -H 'Content-Type: application/json' -d '{}' "$ADDR/v0/runs/$RUN/resume" -w '\nHTTP %{http_code}\n'; }
|
|
|
|
step_export() { # $1 = format
|
|
local out id
|
|
out=$(api -H 'Content-Type: application/json' -d "{\"format\":\"$1\"}" "$ADDR/v0/books/$BOOK/exports")
|
|
id=$(printf '%s' "$out" | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])') || die "no export id: $out"
|
|
for _ in $(seq 1 30); do
|
|
sleep 2
|
|
out=$(api "$ADDR/v0/books/$BOOK/exports/$id")
|
|
printf '%s' "$out" | grep -q '"state":"ready"' && break
|
|
printf '%s' "$out" | grep -q '"state":"failed"' && die "the export failed: $out"
|
|
done
|
|
say "$out"
|
|
mkdir -p "$W/evidence/file"
|
|
api "$ADDR/v0/books/$BOOK/exports/$id/content" -o "$W/evidence/file/book.$1" -w "download HTTP %{http_code} bytes=%{size_download}\n"
|
|
say "sha256: $(sha256sum "$W/evidence/file/book.$1")"
|
|
sql "select id,format,state,complete,size_bytes from exports where id='$id'" | sed 's/^/ exports row: /'
|
|
say '⚠ complete is read from Postgres and never from the API: the wire deliberately does not carry it.'
|
|
}
|
|
|
|
step_collect() { # $1 = label
|
|
python3 "$(dirname "$0")/collect.py" --stand "$W" --dsn "$DSN" --book "${BOOK:-}" --run "${RUN:-}" \
|
|
--sha "$FREEZE_SHA" --label "${1:-collect}"
|
|
}
|
|
|
|
step_spread() { # the shipped-text measure the door has never had (unified backlog row 406)
|
|
local dir; dir=$(sql "select workdir from books where id='$BOOK'")
|
|
"$W/bin/tmctl" export --config "$dir/book.yaml" --json --pairs > "$W/evidence/pairs.json" 2>/dev/null \
|
|
|| die "the export verb refused"
|
|
python3 "$(dirname "$0")/spread.py" --pairs "$W/evidence/pairs.json" \
|
|
--bank "$dir/project.db.bank.json" --signature "$dir/project.db.mined-signature.yaml" \
|
|
--json "$W/evidence/spread.json"
|
|
}
|
|
|
|
case "${1:-help}" in
|
|
gate) step_gate ;;
|
|
login) step_login ;;
|
|
grant) shift; step_grant "$@" ;;
|
|
intake) shift; step_intake "$@" ;;
|
|
options) shift; step_options "$@" ;;
|
|
start) step_start ;;
|
|
watch) step_watch ;;
|
|
evidence) shift; step_evidence "$@" ;;
|
|
sign) shift; step_sign "$@" ;;
|
|
resume) step_resume ;;
|
|
export) shift; step_export "$@" ;;
|
|
collect) shift; step_collect "$@" ;;
|
|
spread) step_spread ;;
|
|
help|*) sed -n '1,20p' "$0" ;;
|
|
esac
|