// Versioned, idempotent seed for the three canvas personas. // // Replaces the round-1 raw `UPDATE auth_service.user SET is_superuser=TRUE` step // with a deterministic, auditable script that: // 1. POSTs each persona to /api/v1/auth/register (idempotent: 409 is OK). // 2. Promotes is_superuser via a controlled SQL UPDATE through the auth-service // pod, recorded in this file under git so the change is reviewable. // 3. Verifies dev-login succeeds for each persona before exiting. // // Usage: node qa/seeds/001_personas.mjs // Idempotent: safe to re-run; will skip personas that already exist + are active. import { execSync } from "node:child_process"; const TENANT_ID = "a0000000-0000-0000-0000-000000000001"; const BASE = "https://demo.flow-master.ai"; const PERSONAS = [ { email: "ceo-head@flow-master.ai", username: "ceo_head", first: "Mariana", last: "Cole", title: "Chief Executive" }, { email: "hr-head@flow-master.ai", username: "hr_head", first: "Aisha", last: "Khan", title: "HR Director" }, { email: "it-head@flow-master.ai", username: "it_head", first: "Rohan", last: "Patel", title: "IT Director" }, ]; const PASSWORD = "CanvasDog!ood-2026"; async function register(persona) { const body = { email: persona.email, username: persona.username, first_name: persona.first, last_name: persona.last, display_name: `${persona.first} ${persona.last} — ${persona.title}`, password: PASSWORD, confirm_password: PASSWORD, tenant_id: TENANT_ID, }; const r = await fetch(`${BASE}/api/v1/auth/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (r.status === 200 || r.status === 201) return { created: true }; if (r.status === 409) return { existing: true }; const txt = await r.text(); if (r.status === 400 && /already exists/i.test(txt)) return { existing: true }; if (r.status === 422 && /already exists/i.test(txt)) return { existing: true }; if (r.status === 429) return { rate_limited: true }; throw new Error(`register ${persona.email} → ${r.status}: ${txt.slice(0, 200)}`); } const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); function promoteToSuperuser() { const pod = execSync(`ssh build01 'kubectl -n baseline get pod -l app=auth-service -o jsonpath="{.items[?(@.status.phase==\\"Running\\")].metadata.name}"'`, { encoding: "utf8" }).trim(); if (!pod) throw new Error("no running auth-service pod found"); const remoteScript = "/tmp/canvas_personas_promote.py"; const py = [ "import asyncio, os", "from sqlalchemy.ext.asyncio import create_async_engine", "from sqlalchemy import text", "async def main():", " e = create_async_engine(os.environ['DATABASE_URL'])", " async with e.connect() as c:", " await c.execute(text(\"UPDATE auth_service.\\\"user\\\" SET is_superuser=TRUE, status='active' WHERE email LIKE '%-head@flow-master.ai'\"))", " await c.commit()", " r = await c.execute(text(\"SELECT email, is_superuser, status FROM auth_service.\\\"user\\\" WHERE email LIKE '%-head@flow-master.ai' ORDER BY email\"))", " for row in r: print(row)", "asyncio.run(main())", ].join("\n"); const b64 = Buffer.from(py).toString("base64"); execSync(`ssh build01 'kubectl -n baseline exec ${pod} -- sh -c "echo ${b64} | base64 -d > ${remoteScript}"'`, { encoding: "utf8" }); return execSync(`ssh build01 'kubectl -n baseline exec ${pod} -- python3 ${remoteScript}'`, { encoding: "utf8" }); } async function verifyDevLogin(persona) { const r = await fetch(`${BASE}/api/v1/auth/dev-login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: persona.email }), }); if (!r.ok) throw new Error(`dev-login ${persona.email} → ${r.status}`); const body = await r.json(); if (!body.access_token) throw new Error(`dev-login ${persona.email} no access_token`); return body.access_token.slice(0, 24) + "..."; } const results = []; for (const p of PERSONAS) { const reg = await register(p); results.push({ email: p.email, ...reg }); const status = reg.existing ? "already exists" : reg.rate_limited ? "rate-limited (skip)" : "created"; console.log(`register ${p.email}: ${status}`); await sleep(1500); } console.log("\npromoting to superuser via auth_service DB:"); console.log(promoteToSuperuser()); console.log("\nverifying dev-login:"); for (const p of PERSONAS) { const token = await verifyDevLogin(p); console.log(` ${p.email} → ${token}`); } console.log("\nseed complete. all 3 personas registered, promoted, and dev-login verified.");