Project-2 baseline

This commit is contained in:
2026-10-04 21:19:23 -04:00
parent 0f04889edf
commit 213eb3c403
861 changed files with 1710 additions and 3363322 deletions

View File

@@ -35,7 +35,7 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential git curl ca-certificates gnupg procps sudo xz-utils \
libssl-dev zlib1g-dev \
postgresql postgresql-client \
postgresql postgresql-client ffmpeg \
&& rm -rf /var/lib/apt/lists/*
# --- Node via nvm: 14 / 16 / 18 / 20 (prebuilt). Default 20 symlinked to /usr/local/bin so the

View File

@@ -4,7 +4,7 @@
"build": {
"dockerfile": "Dockerfile",
"args": {
"TOOLKIT_BUILD_ID": "1789430760274-eddbpv"
"TOOLKIT_BUILD_ID": "1790712369311-8xr6mu"
}
},
"appPort": [

View File

@@ -76,7 +76,10 @@ dnsjail_apply() {
drop_ours
# cache-size=0: every lookup goes upstream, so a jailed container sees what an unjailed
# one would rather than an answer this resolver decided to keep.
dnsmasq --no-resolv --no-hosts --listen-address=127.0.0.1 --bind-interfaces \
# -u root: dnsmasq 2.80 (buster and older bases) drops to "nobody" and calls capset to
# retain CAP_NET_ADMIN, which docker's default cap set does not grant -- so it exits and
# the jail fails open on every such image.
dnsmasq -u root --no-resolv --no-hosts --listen-address=127.0.0.1 --bind-interfaces \
--cache-size=0 --pid-file="$STATE/dnsmasq.pid" --address=/#/ $srv \
>/dev/null 2>>"$STATE/dnsmasq.err" || true
fi

View File

@@ -156,6 +156,8 @@ if (!instance)
const tk = JSON.parse(fs.readFileSync('toolkit.json', 'utf-8'));
expected = tk.explorePorts && tk.explorePorts.serverHost ? [3000, 3001] : [3000];
if (tk.explorePorts && tk.explorePorts.corpusHost) expected.push(3002);
if (tk.explorePorts && tk.explorePorts.companionHost)
expected.push(tk.explorePorts.companionHost);
} catch {}
const folder = process.cwd();

View File

@@ -44,6 +44,34 @@ _nm_link() {
ln -sfn "$root/node_modules" node_modules
}
# g++ peaks near 400MiB on this codebase's biggest translation units, and nproc reports the
# HOST's core count, so a many-core laptop with a small Docker VM OOMs mid-build. Bound the
# job count by whichever of the VM's memory and the cgroup cap is smaller.
_frepple_jobs() {
local n mem cg j
n=$(nproc)
mem=$(awk '/^MemTotal:/{print $2*1024}' /proc/meminfo)
cg=$(cat /sys/fs/cgroup/memory.max 2>/dev/null \
|| cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null || echo)
case "$cg" in ''|max|*[!0-9]*) ;; *) [ "$cg" -lt "$mem" ] && mem=$cg ;; esac
j=$(( mem / 734003200 ))
[ "$j" -lt 1 ] && j=1
[ "$j" -gt "$n" ] && j=$n
echo "$j"
}
# Docker Desktop's macOS bind mount can write a compiled wheel with the right length and
# the wrong bytes, so the venv imports die on a signal (132/135/139) rather than an error.
# A reinstall lands the authentic file; a Django-level failure exits 1 and is not retried.
_frepple_migrate() {
local rc=0
./frepplectl.py migrate --noinput || rc=$?
if [ "$rc" -le 128 ]; then return "$rc"; fi
echo "venv extension died on signal $rc — reinstalling requirements and retrying" >&2
python3 -m pip install --force-reinstall --no-cache-dir -r requirements.txt -q
./frepplectl.py migrate --noinput
}
case "$REPO_NAME" in
ZenBill-006)
# Install deps + create databases
@@ -313,6 +341,70 @@ case "$REPO_NAME" in
&& _nm_link flaredown-frontend \
&& (npm install --unsafe-perm --no-audit --no-fund || echo "WARNING: frontend npm install failed (explore-only)" >&2) ) || true
;;
frepple)
# The minified JS the app serves is tracked, so pnpm+grunt are for a worker who
# edits frontend source, not a prerequisite. The cmake build creates venv/ and
# pip-installs into it. odoo_addon is pinned, NOT --remote like upstream CI.
# DEBUG_JS=DEBUG, and DEBUG is true under runserver, which points the two Vue
# screens at a Vite dev server on :5173 that nothing starts. FREPPLE_PORT is where
# the BROWSER posts forecast saves, so the service has to bind 0.0.0.0 to be
# reachable. localsettings.py is upstream's own gitignored override hook.
# `demo` alone holds no forecasts, which leaves the forecast screens empty; adding
# distribution_demo and planning it reproduces upstream's own scenario1 content.
# The `doc` target is not in `all`, so without it the Help menu and the help icon on
# 89 report screens 404; its own symlink is absolute, so relink it relatively or the
# host sees a dangling link into the container's /workspace.
( cd /workspace/repo \
&& git submodule update --init freppledb/odoo/odoo_addon \
&& pnpm install --frozen-lockfile \
&& grunt \
&& cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
&& cmake --build build --parallel "$(_frepple_jobs)" \
&& { cmake --build build --target doc \
&& ln -sfn ../../../build/doc/_build/html freppledb/common/static/doc \
|| echo "NOTE: the docs did not build; in-app help links will 404" >&2; } \
&& printf 'DEBUG_JS = False\nfor _a in DATABASES:\n DATABASES[_a]["FREPPLE_PORT"] = DATABASES[_a]["FREPPLE_PORT"].replace("127.0.0.1:", "0.0.0.0:")\n' \
> localsettings.py \
&& _frepple_migrate \
&& ./frepplectl.py loaddata demo \
&& ./frepplectl.py loaddata distribution_demo \
&& ./frepplectl.py runplan --env=fcst,supply --background \
&& ./frepplectl.py shell -c "
from django.contrib.auth import get_user_model
U = get_user_model()
u, _ = U.objects.get_or_create(username='admin', defaults={'email': 'admin@example.com'})
u.is_superuser = True; u.is_staff = True; u.set_password('frepple'); u.save()
print('admin user ready')
" ) \
|| { echo "FATAL: frepple setup did not complete — the app would not serve." >&2; exit 1; }
;;
freeitsm)
# Plain PHP / Apache, no composer. config.php is left exactly as upstream tracks it
# (the image puts its Windows-path require on include_path); db_config.php is the
# doc-root stub the test scripts require, excluded locally so git status stays clean.
# Apache writes attachments and imports as www-data, but a bind mount can force those
# dirs root-owned and swallow the chown, so the directory mode is what has to give.
( cd /workspace/repo \
&& cp docker/db_config.php db_config.php \
&& _fi_ex="$(git rev-parse --git-path info/exclude)" \
&& mkdir -p "$(dirname "$_fi_ex")" \
&& { grep -qxF 'db_config.php' "$_fi_ex" 2>/dev/null \
|| echo 'db_config.php' >> "$_fi_ex"; } \
&& for _fi_d in tickets/attachments change-management/attachments \
uploads/asset-imports uploads/documents; do \
mkdir -p "$_fi_d"; \
chown -R www-data:www-data "$_fi_d" 2>/dev/null || true; \
find "$_fi_d" -type d -exec chmod a+rwx {} +; \
done \
&& if [ "$(mysql -u root -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='freeitsm'" 2>/dev/null || echo 0)" -lt 10 ]; then
mysql -u root freeitsm < database/freeitsm.sql \
|| { echo "FATAL: could not load database/freeitsm.sql — is the freeitsm database present?" >&2; exit 1; }
fi \
&& apache2ctl -k start 2>/dev/null \
&& /usr/local/bin/freeitsm-seed.sh \
&& apache2ctl -k stop 2>/dev/null ) \
|| { echo "FATAL: freeitsm setup did not complete — the app would not log in." >&2; exit 1; }
;;
breezy-complete)
# Monorepo: Rails 7.0 / Ruby 3.2.0 API (backend/) + Next.js 14 frontend
# (frontend/); Postgres + Redis baked in the image. The offline Clerk-bypass
@@ -389,6 +481,16 @@ CALL_METADATA="$CALL_METADATA" node -e '
`${home}/.claude/settings.json`,
JSON.stringify({ env }, null, 2) + "\n"
);
// Onboarding preflights api.anthropic.com + platform.claude.com, neither from
// ANTHROPIC_BASE_URL, and exits 1 unresolved, so a jailed container never records it done.
const cfgPath = `${home}/.claude.json`;
let cfg = {};
try {
cfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
} catch {}
cfg.hasCompletedOnboarding = true;
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
'
# Reference-data corpus: expose it at the stable /data/zeta-corpus path (the same path a trial
@@ -426,8 +528,8 @@ bash /workspace/welcome.sh explore 2>/dev/null
_AK="fde503c3bdb6e5cc9c48b1f8e4c2abeb"
_DK="e966e45af5ad1a18005f9fdb831186ea"
_WID="w-mu1wvj9k-gtnk"
_VER="037cfcf94b"
_WID="w-mun3wr6n-v83f"
_VER="1.0.0"
_CT="explore"
_RP=$(node -e "try{process.stdout.write(require('/workspace/toolkit.json').repo)}catch{}" 2>/dev/null)
_SID="$(date +%s)-$$"

View File

@@ -209,6 +209,20 @@ case "$REPO_NAME" in
wait_for_pg
for _ in $(seq 1 60); do mongo_up && break; sleep 0.5; done
;;
frepple)
# Provisioned at image build time (the CLI overrides ENTRYPOINT); just start it.
pg_ctlcluster "$(ls /etc/postgresql | head -1)" main start 2>/dev/null || true
for i in $(seq 1 60); do pg_isready -h 127.0.0.1 -q && break; sleep 0.5; done
;;
freeitsm)
# MySQL 8 (Percona). The database and app user are created at image BUILD time —
# the devcontainer CLI overrides ENTRYPOINT, so nothing there would run.
if ! mysqladmin ping >/dev/null 2>&1; then
sudo mkdir -p /var/run/mysqld && sudo chown -R mysql:mysql /var/run/mysqld /var/lib/mysql 2>/dev/null || true
sudo service mysql start >/dev/null 2>&1 || (sudo mysqld_safe --user=mysql >/dev/null 2>&1 &) || echo "warning: mysql start failed" >&2
fi
for i in $(seq 1 120); do mysqladmin ping >/dev/null 2>&1 && break; sleep 0.5; done
;;
breezy-complete)
# Postgres + Redis (Sidekiq). Start both; wait_for_pg is the gate. Trust
# auth (set in the image) — PGPASSWORD is baked but inert, no role seeding.

View File

@@ -177,6 +177,12 @@ async function create(name) {
// so a collision would silently point the client's livereload at the other container.
if (ports.livereloadHost)
env.EXPLORE_LIVERELOAD_PORT = String(await freePort(ports.livereloadHost + 10));
// Above clientPort, not just near its own base: freePort releases each probe before it
// resolves, so two independent calls can hand back the same number.
if (ports.companionHost)
env.EXPLORE_COMPANION_PORT = String(
await freePort(Math.max(Number(ports.companionHost) + 10, clientPort + 1))
);
up(name, env);
reportUp(name, tk);
}

View File

@@ -652,6 +652,13 @@ if (gitRepo) {
git('read-tree HEAD', { env: indexEnv });
git('add -A', { env: indexEnv });
patch = git('diff --cached --binary --full-index HEAD', diffOpts);
if (patch) {
console.error(
'Warning: no turn checkpoint was found, so snapshot.patch holds every change in the ' +
'working tree, including edits the agent made during the captured turn. Check it ' +
'and remove those before converting the snapshot to a task.'
);
}
}
try {

View File

@@ -86,3 +86,14 @@ never a numeric magnitude, never points, never a cap or pinned score: the
grader sizes the subtraction itself. Always state the behavior that does NOT trip the penalty.
Never describe how criteria combine into an overall score.>
`;
/** The title names the task, not its author: a leading `<EUID>-` (the worker's 12-char id,
* which workers put on their task dir) is dropped. Anything else is used as given. */
export function rubricTitleSlug(slug: string): string {
return slug.replace(/^(?=[A-Z0-9]*\d)[A-Z0-9]{12}-(?=\S)/, '');
}
/** The scaffold with `<task-slug>` filled in for a task whose name is already known. */
export function holisticRubricScaffoldFor(slug: string): string {
return HOLISTIC_RUBRIC_SCAFFOLD.replace('<task-slug>', rubricTitleSlug(slug));
}

