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

@@ -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