docs: README + obsidian parity with SCA-only posture, flood caps, lockout tiers; dev-script secret bootstrap, stale-backend kill, patch-test backdate

- README: payments/2FA sections rewritten for the SCA-only posture (no
  TWO_FACTOR_FALLBACK, tokenize-result wire contract, 402 refusal), deposit
  carve-out clarified, gift-card 12-hex codes + 14-day cancellation, flood-cap
  insert sites enumerated, escalating lockout tiers documented, ICO
  registration note, updated test counts (2,555 backend + 129 frontend).
- local-dev-2.sh: fail-closed dev secret bootstrap (auto-generates
  JWT_SECRET_KEY / TWO_FACTOR_PEPPER into the gitignored .env), kills stale
  backends holding :8080 before the tmux reset, passes
  RUSTFS_ENDPOINT/GO_TESTING=1 to the dev backend, and backdates seeded patch
  tests 60 days so past gel bookings pass the 24h notice gate.
- Obsidian manuals (Technical/Admin/User/Feature Catalog/Overview/Gift Card
  T&C/Privacy/T&C/Testing Architecture/payments and money processes + p14 plan
  + workspace state) updated to the post-round-2 state.
This commit is contained in:
2026-08-22 00:34:50 +01:00
parent af685df40c
commit 1d9c87d6d6
15 changed files with 342 additions and 223 deletions
+70 -1
View File
@@ -30,6 +30,43 @@ else
exit 1
fi
# --- 1a. Fail-closed secret bootstrap (dev only) ---
# The backend fail-closes on an empty/weak JWT_SECRET_KEY (main.go) and on an
# unset TWO_FACTOR_PEPPER in production builds, but .env.example ships both
# empty — so a fresh `cp .env.example .env` could never boot the dev backend.
# Generate strong random dev values and persist them into the gitignored .env
# (idempotent: only fills empty/weak values). Production deploys must still set
# real values explicitly; this merely removes the local-dev foot-gun.
bootstrap_secret() {
local name="$1" gen="$2" minlen="$3"
local val="${(P)name:-}"
if [ -z "$val" ] || [ ${#val} -lt $minlen ]; then
local generated
generated="$(${=gen})" || return 1
export "$name=$generated"
if awk -v name="$name" -v value="$generated" '
$0 ~ "^" name "=" { print name "=" value; replaced=1; next }
{ print }
END { if (!replaced) print name "=" value }
' .env > .env.tmp 2>/dev/null && mv .env.tmp .env 2>/dev/null; then
log_info "$name was empty/weak — generated a random dev value and wrote it to .env"
else
rm -f .env.tmp 2>/dev/null
return 1
fi
fi
return 0
}
if ! bootstrap_secret "JWT_SECRET_KEY" "openssl rand -hex 32" 32; then
log_error "Failed to generate JWT_SECRET_KEY (openssl unavailable?). Set it manually in .env."
exit 1
fi
# TWO_FACTOR_PEPPER is optional in dev builds (one-time warning when unset),
# but generating it keeps dev 2FA code-hashing identical to production.
bootstrap_secret "TWO_FACTOR_PEPPER" "openssl rand -base64 32" 32 \
|| log_info "TWO_FACTOR_PEPPER generation failed (openssl?) — dev will use the legacy digest with a one-time warning."
# --- 2. Docker Checks ---
if ! docker info > /dev/null 2>&1; then
log_info "Docker daemon not running. Starting..."
@@ -129,6 +166,25 @@ if tmux has-session -t $SESSION_NAME 2>/dev/null; then
tmux kill-session -t $SESSION_NAME
fi
# A backend started OUTSIDE the tmux session (e.g. a manual `go run` from a
# prior terminal) survives the session kill above and still holds :8080,
# which would make the new backend pane die with "address already in use".
# The script is a full dev reset (it wipes postgres + rustfs data), so killing
# the stale dev backend on the app's own port is in scope.
if command -v ss > /dev/null 2>&1 && ss -tlnp 2>/dev/null | grep -q ":8080 "; then
log_info "Port 8080 is held by a stale backend process — killing it..."
ss -tlnp 2>/dev/null | grep ":8080 " | grep -o 'pid=[0-9]*' | cut -d= -f2 | sort -u | while read -r pid; do
kill "$pid" 2>/dev/null && log_info "Killed stale backend pid $pid"
done
sleep 1
if ss -tlnp 2>/dev/null | grep -q ":8080 "; then
log_error "Port 8080 is still in use after killing stale backends."
ss -tlnp 2>/dev/null | grep ":8080 "
log_error "Free port 8080 and re-run."
exit 1
fi
fi
log_step "Starting tmux session '$SESSION_NAME'..."
tmux new-session -d -s $SESSION_NAME -n "Workspace"
@@ -158,7 +214,7 @@ tmux select-pane -t $SESSION_NAME:0.0 -T "DB"
# Pane 1: Backend (Split Horizontally)
tmux split-window -v -t $SESSION_NAME
tmux send-keys -t $SESSION_NAME "set -a; source .env > /dev/null 2>&1; cd backend && POSTGRES_HOST=localhost go run -tags dev ./main.go" Enter
tmux send-keys -t $SESSION_NAME "set -a; source .env > /dev/null 2>&1; cd backend && POSTGRES_HOST=localhost RUSTFS_ENDPOINT=http://localhost:9000 GO_TESTING=1 go run -tags dev ./main.go" Enter
tmux select-pane -t $SESSION_NAME:0.1 -T "Backend"
# Pane 2: Frontend (Split Vertically from Backend)
@@ -448,6 +504,19 @@ if [[ -n "$PATCH_TEST_ID" ]]; then
&& patch_recorded=$((patch_recorded + 1))
fi
done
# The admin API records tested_at = NOW(), but the seed later creates PAST
# gel bookings (7-28 days ago) for Emma/Lily/Grace. The backend's 24h
# patch-test notice gate (manage.go) requires booking_time >= tested_at+24h,
# which would reject every past gel booking by construction. Backdate the
# records so the demo gel history can exist.
if [[ "$patch_recorded" -gt 0 ]]; then
docker exec postgres psql -U myuser -d mydb -c "
UPDATE user_patch_tests SET tested_at = NOW() - INTERVAL '60 days'
WHERE user_id IN ('${GRACE_ID}','${EMMA_ID}','${ISLA_ID}','${LILY_ID}')
AND patch_test_id = '${PATCH_TEST_ID}';
" > /dev/null 2>&1 \
&& echo "${C_GREEN}✅ Patch tests backdated so past gel bookings pass the 24h notice gate${C_RESET}"
fi
echo "${C_GREEN}✅ Patch test seeded (SQL) and recorded for $patch_recorded users${C_RESET}"
else
echo "${C_YELLOW}⚠️ Patch test seeding failed (check DB connection or patch_tests table)${C_RESET}"