View File

@@ -2,4 +2,4 @@
* Plugin-side re-export, so snapshot-to-task.ts resolves `./lib/copy-tree`
* both here and in the toolkit's flat scripts/ dir.
*/
export * from '../../../../raccoon-worker-toolkit/static/scripts/lib/copy-tree';
export * from '../../../../static/scripts/lib/copy-tree';

View File

@@ -24,7 +24,7 @@ import { hideBin } from 'yargs/helpers';
import { stripAuthoringScaffolding, truncationIndex, turnsFromLines } from './harness-session.mjs';
// This script must not call cpSync — it fails EACCES on a macOS docker bind mount.
import { HOLISTIC_RUBRIC_SCAFFOLD } from './holistic-rubric-scaffold';
import { holisticRubricScaffoldFor } from './holistic-rubric-scaffold';
import { copyTree } from './lib/copy-tree';
import { collectCwds, sanitizeSessionJsonl } from './sanitize-session-jsonl';
@@ -234,6 +234,7 @@ mkdirSync(join(taskDir, 'reference-runs'), { recursive: true });
// task-shared/ are skipped by the existsSync guard below.
const sharedFiles = [
{ src: 'test.sh', dest: 'tests/test.sh' },
{ src: 'codex-grader.py', dest: 'tests/codex-grader.py' },
{
src: 'grader-system-prompt-consolidated.md',
dest: 'tests/grader-system-prompt-consolidated.md',
@@ -663,6 +664,7 @@ gpus = 0
allow_internet = true
[verifier.env]
GRADER_HARNESS = "codex"
ANTHROPIC_API_KEY = "\${ANTHROPIC_API_KEY}"
ANTHROPIC_BASE_URL = "\${ANTHROPIC_BASE_URL}"
@@ -764,7 +766,7 @@ const holisticRubricMd = `<!--
when you are done.
-->
${HOLISTIC_RUBRIC_SCAFFOLD}`;
${holisticRubricScaffoldFor(slug)}`;
writeFileSync(join(taskDir, 'tests', 'holistic-rubric.md'), holisticRubricMd);
log.info('Scaffolded tests/holistic-rubric.md (needs manual editing)');

View File

@@ -1 +0,0 @@
/home/eric/workspaces/dataannotation/current-project/worker-toolkit-potion-polyglot/repos

View File

@@ -29,6 +29,9 @@ REPO_NAME=$(node -e "try{process.stdout.write(require('/workspace/toolkit.json')
# $EXPLORE_CLIENT_PORT; prefer it, falling back to toolkit.json then 3000 for
# older containers built before this var existed.
CLIENT_HOST_PORT="${EXPLORE_CLIENT_PORT:-$(node -e "try{process.stdout.write(String(require('/workspace/toolkit.json').explorePorts.clientHost))}catch{process.stdout.write('3000')}" 2>/dev/null || echo 3000)}"
# Companion services publish host:container IDENTICAL (see package-worker-toolkit), so this one
# value is both what the service binds and what the browser reaches.
COMPANION_PORT="${EXPLORE_COMPANION_PORT:-$(node -e "try{process.stdout.write(String(require('/workspace/toolkit.json').explorePorts.companionHost||4201))}catch{process.stdout.write('4201')}" 2>/dev/null || echo 4201)}"
# --- process helpers ---------------------------------------------------------
@@ -82,6 +85,10 @@ stop_app() {
_kill_pidfile "$pf"
stopped=1
done
# frePPLe's planning engine daemonizes itself, so no pidfile covers it.
if [ "${REPO_NAME:-}" = "frepple" ] && [ -x /workspace/repo/frepplectl.py ]; then
( cd /workspace/repo && ./frepplectl.py stopwebservice ) >/dev/null 2>&1 || true
fi
if [ "$stopped" = 1 ]; then printf "${GRAY}Stopped the app.${RESET}\n"; else printf "${GRAY}Nothing to stop.${RESET}\n"; fi
}
@@ -500,17 +507,20 @@ setup_repo() {
printf " ${GRAY}first-time setup for %s (%s) \xe2\x80\x94 runs once\xe2\x80\xa6${RESET}\n" "$repo" "${runtime:-explore-only}"
case "$kind" in
elixir)
# asdf-only (no rbenv/nvm estate has elixir). Version comes from the member's
# .tool-versions; shims are already on PATH. deps + a MIX_ENV=test compile so the
# Version comes from the member's .tool-versions where asdf manages it, else from
# the image. dev.secret.exs is seeded BEFORE any mix task: every task evaluates
# config/<env>.exs, so a member whose config imports it can't even run local.hex
# without it. deps + a MIX_ENV=test compile so the
# suite is warm and compile errors surface at setup, not mid-explore. bootEnv covers
# any compile-time env a member reads (e.g. epihub's ZOOM_* module attributes). DB/ecto
# prep is member-specific → leave it to setupCmd; a worker runs `mix test` with it.
( cd "$dir" \
&& for kv in $bootenv; do export "$kv"; done \
&& mix local.hex --force >/dev/null 2>&1 \
&& mix local.rebar --force >/dev/null 2>&1 \
&& { [ -f config/dev.secret.exs.example ] && [ ! -f config/dev.secret.exs ] && cp config/dev.secret.exs.example config/dev.secret.exs; true; } \
&& mix deps.get \
&& { mix local.hex --force >/dev/null 2>&1 \
|| printf " ${GRAY}(hex not refreshed \xe2\x80\x94 using the image's copy)${RESET}\n"; } \
&& { mix local.rebar --force >/dev/null 2>&1 || true; } \
&& mix deps.get </dev/null \
&& MIX_ENV=test mix compile ) || return 1 ;;
ruby)
if _asdf_ok; then
@@ -855,19 +865,61 @@ start_poly() {
cmd="env $bootenv CARGO_TARGET_DIR=/opt/raccoon-cargo-target/$repo $startcmd" ;;
*) printf "${YELLOW}runtime '%s' for %s isn't runnable here \xe2\x80\x94 explore-only.${RESET}\n" "$runtime" "$repo"; return 0 ;;
esac
# Companion services a member cannot work without. strongsuit-app is a front end to the
# strongsuit_phx Phoenix backend, and it does not degrade when that is absent:
# getImportantDateRecommendations rethrows the connection failure and none of its four
# callers catch it, so those pages throw instead of rendering an empty list.
case "$repo" in
strongsuit-app)
local phx_dir=/workspace/repos/strongsuit_phx
# Every absolute link the app renders is built from DOMAIN, so it has to carry the
# port the worker's browser reaches — which instance.js moves per named instance.
cmd="env DOMAIN=http://localhost:$CLIENT_HOST_PORT PHX_DOMAIN=localhost:$COMPANION_PORT $cmd"
# Members are set up lazily, and nothing else ever runs setup_repo for a member that
# is never itself the target. After strongsuit-app's own setup, so the database it
# creates and seeds exists before phx's migration baseline and seed read it.
setup_repo strongsuit_phx \
|| printf " ${YELLOW}\xe2\x9a\xa0 strongsuit_phx setup failed \xe2\x80\x94 ${RESET}${GRAY}run-app --logs${RESET}\n"
# The DB-dependent half runs on EVERY boot, not once: setup_repo's marker is set even
# when its work was skipped, so a worker who ran `run-app strongsuit_phx` first (no
# database yet) would otherwise be stuck with phx 503ing on unbaselined migrations.
# Both scripts are idempotent and cheap once prepared.
( bash /workspace/scripts/seed/setup-strongsuit-phx.sh "$phx_dir" \
&& bash /workspace/scripts/seed/seed-important-date-recommendations.sh ) \
>> "$RUN_DIR/setup-strongsuit_phx.log" 2>&1 \
|| printf " ${YELLOW}\xe2\x9a\xa0 strongsuit_phx database prep failed \xe2\x80\x94 ${RESET}${GRAY}run-app --logs${RESET}\n"
if [ -d "$phx_dir/deps" ] && [ -d "$phx_dir/_build" ]; then
printf " ${CYAN}\xe2\x96\xb6${RESET} starting strongsuit_phx (elixir)\xe2\x80\xa6\n"
# Derived, not a second env var: the app sends PHX_AUTH_TOKEN and the phx plug
# compares against API_AUTH_TOKEN, and a drift shows up only as silent 401s.
_spawn phx "$phx_dir" "env MIX_ENV=dev PHX_PORT=$COMPANION_PORT API_AUTH_TOKEN=${PHX_AUTH_TOKEN:-dummy} mix phx.server"
_wait_tcp "$COMPANION_PORT" 45 || printf " ${YELLOW}\xe2\x9a\xa0 strongsuit_phx didn't come up \xe2\x80\x94 ${RESET}${GRAY}run-app --logs${RESET}\n"
else
printf " ${GRAY}strongsuit_phx isn't built \xe2\x80\x94 the backend on :%s will be absent.\n" "$COMPANION_PORT"
printf " build it: ${RESET}${GRAY}bash /workspace/scripts/seed/setup-strongsuit-phx.sh${RESET}\n"
fi ;;
esac
printf " ${CYAN}\xe2\x96\xb6${RESET} starting %s (%s)\xe2\x80\xa6\n" "$repo" "$runtime"
_spawn app "$dir" "$cmd"
if _wait_tcp 3000; then
printf " ${CYAN}\xe2\x9c\x85 %s is up${RESET} open ${CYAN}http://localhost:%s${RESET}\n" "$repo" "$CLIENT_HOST_PORT"
# Members whose usable entry point isn't "/". strongsuit-app's is the local dev-login
# route: "/" redirects to a hosted Auth0 tenant that can't be reached offline, so the
# URL printed here — the one a terminal makes clickable — has to be the one that works.
local landing=""
case "$repo" in
strongsuit-app) landing="/dev-login" ;;
esac
printf " ${CYAN}\xe2\x9c\x85 %s is up${RESET} open ${CYAN}http://localhost:%s%s${RESET}\n" "$repo" "$CLIENT_HOST_PORT" "$landing"
# Per-member "how do I actually get in" notes. Only members whose landing page needs
# more than the URL need an entry here (e.g. an app whose real sign-in is a hosted
# third-party login that can't be reached offline).
case "$repo" in
strongsuit-app)
printf " ${GRAY}Sign-in normally goes through a hosted Auth0 page, which isn't reachable\n"
printf " offline, so this app ships a local-only dev-login route. Open\n"
printf " ${RESET}${CYAN}http://localhost:%s/dev-login${RESET}${GRAY} to sign in as a seeded admin\n" "$CLIENT_HOST_PORT"
printf " (${RESET}${GRAY}?role=MSS${RESET}${GRAY} or ${RESET}${GRAY}?role=MEMBER${RESET}${GRAY} for the other roles). The DB was seeded during setup.${RESET}\n"
printf " offline; the link above is a local-only dev-login route that signs you in\n"
printf " as a seeded admin (${RESET}${GRAY}?role=MSS${RESET}${GRAY} or ${RESET}${GRAY}?role=MEMBER${RESET}${GRAY} for the other roles).\n"
printf " The DB was seeded during setup. Plain ${RESET}${GRAY}http://localhost:%s/${RESET}${GRAY} redirects to\n" "$CLIENT_HOST_PORT"
printf " Auth0 and cannot work offline.${RESET}\n"
;;
ABDM-FE)
printf " ${GRAY}This app is served under a ${RESET}${GRAY}/app${RESET}${GRAY} basename, so the bare URL above renders\n"
@@ -902,6 +954,53 @@ start_poly() {
# passes the demo login to print. Optional $2 is a one-line note printed above the
# login (e.g. a subdomain caveat).
# start_rails <login-hint> [url-note]
start_frepple() {
# Django dev server; the C++ engine is already built in-tree by post-create.
_spawn app /workspace/repo "./frepplectl.py runserver 0.0.0.0:3000"
printf " ${YELLOW}\xe2\x96\xb6${RESET} starting the frePPLe web app (Django)\xe2\x80\xa6\n"
printf " ${GRAY}\xe2\x8f\xb3 waiting for the app to come up\xe2\x80\xa6${RESET}\n"
if _wait_tcp 3000; then
printf " ${YELLOW}\xe2\x9c\x85 app is up${RESET}\n"
# Django serves plan data read-only; the forecast editor and the plan screens save by
# POSTing from the browser to the engine's web service on 8002. Django starts it on
# first login, so start it here instead and the first save can't race the plan load.
printf " ${YELLOW}\xe2\x96\xb6${RESET} loading the plan into the planning engine\xe2\x80\xa6\n"
( cd /workspace/repo && ./frepplectl.py runwebservice --daemon --forcerestart \
>>"$RUN_DIR/webservice.log" 2>&1 ) || true
if _wait_tcp 8002 300; then
printf " ${YELLOW}\xe2\x9c\x85 planning engine is up${RESET}\n"
else
printf " ${RED}\xe2\x9a\xa0 the planning engine didn't come up \xe2\x80\x94 editing a forecast won't save${RESET}\n"
printf " check the logs: ${GRAY}%s/webservice.log${RESET}\n" "$RUN_DIR"
fi
printf " open ${YELLOW}http://localhost:%s${RESET}\n" "$CLIENT_HOST_PORT"
printf " login ${GRAY}admin / frepple${RESET}\n"
else
printf " ${RED}\xe2\x9a\xa0 the app didn't come up in time${RESET}\n"
printf " check the logs: ${GRAY}run-app --logs${RESET}\n"
fi
printf " logs ${GRAY}%s/app.log${RESET}\n" "$RUN_DIR"
printf " stop ${GRAY}run-app --stop${RESET}\n"
}
start_freeitsm() {
# Plain PHP behind Apache — no dev server. -DFOREGROUND keeps it in _spawn's
# process group so --logs and --stop behave like every other repo.
_spawn app /workspace/repo "apache2ctl -DFOREGROUND"
printf " ${YELLOW}\xe2\x96\xb6${RESET} starting Apache (mod_php)\xe2\x80\xa6\n"
printf " ${GRAY}\xe2\x8f\xb3 waiting for the app to come up\xe2\x80\xa6${RESET}\n"
if _wait_tcp 3000; then
printf " ${YELLOW}\xe2\x9c\x85 app is up${RESET}\n"
printf " open ${YELLOW}http://localhost:%s${RESET}\n" "$CLIENT_HOST_PORT"
printf " login ${GRAY}admin / freeitsm123 (staff sign-in; setup already cleared the forced change)${RESET}\n"
else
printf " ${RED}\xe2\x9a\xa0 the app didn't come up in time${RESET}\n"
printf " check the logs: ${GRAY}run-app --logs${RESET}\n"
fi
printf " logs ${GRAY}%s/app.log${RESET}\n" "$RUN_DIR"
printf " stop ${GRAY}run-app --stop${RESET}\n"
}
start_rails() {
local login_hint="${1:-}" url_note="${2:-}"
_spawn app /workspace/repo "bin/rails server -b 0.0.0.0 -p 3000"
@@ -952,6 +1051,8 @@ start_app() {
# welcome page. No login to print. The app grows over time.
start_rails "" "young app — no routes defined yet, so this shows the default Rails welcome page" ;;
breezy-complete) start_breezy_complete ;;
frepple) start_frepple ;;
freeitsm) start_freeitsm ;;
*)
printf "${YELLOW}run-app isn't configured for repo '%s'.${RESET}\n" "${REPO_NAME:-unknown}"
printf "Start the app with the project's own dev command from ${GRAY}/workspace/repo${RESET}.\n"

View File

@@ -53,6 +53,38 @@ _harness_trim() {
printf '%s' "${out:-$1}"
}
# Every env var a harness authenticates from, registry-derived so a new harness row is
# covered without touching this. ANTHROPIC_* unconditionally: it is what .env carries and
# what harbor-run hands the trial sandbox, registry or not.
_harness_credential_vars() {
local id key_env base_url_env proxy_path
printf '%s\n' ANTHROPIC_API_KEY ANTHROPIC_BASE_URL
while IFS=$'\t' read -r id key_env base_url_env proxy_path; do
if [ -n "$key_env" ]; then printf '%s\n' "$key_env"; fi
if [ -n "$base_url_env" ]; then printf '%s\n' "$base_url_env"; fi
done < <(_harness_query --authoring-credentials 2>/dev/null || true)
}
# Source .env into the CALLER's environment and trim what a harness reads its key from.
# For codex the live value is now the env var, not the auth file harness_write_auth
# cleans, so a raw `set -a; . .env` is the 401 all over again on a Windows-saved file.
harness_load_env() {
local file="${1:-${RACCOON_ENV_FILE:-/workspace/.env}}" v
if [ -f "$file" ]; then
set -a
# shellcheck disable=SC1090
. "$file" 2>/dev/null || true
set +a
fi
# Trimming twice is a no-op, so a var named by several rows needs no dedupe.
while read -r v; do
[ -n "$v" ] || continue
if [ -n "${!v:-}" ]; then
export "$v=$(_harness_trim "${!v}")"
fi
done < <(_harness_credential_vars)
}
# The proxy root: the worker's ANTHROPIC_BASE_URL minus its provider path.
_harness_proxy_root() {
local base_url

View File

@@ -40,7 +40,12 @@ _scripts_dir="${HARNESS_SCRIPTS_DIR:-/workspace/scripts}"
# .bashrc (the key, the call origin) nor the profile that puts the CLI on PATH.
# Failures stay swallowed — an unreadable .env must not stop the agent starting.
export PATH="$HOME/.local/bin:$PATH"
if [ -f "${RACCOON_ENV_FILE:-/workspace/.env}" ]; then
# shellcheck disable=SC1091
HARNESS_SCRIPTS_DIR="$_scripts_dir" . "$_scripts_dir/lib/harness-credentials.sh" 2>/dev/null || true
if command -v harness_load_env >/dev/null 2>&1; then
harness_load_env || true
elif [ -f "${RACCOON_ENV_FILE:-/workspace/.env}" ]; then
# Untrimmed, but a key with a stray \r beats no key at all.
set -a
# shellcheck disable=SC1090
. "${RACCOON_ENV_FILE:-/workspace/.env}" 2>/dev/null || true

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env bash
# Give the personalization surface something to work on, offline.
#
# important_date_recommendation rows are what the member and MSS home pages count and what
# /member/important-date-recommendations lists. In production the Phoenix NewAccountWorker
# produces them from a member's Cronofy calendar plus an LLM call — neither reachable offline,
# so the table stays empty however long the app runs and the feature looks broken when it is
# only unfed. This synthesizes the same rows from the seed's own contacts and their birthdays.
# cronofy_event_id is left null: the column is nullable with no foreign key, only the Ecto
# changeset requires it, and the read path preloads it to nil.
#
# Runs inside the Explore container, from run-app's strongsuit-app companion block:
#
# bash /workspace/scripts/seed/seed-important-date-recommendations.sh [database]
set -euo pipefail
DB="${1:-${PGDATABASE:-strongsuit}}"
# The app's own setup creates and seeds this database; without it there is nothing to read.
if ! PGPASSWORD="${PGPASSWORD:-postgres}" psql -h "${PGHOST:-localhost}" -U "${PGUSER:-postgres}" \
-d "$DB" -tAc "select 1 from important_date_recommendation limit 1" >/dev/null 2>&1; then
echo " database \"$DB\" has no app schema yet — nothing to seed."
exit 0
fi
PGPASSWORD="${PGPASSWORD:-postgres}" psql -h "${PGHOST:-localhost}" -U "${PGUSER:-postgres}" \
-d "$DB" -v ON_ERROR_STOP=1 <<'SQL'
with member_family as (
select u.id as user_id, u.person_id, fu.family_id
from "user" u
join family_user fu on fu.user_id = u.id
where u.role = 'MEMBER' and u.deleted_at is null
),
-- A family's contacts hang off its principal person, who is often NOT a user: the seeded member
-- user is a spouse, and the birthdays sit on the principal's relationships. So expand one hop
-- (either direction) from the member's own person before reading contacts off person1, which is
-- the direction app/models/person.server.ts queries.
member_people as (
select user_id, family_id, person_id from member_family
union
select mf.user_id, mf.family_id,
case when r.person1_id = mf.person_id then r.person2_id else r.person1_id end
from member_family mf
join relationship r
on (r.person1_id = mf.person_id or r.person2_id = mf.person_id)
and r.deleted_at is null
)
insert into important_date_recommendation
(id, important_date_type, date, member_user_id, cronofy_event_id,
created_at, updated_at, status, first_name, last_name, family_id)
select
-- family_id is part of the key: a member in two families gets one row per family. The guard
-- below keys on names rather than d.id, so it is the coarser of the two.
'seed_idr_' || substr(md5(mf.user_id || p2.id || d.id || coalesce(mf.family_id, '')), 1, 20),
lower(d.type::text),
occ.occurs_on + time '09:00',
mf.user_id,
null,
now(), now(), 'pending',
p2.first_name, p2.last_name,
mf.family_id
from member_family mf
join member_people mp on mp.user_id = mf.user_id and mp.family_id = mf.family_id
join relationship r on r.person1_id = mp.person_id and r.deleted_at is null
join person p2 on p2.id = r.person2_id
join important_date d on d.person_id = p2.id and d.type in ('BIRTHDAY', 'ANNIVERSARY')
and d.deleted_at is null
-- The next occurrence, so the list reads as something upcoming rather than a set of
-- anniversaries that all fell in 1970. The day is clamped to the last of its month because a
-- Feb-29 date has no counterpart in a common year and make_date() raises rather than rounding,
-- which under ON_ERROR_STOP would abort the whole seed rather than skip one row.
cross join lateral (
select occurs_on from (
select make_date(gs.yr, d.month,
least(d.day,
extract(day from (make_date(gs.yr, d.month, 1)
+ interval '1 month' - interval '1 day'))::int)) as occurs_on
from generate_series(extract(year from current_date)::int,
extract(year from current_date)::int + 1) as gs(yr)
) c
where c.occurs_on >= current_date
order by c.occurs_on
limit 1
) occ
where p2.id <> mf.person_id
-- Skips any member/family/person/date-type that already has a recommendation, including ones
-- the member has since accepted or declined; `on conflict` covers rows an earlier run wrote.
and not exists (
select 1 from important_date_recommendation x
where x.member_user_id = mf.user_id
and x.family_id is not distinct from mf.family_id
and x.important_date_type = lower(d.type::text)
and x.first_name is not distinct from p2.first_name
and x.last_name is not distinct from p2.last_name
)
on conflict (id) do nothing;
select count(*) as pending_recommendations
from important_date_recommendation where status = 'pending';
SQL

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env bash
# Prepare strongsuit_phx to run against the same database the Remix app uses.
#
# Three things stand between a checkout and a working service, none of them a code change:
#
# 1. config/dev.exs ends with `import_config "dev.secret.exs"`, so mix won't boot without that
# gitignored file. run-app's elixir setup seeds it from dev.secret.exs.example, which carries
# no Repo override — so this script OVERWRITES rather than skipping when it already exists.
# 2. dev.exs points Ecto at the database "postgres", but the two services share ONE database and
# the toolkit seeds "strongsuit". Left alone, phx 401s every request from an empty user table.
# 3. Prisma owns the schema, so the tables exist but Ecto's schema_migrations is empty. The
# migrations are recorded as applied rather than run; otherwise the dev-only CheckRepoStatus
# plug 503s every request.
#
# Idempotent. Run after the strongsuit-app setup, which creates and seeds the database.
set -euo pipefail
REPO_DIR="${1:-/workspace/repos/strongsuit_phx}"
DB="${PGDATABASE:-strongsuit}"
cat > "$REPO_DIR/config/dev.secret.exs" <<'ELIXIR'
# Local development config for the offline toolkit container. Generated by
# explore/scripts/seed/setup-strongsuit-phx.sh; gitignored, so not a change to the repo.
# Every credential here is an inert dummy: none of these services is reachable offline.
import Config
# The Remix app and this service share one database, and the toolkit seeds "strongsuit".
config :scrubbed011_phx, Scrubbed011Phx.Repo, database: "strongsuit"
# The port both sides of PHX_DOMAIN agree on. dev.exs hardcodes 4000, which this toolkit already
# publishes for another estate, so the caller passes its own.
# Only the port is overridden here: Config deep-merges keyword lists, so a `watchers: []` would
# merge INTO dev.exs's list rather than replace it. The esbuild watcher therefore still runs and
# still fails on the missing assets/vendor/topbar -- once, without restarting, and the endpoint
# serves throughout. The API the Remix app calls needs no bundle.
config :scrubbed011_phx, Scrubbed011PhxWeb.Endpoint,
http: [ip: {0, 0, 0, 0}, port: String.to_integer(System.get_env("PHX_PORT") || "4201")]
config :scrubbed011_phx, Scrubbed011Phx.ApiClients.Cio,
host: "https://track.customer.io",
site_id: "dummy",
api_key: "dummy"
config :scrubbed011_phx, Scrubbed011Phx.ApiClients.Twilio,
account_sid: "dummy",
auth_token: "dummy",
messaging_service_sid: "dummy"
config :scrubbed011_phx, Scrubbed011Phx.TalkJsMessages, twilio_from_number: "+15005550006"
config :scrubbed011_phx, Scrubbed011PhxWeb.Plugs.TalkJsWebhook, secret_key: "dummy"
config :langchain, :anthropic_key, "dummy"
config :scrubbed011_phx, Scrubbed011Phx.ApiClients.Talkjs,
app_id: "dummy",
secret_key: "dummy"
config :scrubbed011_phx, Scrubbed011Phx.ApiClients.CronofyApi,
host: "https://api.cronofy.com",
client_id: "dummy",
client_secret: "dummy"
# The Remix app, as this service sees it: same container, its own port.
config :scrubbed011_phx, Scrubbed011Phx.ApiClients.Scrubbed011App,
host: "http://localhost:3000",
ss_app_api_key: "dummy"
ELIXIR
echo " wrote config/dev.secret.exs"
cd "$REPO_DIR"
mix local.hex --force >/dev/null 2>&1 || true
mix local.rebar --force >/dev/null 2>&1 || true
MIX_ENV=dev mix deps.get
# assets.setup only downloads the esbuild/tailwind binaries, which has to happen while there is
# still a network; it does NOT build a bundle (see the watchers note above). NOT `mix setup`:
# that alias runs ecto.setup, and Prisma owns this schema.
MIX_ENV=dev mix assets.setup
MIX_ENV=dev mix compile
# Record the migrations Prisma has already applied the equivalent of. Skipped when the database
# isn't there yet, which is what `run-app strongsuit_phx` on its own looks like — the app's own
# setup is what creates and seeds it.
if ! PGPASSWORD="${PGPASSWORD:-postgres}" psql -h "${PGHOST:-localhost}" -U "${PGUSER:-postgres}" \
-d "$DB" -tAc 'select 1' >/dev/null 2>&1; then
echo " database \"$DB\" not ready — run \`run-app strongsuit-app\`, which creates it and"
echo " starts this service alongside the app."
exit 0
fi
versions=$(ls priv/repo/migrations | sed 's/_.*//' | awk '{printf "(%s, now()),", $1}' | sed 's/,$//')
if [ -n "$versions" ]; then
PGPASSWORD="${PGPASSWORD:-postgres}" psql -h "${PGHOST:-localhost}" -U "${PGUSER:-postgres}" \
-d "$DB" -v ON_ERROR_STOP=1 -q \
-c "create table if not exists schema_migrations (
version bigint primary key, inserted_at timestamp(0));" \
-c "insert into schema_migrations (version, inserted_at) values $versions on conflict (version) do nothing;"
echo " baselined $(ls priv/repo/migrations | wc -l | tr -d ' ') migrations in schema_migrations"
fi
echo "strongsuit_phx ready — start it with: MIX_ENV=dev mix phx.server (listens on :${PHX_PORT:-4201})"
echo " its own HTML pages render unstyled, and phx.log carries one esbuild error for the"
echo " missing assets/vendor/topbar -- neither affects the JSON API the app uses"

View File

@@ -156,7 +156,12 @@ set -euo pipefail
# ~/.local/bin, where the CLI itself lives. Both are set here so a launch works the
# same either way, with the key .env holds right now.
export PATH="\$HOME/.local/bin:\$PATH"
if [ -f "\${RACCOON_ENV_FILE:-/workspace/.env}" ]; then
# Through the lib, not a bare source: the key a custom codex provider authenticates with
# is this env var, and a .env saved on Windows leaves a \\r on it that the proxy 401s.
HARNESS_SCRIPTS_DIR="$_HARNESS_REGISTRY_DIR" . "$_HARNESS_REGISTRY_DIR/lib/harness-credentials.sh" 2>/dev/null || true
if command -v harness_load_env >/dev/null 2>&1; then
harness_load_env || true
elif [ -f "\${RACCOON_ENV_FILE:-/workspace/.env}" ]; then
set -a
. "\${RACCOON_ENV_FILE:-/workspace/.env}"
set +a

View File

@@ -1,298 +0,0 @@
{
"polyglot": true,
"repos": [
{
"repo": "lambda-cloudwatch-logs-to-loggly",
"defaultCommit": "f17e2d3",
"runtime": "node:14"
},
{
"repo": "lambda-potion-engagement",
"defaultCommit": "c64365b",
"runtime": "node:14"
},
{
"repo": "lambda-potion-schedular",
"defaultCommit": "0843570",
"runtime": "node:14"
},
{
"repo": "lambda-potion-transcription-scheduler",
"defaultCommit": "1a2e3d5",
"runtime": "node:14"
},
{
"repo": "lambda-video-processing",
"defaultCommit": "0e4a9b5",
"runtime": "node:18"
},
{
"repo": "microservice-dynamic-screen-recording",
"defaultCommit": "31e142b",
"runtime": "node:18"
},
{
"repo": "microservice-potion-voice",
"defaultCommit": "b65ca17",
"runtime": "node:14"
},
{
"repo": "potion-dynamic-screen-recording-lambda",
"defaultCommit": "57ed9e6",
"runtime": "node:14"
},
{
"repo": "potion-job-consumer",
"defaultCommit": "93f8a10",
"runtime": "node:18"
},
{
"repo": "potion-job-producer",
"defaultCommit": "04663d1",
"runtime": "node:18"
},
{
"repo": "potion-video-processing",
"defaultCommit": "59c6af9",
"runtime": "node:14"
},
{
"repo": "potion-voice",
"defaultCommit": "fcd8a9d",
"runtime": "node:14"
},
{
"repo": "potion-watcher",
"defaultCommit": "0e5973b",
"runtime": "node:18"
},
{
"repo": "potion-website-recording-handler",
"defaultCommit": "c58a9bb",
"runtime": "node:18"
},
{
"repo": "potion-app",
"defaultCommit": "f89abccf",
"runtime": "node:16",
"startCmd": "bash -c \"cp -n .env.client.development .env.local 2>/dev/null || true; export POTION_APP_ENV=local; [ -f .nuxt/store.js ] || npx nuxt build; node scripts/seed-dev-user.js || true; node server/index.js\"",
"setupCmd": "bash -c \"cp -n .env.client.development .env.local 2>/dev/null || true; export POTION_APP_ENV=local; [ -f .nuxt/store.js ] || npx nuxt build\""
},
{
"repo": "potion-custom-domain-app",
"defaultCommit": "01a7034",
"runtime": "none"
},
{
"repo": "potion-website",
"defaultCommit": "27995f8",
"runtime": "node:16"
},
{
"repo": "browser-extensions",
"defaultCommit": "b5e75d4",
"runtime": "node:18"
},
{
"repo": "gcp-application",
"defaultCommit": "469056f",
"runtime": "node:18"
},
{
"repo": "lambda-text-to-speech",
"defaultCommit": "99054ac",
"runtime": "node:18"
},
{
"repo": "potion-multi-dsr-watcher",
"defaultCommit": "c275d7f",
"runtime": "node:18",
"startCmd": "npx @google-cloud/functions-framework --target=potion-multi-dsr-watcher",
"bootEnv": "MONGODB_URI=mongodb://127.0.0.1:27017/potion_dev"
},
{
"repo": "potion-qa",
"defaultCommit": "3920e6c",
"runtime": "node:18"
},
{
"repo": "potion-snapshot-testing",
"defaultCommit": "a80eb8d",
"runtime": "node:18"
},
{
"repo": "potion-web",
"defaultCommit": "0a7e699",
"runtime": "node:18",
"startCmd": "npx nuxt dev --host 0.0.0.0 --port 3000",
"bootEnv": "POTION_APP_ENV=development BUGSNAG_FRONTEND_KEY=00000000000000000000000000000000 API_BASE_URL=http://localhost:4300 POTION_BASE_URL=http://localhost:4300"
},
{
"repo": "potion-analytics",
"defaultCommit": "43a7d23",
"runtime": "node:20"
},
{
"repo": "potion-api",
"defaultCommit": "5abe18f",
"runtime": "node:20"
},
{
"repo": "MODNet-with-training",
"defaultCommit": "dace325",
"runtime": "python:3.10"
},
{
"repo": "avds-cleaner",
"defaultCommit": "bd3a503",
"runtime": "python:3.10"
},
{
"repo": "avspeech",
"defaultCommit": "ca0f90d",
"runtime": "python:3.10"
},
{
"repo": "lambda-datadog-forwarder",
"defaultCommit": "a57ae74",
"runtime": "python:3.10"
},
{
"repo": "potion-ai",
"defaultCommit": "0e454d8",
"runtime": "python:3.10"
},
{
"repo": "potion-ai-cpu",
"defaultCommit": "ad61fa7",
"runtime": "python:3.10"
},
{
"repo": "potion-ai-gpu",
"defaultCommit": "8413d71",
"runtime": "python:3.10"
},
{
"repo": "potion-stitch",
"defaultCommit": "cfaed2f",
"runtime": "python:3.10"
},
{
"repo": "potion-tryon",
"defaultCommit": "b7da6a2",
"runtime": "python:3.10"
},
{
"repo": "potion-video-background-change",
"defaultCommit": "e6f2ea4",
"runtime": "python:3.10"
},
{
"repo": "potion-voice-dataset",
"defaultCommit": "f3d79d6",
"runtime": "python:3.10"
},
{
"repo": "potion-voice-utils",
"defaultCommit": "eadc48b",
"runtime": "python:3.10"
},
{
"repo": "sentence-split-service",
"defaultCommit": "32356d2",
"runtime": "python:3.10"
},
{
"repo": "urlbox-experiments",
"defaultCommit": "141fe18",
"runtime": "python:3.10"
},
{
"repo": "video-synth-api",
"defaultCommit": "167fcd7",
"runtime": "python:3.10"
},
{
"repo": "wav2lip-fa",
"defaultCommit": "8448ef0",
"runtime": "python:3.10"
},
{
"repo": "yeahsure-tryon",
"defaultCommit": "c8dee39",
"runtime": "python:3.10"
},
{
"repo": "gcp-infrastructure",
"defaultCommit": "a7dc5cc",
"runtime": "none"
},
{
"repo": "potion-ai-pretrained-models-infra",
"defaultCommit": "8a88770",
"runtime": "none"
},
{
"repo": "potion-app-infra",
"defaultCommit": "2107464",
"runtime": "none"
},
{
"repo": "potion-bastion",
"defaultCommit": "062af16",
"runtime": "none"
},
{
"repo": "potion-video-processing-devops",
"defaultCommit": "566286d",
"runtime": "none"
},
{
"repo": "elasticmq-container",
"defaultCommit": "de8acb5",
"runtime": "none"
},
{
"repo": "gcp-cloud-infrastructure",
"defaultCommit": "aa033c8",
"runtime": "none"
},
{
"repo": "potion-devops",
"defaultCommit": "84a4532",
"runtime": "none"
},
{
"repo": "potion-wp-site",
"defaultCommit": "cb71e3a",
"runtime": "none"
}
],
"defaultRepo": "potion-app",
"version": "037cfcf94b",
"blockedHosts": [
"sendpotion.com",
"www.sendpotion.com",
"app.sendpotion.com",
"staging.sendpotion.com",
"development.sendpotion.com",
"devleopment.sendpotion.com",
"meawww.sendpotion.com",
"blog.sendpotion.com",
"help.sendpotion.com",
"terms.sendpotion.com",
"pricing.sendpotion.com",
"videoassets.sendpotion.com",
"subtitleassets.sendpotion.com",
"audioassets.sendpotion.com",
"videoassets.staging.sendpotion.com",
"subtitleassets.staging.sendpotion.com",
"audioassets.staging.sendpotion.com"
],
"explorePorts": {
"clientHost": 4300,
"serverHost": null,
"livereloadHost": null,
"corpusHost": null
}
}

View File

@@ -208,6 +208,38 @@ case "$REPO" in
printf "${GRAY}Want another container with its own separate working tree (e.g. a different commit / repo state)?${RESET}\n"
printf "${GRAY}On the host, from explore/: node instance.js b then: node instance.js shell b${RESET}\n\n"
;;
frepple)
printf "${COLOR}Run the app${RESET} in one command: ${GRAY}run-app${RESET} — starts the Django web app.\n"
printf "Open ${GRAY}http://localhost:${CLIENT_PORT}/${RESET} and sign in as ${GRAY}admin${RESET} / ${GRAY}frepple${RESET}\n"
printf "Setup loaded the ${GRAY}demo${RESET} dataset, so items, buffers, operations and demands are\n"
printf "already there. Other datasets: ${GRAY}frepplectl.py loaddata manufacturing_demo${RESET} (also\n"
printf "distribution_demo, jobshop, flow_line).\n\n"
printf "${YELLOW}Three languages, one app${RESET} — a C++ planning engine (${GRAY}src/${RESET}, built in-tree to\n"
printf "${GRAY}bin/frepple${RESET}), a Django app (${GRAY}freppledb/${RESET}) and a Vue frontend.\n"
printf "Two test suites:\n"
printf "${GRAY}cd test && ./runtest.py${RESET} the 83 engine scenarios, seconds, no database\n"
printf "${GRAY}./frepplectl.py test freppledb${RESET} the Django suite, a few minutes\n"
printf "Rebuild the engine after editing ${GRAY}src/${RESET}: ${GRAY}cmake --build build --parallel${RESET}\n\n"
printf "${GRAY}The two browser tests (freppledb/*/tests/test_frontend.py) need Chrome and fail${RESET}\n"
printf "${GRAY}here; that is expected, not your environment.${RESET}\n\n"
printf "${GRAY}Want another container with its own separate working tree (e.g. a different commit / repo state)?${RESET}\n"
printf "${GRAY}On the host, from explore/: node instance.js b then: node instance.js shell b${RESET}\n\n"
;;
freeitsm)
printf "${COLOR}Run the app${RESET} in one command: ${GRAY}run-app${RESET} — boots Apache (mod_php).\n"
printf "Open ${GRAY}http://localhost:${CLIENT_PORT}/${RESET} and sign in as ${GRAY}admin${RESET} / ${GRAY}freeitsm123${RESET}\n"
printf "(staff sign-in; setup already cleared the forced password change). Setup seeds demo\n"
printf "data for 19 of the 20 modules; the LMS importer refuses and is left empty.\n\n"
printf "${YELLOW}No framework and no composer${RESET} — plain PHP served from the repo root.\n"
printf "Verifier: the ${GRAY}27 standalone scripts in tests/${RESET}, run one at a time:\n"
printf "${GRAY}su -s /bin/bash www-data -c 'cd /workspace/repo && php tests/cmdb-typed-fields.php'${RESET}\n"
printf "Run them as ${YELLOW}www-data${RESET}: they write a PHP session file that Apache reopens, so as\n"
printf "root the HTTP-driving ones fail with ${GRAY}Not authenticated${RESET}.\n"
printf "${GRAY}calendar-sync-tasks.php${RESET} and ${GRAY}task-recurrence-spawn.php${RESET} exit non-zero printing\n"
printf "nothing — a pre-existing defect in those two scripts, not your environment.\n\n"
printf "${GRAY}Want another container with its own separate working tree (e.g. a different commit / repo state)?${RESET}\n"
printf "${GRAY}On the host, from explore/: node instance.js b then: node instance.js shell b${RESET}\n\n"
;;
breezy-complete)
printf "${COLOR}Run the app${RESET} in one command: ${GRAY}run-app${RESET} — boots the Rails API (${GRAY}backend/${RESET}) and the\n"
printf "Next.js frontend (${GRAY}frontend/${RESET}). Open ${GRAY}http://localhost:${CLIENT_PORT}/pro_signin${RESET} — auth is\n"
@@ -219,4 +251,10 @@ case "$REPO" in
printf "${GRAY}Want another container with its own separate working tree (e.g. a different commit / repo state)?${RESET}\n"
printf "${GRAY}On the host, from explore/: node instance.js b then: node instance.js shell b${RESET}\n\n"
;;
*)
printf "${COLOR}Run the app${RESET} in one command: ${GRAY}run-app${RESET}, then open ${GRAY}http://localhost:${CLIENT_PORT}/${RESET}\n"
printf "See ${GRAY}README.md${RESET} for this toolkit's verifier command and any repo-specific setup.\n\n"
printf "${GRAY}Want another container with its own separate working tree (e.g. a different commit / repo state)?${RESET}\n"
printf "${GRAY}On the host, from explore/: node instance.js b then: node instance.js shell b${RESET}\n\n"
;;
esac