/** * PersonaForge Stewardship Dashboard * * The steward-facing workspace for vetting corpus artifacts before a build. * Not the admin panel — this is the person responsible for the corpus doing * their actual review work, with Ody as a research assistant. * * Routes (agentify.help host, dashboard-token protected): * GET /steward/:slug — SSR dashboard * GET /api/steward/:slug/info — subject + stats * GET /api/steward/:slug/artifacts — list artifacts + curation * PATCH /api/steward/:slug/artifacts/:artifact_id — update curation status * POST /api/steward/:slug/artifacts/:artifact_id/ody-ask — Ody analysis * POST /api/steward/:slug/corpus/fetch — trigger essay fetch * POST /api/steward/:slug/corpus/build — build approved-only corpus * GET /api/steward/:slug/dashboard-token — admin: get/generate token */ import type { Express, Request, Response } from "express"; import { pool } from "../db"; import crypto from "crypto"; import Anthropic from "@anthropic-ai/sdk"; const ADMIN_KEY = process.env.ADMIN_KEY || "b0db7a87384fc814b0f46ea7bdc6ab6a81152be5b098718b"; const claude = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); // ── Schema migrations ───────────────────────────────────────────────────────── export async function initStewardDash() { // Curation columns on artifacts await pool.query(`ALTER TABLE personaforge_artifacts ADD COLUMN IF NOT EXISTS curation_status TEXT NOT NULL DEFAULT 'pending'`); await pool.query(`ALTER TABLE personaforge_artifacts ADD COLUMN IF NOT EXISTS curation_note TEXT`); await pool.query(`ALTER TABLE personaforge_artifacts ADD COLUMN IF NOT EXISTS ody_verdict TEXT`); // Manual artifact content (bypasses URL fetch — used for injected bio facts, Wikipedia excerpts, etc.) await pool.query(`ALTER TABLE personaforge_artifacts ADD COLUMN IF NOT EXISTS content_text TEXT`); // Dashboard token stored on personaforge_subjects (guaranteed to exist for all subjects) await pool.query(`ALTER TABLE personaforge_subjects ADD COLUMN IF NOT EXISTS dashboard_token TEXT`); // Biographical baseline facts (structured markdown, highest-priority corpus chunk, not curatable) await pool.query(`ALTER TABLE personaforge_subjects ADD COLUMN IF NOT EXISTS bio_facts_md TEXT`); // PTP attestation metadata for bio_facts (who wrote it and under what token/scope) await pool.query(`ALTER TABLE personaforge_subjects ADD COLUMN IF NOT EXISTS bio_facts_ptp_token_id TEXT`); await pool.query(`ALTER TABLE personaforge_subjects ADD COLUMN IF NOT EXISTS bio_facts_attested_at TIMESTAMPTZ`); await pool.query(`ALTER TABLE personaforge_subjects ADD COLUMN IF NOT EXISTS bio_facts_vcap_uri TEXT`); // Wikipedia/secondary source summary (skepticism-weighted; separate from primary corpus) await pool.query(`ALTER TABLE personaforge_subjects ADD COLUMN IF NOT EXISTS wiki_summary_md TEXT`); // PTP attestation metadata for wiki summary await pool.query(`ALTER TABLE personaforge_subjects ADD COLUMN IF NOT EXISTS wiki_ptp_token_id TEXT`); await pool.query(`ALTER TABLE personaforge_subjects ADD COLUMN IF NOT EXISTS wiki_attested_at TIMESTAMPTZ`); // Corpus consult log (PTP injection events, QA sessions) await pool.query(` CREATE TABLE IF NOT EXISTS corpus_consult_log ( id SERIAL PRIMARY KEY, slug TEXT NOT NULL, event_type TEXT NOT NULL, token_id TEXT, scope TEXT, purpose TEXT, attested_at TEXT, session_notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ) `); // Also add to registry for forward-compat (new stewards via web form) await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS dashboard_token TEXT`).catch(() => {}); // Co-steward collaboration: array of invited co-steward emails await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS co_steward_emails TEXT[] DEFAULT '{}'`).catch(() => {}); await pool.query(`ALTER TABLE personaforge_subjects ADD COLUMN IF NOT EXISTS co_steward_emails TEXT[] DEFAULT '{}'`).catch(() => {}); console.log("[StewardDash] Schema columns ready"); } // ── Helpers ─────────────────────────────────────────────────────────────────── function isAgentify(req: Request): boolean { const candidates = [ req.headers["x-forwarded-host"], req.headers["x-geo-node-host"], req.hostname, req.headers["host"], ].flat().filter(Boolean).map((h: any) => h.toString().toLowerCase().split(",")[0].trim()); return candidates.some(h => h.includes("agentify") && !h.startsWith("skills.")); } async function validateStewardToken(slug: string, token: string | undefined): Promise { if (!token) return false; // Primary source: personaforge_subjects (exists for all subjects including admin-seeded) const { rows } = await pool.query( `SELECT dashboard_token FROM personaforge_subjects WHERE slug = $1`, [slug] ); if (rows.length > 0 && rows[0].dashboard_token === token) return true; // Fallback: agentify_subject_registry (for stewards registered via web form) const { rows: reg } = await pool.query( `SELECT dashboard_token FROM agentify_subject_registry WHERE subject_slug = $1`, [slug] ).catch(() => ({ rows: [] })); return reg.length > 0 && reg[0].dashboard_token === token; } async function getOrCreateDashboardToken(slug: string): Promise { // Primary: personaforge_subjects const { rows } = await pool.query( `SELECT dashboard_token FROM personaforge_subjects WHERE slug = $1`, [slug] ); if (rows[0]?.dashboard_token) return rows[0].dashboard_token; const token = crypto.randomBytes(24).toString("hex"); // Store in personaforge_subjects const updated = await pool.query( `UPDATE personaforge_subjects SET dashboard_token = $2, updated_at = NOW() WHERE slug = $1 RETURNING slug`, [slug, token] ); if ((updated.rowCount || 0) === 0) throw new Error(`Subject '${slug}' not found in personaforge_subjects`); // Also sync to agentify_subject_registry if that row exists await pool.query( `UPDATE agentify_subject_registry SET dashboard_token = $2, updated_at = NOW() WHERE subject_slug = $1`, [slug, token] ).catch(() => {}); return token; } async function fetchEssayText(url: string): Promise { try { const html = await fetch(url, { headers: { "User-Agent": "WellSpr.ing PersonaForge/1.0" }, signal: AbortSignal.timeout(15000), }).then(r => r.text()); return html .replace(/]*>[\s\S]*?<\/script>/gi, " ") .replace(/]*>[\s\S]*?<\/style>/gi, " ") .replace(/<[^>]+>/g, " ") .replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/ /g, " ") .replace(/\s+/g, " ").trim(); } catch { return ""; } } // ── SSR Dashboard page ──────────────────────────────────────────────────────── function dashboardPage(slug: string, subjectName: string): string { return ` ${subjectName} — Stewardship Dashboard | Agentify.Help
AGENTIFY.HELP
${subjectName}
Corpus Stewardship Dashboard
Total
Approved
Rejected
Flagged
Pending
0 of 0 reviewed

Biographical Baseline

Loading… ▼ show

Ground-Truth Facts — injected as first corpus chunk, highest priority, not curatable

Wikipedia / Secondary Sources — skepticism-weighted; separate from primary corpus

Loading corpus artifacts…
Building corpus…
`; } // ── Routes ──────────────────────────────────────────────────────────────────── // NOTE: This function is intentionally synchronous so that app.get("/steward/:slug") // is registered before serveSpaFallback() in the production middleware stack. // initStewardDash() runs DB schema migrations in the background — the route // handlers themselves are safe to execute once the DB is available. export function registerStewardRoutes(app: Express) { // Run schema migrations in the background; do NOT await before registering routes initStewardDash().catch((e: any) => console.error("[StewardDash] init error:", e.message)); // ── GET /steward/:slug — SSR dashboard ────────────────────────────────── // Note: host guard removed — the dashboard token is the access control. // Subjects may exist in either personaforge_subjects (admin-seeded) or // agentify_subject_registry (registered via web form). We check both and // auto-promote registry-only subjects into personaforge_subjects on first access. app.get("/steward/:slug", async (req: Request, res: Response) => { const { slug } = req.params; const token = req.query.token as string; const notFoundPage = `Not found

Not found

No subject found for slug: ${slug}

`; const accessDeniedPage = `Access denied

Access denied

This dashboard requires a valid stewardship token. Use the link from your confirmation email, or contact ody@wellspr.ing.

`; try { // 1. Look up in personaforge_subjects (primary/canonical source) let { rows: pf } = await pool.query( `SELECT subject_name, build_status, dashboard_token FROM personaforge_subjects WHERE slug = $1`, [slug] ); // 2. If not found in personaforge_subjects, check agentify_subject_registry // (stewards who registered via the web form end up here) if (!pf[0]) { const { rows: reg } = await pool.query( `SELECT subject_name, subject_domain, steward_name, steward_email, dashboard_token, status FROM agentify_subject_registry WHERE subject_slug = $1`, [slug] ).catch(() => ({ rows: [] as any[] })); if (!reg[0]) { return res.status(404).send(notFoundPage); } const r = reg[0]; // Auto-promote into personaforge_subjects so all dashboard features work await pool.query( `INSERT INTO personaforge_subjects (slug, subject_name, domain, build_status, dashboard_token) VALUES ($1, $2, $3, 'active', $4) ON CONFLICT (slug) DO UPDATE SET subject_name = EXCLUDED.subject_name, domain = EXCLUDED.domain, dashboard_token = COALESCE(personaforge_subjects.dashboard_token, EXCLUDED.dashboard_token), updated_at = NOW()`, [slug, r.subject_name, r.subject_domain || "general", r.dashboard_token || null] ).catch((e: any) => console.warn("[StewardDash] auto-promote error:", e.message)); // Re-fetch so the rest of the handler sees the row const refetch = await pool.query( `SELECT subject_name, build_status, dashboard_token FROM personaforge_subjects WHERE slug = $1`, [slug] ); pf = refetch.rows; if (!pf[0]) { // Fallback: use registry data directly const validToken = r.dashboard_token; if (!validToken || token !== validToken) return res.status(403).send(accessDeniedPage); return res.send(dashboardPage(slug, r.subject_name)); } } const subjectName = pf[0].subject_name; const validToken = pf[0].dashboard_token; if (!validToken || token !== validToken) return res.status(403).send(accessDeniedPage); return res.send(dashboardPage(slug, subjectName)); } catch (e: any) { console.error("[StewardDash] /steward/:slug error:", e.message); return res.status(500).send(`

Error

Something went wrong loading this dashboard. Please try again.

`); } }); // ── GET /api/steward/:slug/info ────────────────────────────────────────── app.get("/api/steward/:slug/info", async (req: Request, res: Response) => { const { slug } = req.params; const token = req.query.token as string || req.body?.token; if (!await validateStewardToken(slug, token)) return res.status(403).json({ error: "Invalid token" }); const { rows: pf } = await pool.query(`SELECT * FROM personaforge_subjects WHERE slug = $1`, [slug]); if (!pf[0]) return res.status(404).json({ error: "Subject not found" }); const { rows: counts } = await pool.query( `SELECT curation_status, COUNT(*) as n FROM personaforge_artifacts WHERE subject_slug = $1 GROUP BY curation_status`, [slug] ); const stats: Record = { pending: 0, approved: 0, rejected: 0, flagged: 0 }; counts.forEach(r => { stats[r.curation_status] = parseInt(r.n); }); res.json({ subject: pf[0], stats }); }); // ── GET /api/steward/:slug/artifacts ──────────────────────────────────── app.get("/api/steward/:slug/artifacts", async (req: Request, res: Response) => { const { slug } = req.params; const token = req.query.token as string; if (!await validateStewardToken(slug, token)) return res.status(403).json({ error: "Invalid token" }); const { rows } = await pool.query( `SELECT artifact_id, title, source_url, pub_date, artifact_type, word_count, chunk_count, curation_status, curation_note, ody_verdict, created_at FROM personaforge_artifacts WHERE subject_slug = $1 ORDER BY pub_date DESC NULLS LAST, title ASC`, [slug] ); res.json({ artifacts: rows }); }); // ── PATCH /api/steward/:slug/artifacts/:artifact_id ───────────────────── app.patch("/api/steward/:slug/artifacts/:artifact_id", async (req: Request, res: Response) => { const { slug, artifact_id } = req.params; const { token, curation_status, curation_note } = req.body; if (!await validateStewardToken(slug, token)) return res.status(403).json({ error: "Invalid token" }); const allowed = ["pending", "approved", "rejected", "flagged"]; if (!allowed.includes(curation_status)) return res.status(400).json({ error: "Invalid curation_status" }); await pool.query( `UPDATE personaforge_artifacts SET curation_status = $1, curation_note = $2 WHERE artifact_id = $3 AND subject_slug = $4`, [curation_status, curation_note || null, artifact_id, slug] ); res.json({ ok: true }); }); // ── POST /api/steward/:slug/artifacts/:artifact_id/ody-ask ────────────── app.post("/api/steward/:slug/artifacts/:artifact_id/ody-ask", async (req: Request, res: Response) => { const { slug, artifact_id } = req.params; const { token } = req.body; if (!await validateStewardToken(slug, token)) return res.status(403).json({ error: "Invalid token" }); // Get artifact const { rows } = await pool.query( `SELECT title, source_url, word_count, artifact_type FROM personaforge_artifacts WHERE artifact_id = $1 AND subject_slug = $2`, [artifact_id, slug] ); if (!rows[0]) return res.status(404).json({ error: "Artifact not found" }); const art = rows[0]; // Get subject info const { rows: pf } = await pool.query(`SELECT subject_name, domain FROM personaforge_subjects WHERE slug = $1`, [slug]); const subject = pf[0] || { subject_name: slug, domain: "" }; // Fetch essay text (first ~3000 words worth) let essayText = ""; if (art.source_url) { const raw = await fetchEssayText(art.source_url); essayText = raw.substring(0, 12000); // ~3000 words } if (!essayText) { return res.json({ verdict: `Verdict: Flag\n\nCould not fetch the essay text from ${art.source_url || "the source URL"}. You may want to review this one manually before including it.` }); } const prompt = `You are helping a steward vet corpus material for an AI persona based on ${subject.subject_name}. The steward is reviewing each piece of source material to decide whether it should be included in the corpus that will train and ground the persona. **Essay title:** "${art.title}" **Type:** ${art.artifact_type || "essay"} **Word count:** ${art.word_count || "unknown"} **Essay text (excerpt):** ${essayText} Please give a concise vetting assessment (3–5 sentences). Address: 1. Is this representative of ${subject.subject_name}'s actual thinking and voice? 2. Is the content high signal — substantive ideas, not just announcements or filler? 3. Any concerns about quality, accuracy, or relevance to the corpus? Then give your verdict on a new line: "Verdict: Approve", "Verdict: Flag", or "Verdict: Reject".`; try { const msg = await claude.messages.create({ model: "claude-haiku-4-5", max_tokens: 400, messages: [{ role: "user", content: prompt }], }); const verdict = (msg.content[0] as any).text || ""; // Store verdict await pool.query( `UPDATE personaforge_artifacts SET ody_verdict = $1 WHERE artifact_id = $2`, [verdict, artifact_id] ); res.json({ verdict }); } catch (e: any) { console.error("[StewardDash] Ody ask error:", e.message); res.status(500).json({ error: "Claude error: " + e.message }); } }); // ── POST /api/steward/:slug/corpus/fetch ──────────────────────────────── // Delegates to PersonaForge fetch (admin-side), authorized via steward token app.post("/api/steward/:slug/corpus/fetch", async (req: Request, res: Response) => { const { slug } = req.params; const { token } = req.body; if (!await validateStewardToken(slug, token)) return res.status(403).json({ error: "Invalid token" }); // Call the PersonaForge fetch endpoint internally (same process) try { const { rows: pf } = await pool.query( `SELECT source_url FROM personaforge_subjects WHERE slug = $1`, [slug] ); if (!pf[0]?.source_url) return res.status(400).json({ error: "No source URL configured for this subject" }); const sourceUrl = pf[0].source_url; const html = await fetch(sourceUrl, { headers: { "User-Agent": "WellSpr.ing PersonaForge/1.0" } }).then(r => r.text()); const base = new URL(sourceUrl); const found: { slug: string; title: string; url: string }[] = []; const re = /]*>([^<]+)<\/a>/gi; let m: RegExpExecArray | null; while ((m = re.exec(html)) !== null) { const href = m[1].trim(); const title = m[2].trim(); if (!title || title.length < 3) continue; try { const full = new URL(href, base).href; const artSlug = href.replace(/.*\//, "").replace(".html", "").toLowerCase().replace(/[^a-z0-9]+/g, "-"); if (!found.find(r => r.slug === artSlug)) found.push({ slug: artSlug, title, url: full }); } catch {} } let inserted = 0; for (const item of found) { const artifactId = `${slug}::${item.slug}`; const result = await pool.query( `INSERT INTO personaforge_artifacts (subject_slug, artifact_id, title, source_url, artifact_type, curation_status) VALUES ($1, $2, $3, $4, 'essay', 'pending') ON CONFLICT (artifact_id) DO NOTHING`, [slug, artifactId, item.title, item.url] ); if (result.rowCount && result.rowCount > 0) inserted++; } await pool.query(`UPDATE personaforge_subjects SET essay_count = $2, updated_at = NOW() WHERE slug = $1`, [slug, found.length]); res.json({ ok: true, count: found.length, inserted }); } catch (e: any) { console.error("[StewardDash] Fetch error:", e.message); res.status(500).json({ error: e.message }); } }); // ── POST /api/steward/:slug/corpus/build ──────────────────────────────── // Triggers a build using only approved artifacts app.post("/api/steward/:slug/corpus/build", async (req: Request, res: Response) => { const { slug } = req.params; const { token } = req.body; if (!await validateStewardToken(slug, token)) return res.status(403).json({ error: "Invalid token" }); // Get approved artifact IDs const { rows: approved } = await pool.query( `SELECT artifact_id FROM personaforge_artifacts WHERE subject_slug = $1 AND curation_status = 'approved'`, [slug] ); if (approved.length === 0) return res.status(400).json({ error: "No approved artifacts to build" }); // Delegate to personaforge build endpoint (internal HTTP call via admin key) try { const bundleId = crypto.randomUUID(); await pool.query( `UPDATE personaforge_subjects SET build_status = 'building', bundle_id = $2, build_log = '', updated_at = NOW() WHERE slug = $1`, [slug, bundleId] ); res.json({ ok: true, bundle_id: bundleId, approved_count: approved.length, message: "Build started — connect to SSE log stream for progress" }); // Async build (fire and forget — steward watches via SSE) setImmediate(() => runApprovedBuild(slug, bundleId, approved.map(r => r.artifact_id))); } catch (e: any) { res.status(500).json({ error: e.message }); } }); // ── POST /api/steward/:slug/bio-facts/ptp ─────────────────────────────── // PTP-authenticated corpus injection. Any AI agent holding a valid WellSpr.ing // PTP token with scope corpus:write:personaforge:{slug} (or :*) may inject // verified biographical facts and have them stamped with their attestation. // // Intended use: a Claude session runs the QA skill against a persona, verifies // facts against primary sources, then writes the verified facts back to the // corpus with a PTP-attested audit trail. The token_id is stored with the facts // so anyone can verify who/what wrote them and under what scope. // // Flow: // 1. Agent requests PTP token: POST /api/v1/ptp/token // scope: "corpus:write:personaforge:{slug}" (or "corpus:write:personaforge:*") // purpose: "QA review and verified fact injection for [Subject Name]" // 2. Agent presents token here with facts payload // 3. Server verifies token validity + scope coverage // 4. Facts stored with ptp_token_id, attested_at, vcap_attestation_uri // 5. Returns verifiable attestation record app.post("/api/steward/:slug/bio-facts/ptp", async (req: Request, res: Response) => { const { slug } = req.params; const { token_id, bio_facts_md, wiki_summary_md, session_notes } = req.body; if (!token_id) return res.status(400).json({ error: "token_id required (PTP presence token)" }); if (bio_facts_md === undefined && wiki_summary_md === undefined) { return res.status(400).json({ error: "At least one of bio_facts_md or wiki_summary_md is required" }); } try { // Verify the PTP token and check scope coverage for this subject const requiredScope = `corpus:write:personaforge:${slug}`; const ptpResult = await pool.query(`SELECT * FROM ptp_tokens WHERE id = $1`, [token_id]); if (!ptpResult.rows.length) return res.status(403).json({ error: "ptp_token_not_found", token_id }); const tk = ptpResult.rows[0]; const now = new Date(); if (tk.revoked) return res.status(403).json({ error: "ptp_token_revoked", revoked_at: tk.revoked_at }); if (tk.expires_at && new Date(tk.expires_at) < now) { return res.status(403).json({ error: "ptp_token_expired", expired_at: tk.expires_at }); } // Scope check: token scope must equal or be a wildcard superset of corpus:write:personaforge:{slug} const tokenParts = (tk.scope as string).split(":"); const requiredParts = requiredScope.split(":"); let scopeOk = true; for (let i = 0; i < requiredParts.length; i++) { if (tokenParts[i] !== "*" && tokenParts[i] !== requiredParts[i]) { scopeOk = false; break; } } if (!scopeOk) { return res.status(403).json({ error: "scope_insufficient", token_scope: tk.scope, required_scope: requiredScope, hint: `Request a token with scope "corpus:write:personaforge:${slug}" or "corpus:write:personaforge:*"`, }); } // Ensure subject row exists await pool.query( `INSERT INTO personaforge_subjects (slug, subject_name, build_status) VALUES ($1, $1, 'registered') ON CONFLICT (slug) DO NOTHING`, [slug] ); // Write facts + stamp with PTP attestation metadata const attestedAt = new Date().toISOString(); if (bio_facts_md !== undefined) { await pool.query( `UPDATE personaforge_subjects SET bio_facts_md = $2, bio_facts_ptp_token_id = $3, bio_facts_attested_at = $4, bio_facts_vcap_uri = $5, updated_at = NOW() WHERE slug = $1`, [slug, bio_facts_md, token_id, attestedAt, tk.vcap_attestation_uri] ); } if (wiki_summary_md !== undefined) { await pool.query( `UPDATE personaforge_subjects SET wiki_summary_md = $2, wiki_ptp_token_id = $3, wiki_attested_at = $4, updated_at = NOW() WHERE slug = $1`, [slug, wiki_summary_md, token_id, attestedAt] ); } // Log to corpus_consult_log if it exists (non-fatal) await pool.query( `INSERT INTO corpus_consult_log (slug, event_type, token_id, scope, purpose, attested_at, session_notes, created_at) VALUES ($1, 'ptp_bio_injection', $2, $3, $4, $5, $6, NOW())`, [slug, token_id, tk.scope, tk.purpose_declaration, attestedAt, session_notes || null] ).catch(() => {}); // table may not exist in all environments res.json({ ok: true, slug, attested_at: attestedAt, token_id, scope: tk.scope, vcap_attestation_uri: tk.vcac_attestation_uri || tk.vcap_attestation_uri, fields_written: [ ...(bio_facts_md !== undefined ? ["bio_facts_md"] : []), ...(wiki_summary_md !== undefined ? ["wiki_summary_md"] : []), ], verification: `GET https://wellspr.ing/api/v1/ptp/token/${token_id}`, note: "Biographical facts are now stamped with this PTP token. The corpus build will inject them as priority-0 chunks.", }); } catch (e: any) { console.error("[StewardDash] PTP injection error:", e.message); res.status(500).json({ error: e.message }); } }); // ── GET /api/steward/:slug/bio-facts ──────────────────────────────────── app.get("/api/steward/:slug/bio-facts", async (req: Request, res: Response) => { const { slug } = req.params; const token = req.query.token as string; if (!await validateStewardToken(slug, token)) return res.status(403).json({ error: "Invalid token" }); try { const { rows } = await pool.query( `SELECT bio_facts_md, wiki_summary_md FROM personaforge_subjects WHERE slug = $1`, [slug] ); const row = rows[0] || {}; res.json({ bio_facts_md: row.bio_facts_md || null, wiki_summary_md: row.wiki_summary_md || null }); } catch (e: any) { res.status(500).json({ error: e.message }); } }); // ── POST /api/steward/:slug/bio-facts ──────────────────────────────────── // Requires steward token (or admin key). Updates bio_facts_md and/or wiki_summary_md. app.post("/api/steward/:slug/bio-facts", async (req: Request, res: Response) => { const { slug } = req.params; const { token, bio_facts_md, wiki_summary_md } = req.body; const adminKey = (req.headers["x-admin-key"] as string) || req.query.admin_key as string; const isAdmin = adminKey === ADMIN_KEY; if (!isAdmin && !await validateStewardToken(slug, token)) return res.status(403).json({ error: "Invalid token or admin key" }); if (bio_facts_md === undefined && wiki_summary_md === undefined) return res.status(400).json({ error: "Nothing to update" }); try { // Ensure the subject row exists (auto-create if missing, e.g. dev DB or fresh environment) await pool.query( `INSERT INTO personaforge_subjects (slug, subject_name, build_status) VALUES ($1, $1, 'registered') ON CONFLICT (slug) DO NOTHING`, [slug] ); // Update whichever fields were provided if (bio_facts_md !== undefined) { await pool.query( `UPDATE personaforge_subjects SET bio_facts_md = $2, updated_at = NOW() WHERE slug = $1`, [slug, bio_facts_md] ); } if (wiki_summary_md !== undefined) { await pool.query( `UPDATE personaforge_subjects SET wiki_summary_md = $2, updated_at = NOW() WHERE slug = $1`, [slug, wiki_summary_md] ); } res.json({ ok: true }); } catch (e: any) { res.status(500).json({ error: e.message }); } }); // ── POST /api/steward/:slug/artifacts/inject ───────────────────────────── // Admin-only. Inject a manual artifact with full text content (no URL required). // Used for Wikipedia excerpts, articles-about-the-person, curated secondary sources. app.post("/api/steward/:slug/artifacts/inject", async (req: Request, res: Response) => { const { slug } = req.params; const adminKey = (req.headers["x-admin-key"] as string) || req.body.admin_key; if (adminKey !== ADMIN_KEY) return res.status(403).json({ error: "Admin key required" }); const { title, content_text, artifact_type, source_url, pub_date } = req.body; if (!title || !content_text) return res.status(400).json({ error: "title and content_text required" }); const artifactId = `${slug}::injected::${crypto.randomUUID().slice(0, 8)}`; try { await pool.query( `INSERT INTO personaforge_artifacts (subject_slug, artifact_id, title, source_url, artifact_type, content_text, curation_status, pub_date) VALUES ($1, $2, $3, $4, $5, $6, 'approved', $7) ON CONFLICT (artifact_id) DO UPDATE SET content_text = EXCLUDED.content_text, title = EXCLUDED.title`, [slug, artifactId, title, source_url || null, artifact_type || 'injected', content_text, pub_date || null] ); await pool.query(`UPDATE personaforge_subjects SET updated_at = NOW() WHERE slug = $1`, [slug]); res.json({ ok: true, artifact_id: artifactId }); } catch (e: any) { res.status(500).json({ error: e.message }); } }); // ── GET /api/steward/:slug/dashboard-token (admin only) ───────────────── app.get("/api/steward/:slug/dashboard-token", async (req: Request, res: Response) => { const { slug } = req.params; const key = (req.headers["x-admin-key"] as string) || req.query.admin_key as string; if (key !== ADMIN_KEY) return res.status(403).json({ error: "Admin key required" }); const token = await getOrCreateDashboardToken(slug); const dashUrl = `https://agentify.help/steward/${slug}?token=${token}`; res.json({ slug, dashboard_token: token, dashboard_url: dashUrl }); }); // ── POST /api/steward/:slug/invite-co-steward ──────────────────────────── // Invite a collaborator email to co-steward this agent. // They gain access via the /my-agents portfolio magic-link flow. app.post("/api/steward/:slug/invite-co-steward", async (req: Request, res: Response) => { if (!isAgentify(req)) return res.status(404).json({ error: "Not found" }); const { slug } = req.params; const token = req.query.token as string | undefined; const key = (req.headers["x-admin-key"] as string) || (req.query.admin_key as string); if (key !== ADMIN_KEY && !(await validateStewardToken(slug, token))) { return res.status(403).json({ error: "Steward token or admin key required" }); } const { email } = req.body ?? {}; if (!email || !email.includes("@")) return res.status(400).json({ error: "Valid email required" }); const normalized = email.toLowerCase().trim(); await pool.query( `UPDATE agentify_subject_registry SET co_steward_emails = array_append(COALESCE(co_steward_emails, '{}'), $2) WHERE subject_slug = $1 AND NOT ($2 = ANY(COALESCE(co_steward_emails, '{}')))`, [slug, normalized] ).catch(() => {}); await pool.query( `UPDATE personaforge_subjects SET co_steward_emails = array_append(COALESCE(co_steward_emails, '{}'), $2) WHERE slug = $1 AND NOT ($2 = ANY(COALESCE(co_steward_emails, '{}')))`, [slug, normalized] ).catch(() => {}); res.json({ ok: true, slug, co_steward_email: normalized, note: "Co-steward added. They can now use /my-agents with their email to receive a portal access link." }); }); // ── GET /api/steward/:slug/co-stewards ─────────────────────────────────── app.get("/api/steward/:slug/co-stewards", async (req: Request, res: Response) => { if (!isAgentify(req)) return res.status(404).json({ error: "Not found" }); const { slug } = req.params; const token = req.query.token as string | undefined; const key = (req.headers["x-admin-key"] as string) || (req.query.admin_key as string); if (key !== ADMIN_KEY && !(await validateStewardToken(slug, token))) { return res.status(403).json({ error: "Steward token or admin key required" }); } const { rows } = await pool.query( `SELECT co_steward_emails FROM agentify_subject_registry WHERE subject_slug = $1`, [slug] ).catch(() => ({ rows: [] })); res.json({ slug, co_steward_emails: rows[0]?.co_steward_emails || [] }); }); // ── DELETE /api/steward/:slug/co-stewards/:email ───────────────────────── app.delete("/api/steward/:slug/co-stewards/:email", async (req: Request, res: Response) => { if (!isAgentify(req)) return res.status(404).json({ error: "Not found" }); const { slug, email } = req.params; const token = req.query.token as string | undefined; const key = (req.headers["x-admin-key"] as string) || (req.query.admin_key as string); if (key !== ADMIN_KEY && !(await validateStewardToken(slug, token))) { return res.status(403).json({ error: "Steward token or admin key required" }); } const normalized = decodeURIComponent(email).toLowerCase().trim(); await pool.query( `UPDATE agentify_subject_registry SET co_steward_emails = array_remove(COALESCE(co_steward_emails, '{}'), $2) WHERE subject_slug = $1`, [slug, normalized] ).catch(() => {}); await pool.query( `UPDATE personaforge_subjects SET co_steward_emails = array_remove(COALESCE(co_steward_emails, '{}'), $2) WHERE slug = $1`, [slug, normalized] ).catch(() => {}); res.json({ ok: true, slug, removed: normalized }); }); } // ── Approved-only corpus build (async) ─────────────────────────────────────── import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; const S3_HOSTNAME = process.env.VULTR_S3_HOSTNAME || "ewr1.vultrobjects.com"; const S3_BUCKET = process.env.CORPUS_S3_BUCKET || "agentify-corpus"; function getS3(): S3Client { return new S3Client({ endpoint: `https://${S3_HOSTNAME}`, region: "ewr1", forcePathStyle: true, credentials: { accessKeyId: process.env.VULTR_S3_ACCESS_KEY || "", secretAccessKey: process.env.VULTR_S3_SECRET_KEY || "", }, }); } function chunkText(text: string, maxWords = 400): string[] { const sentences = text.split(/(?<=[.!?])\s+/); const chunks: string[] = []; let current = ""; for (const s of sentences) { const proposed = current ? current + " " + s : s; if (proposed.split(/\s+/).length > maxWords && current) { chunks.push(current.trim()); current = s; } else { current = proposed; } } if (current.trim()) chunks.push(current.trim()); return chunks.filter(c => c.split(/\s+/).length >= 30); } async function broadcastStewardLog(slug: string, msg: string) { console.log(`[StewardBuild/${slug}] ${msg}`); await pool.query( `UPDATE personaforge_subjects SET build_log = COALESCE(build_log,'') || $2, updated_at=NOW() WHERE slug=$1`, [slug, `[${new Date().toISOString().slice(11, 19)}] ${msg}\n`] ).catch(() => {}); } async function runApprovedBuild(slug: string, bundleId: string, approvedIds: string[]) { const s3 = getS3(); const allChunks: any[] = []; let chunkSeq = 0; let processed = 0; let errors = 0; const { rows: pf } = await pool.query( `SELECT subject_name, domain, corpus_version, bio_facts_md, wiki_summary_md FROM personaforge_subjects WHERE slug = $1`, [slug] ); const subject = pf[0] || { subject_name: slug, domain: "", corpus_version: "1.0.0" }; await broadcastStewardLog(slug, `Starting approved-only build: ${approvedIds.length} artifacts, bundle ${bundleId}`); // ── Priority chunk 0: Biographical baseline facts ────────────────────── if (subject.bio_facts_md && subject.bio_facts_md.trim()) { const bioChunks = chunkText(subject.bio_facts_md); for (const chunk of bioChunks) { allChunks.push({ chunk_id: `${slug}-${bundleId}-bio-${String(chunkSeq++).padStart(5, "0")}`, subject_slug: slug, artifact_id: `${slug}::bio_facts_baseline`, title: "Biographical Baseline — Ground-Truth Facts", pub_date: null, artifact_type: "bio_facts", priority: "critical", text: chunk, }); } await broadcastStewardLog(slug, `Injected biographical baseline (${bioChunks.length} chunks) as priority-0 corpus anchor.`); } // ── Priority chunk 1: Wikipedia / secondary sources ──────────────────── if (subject.wiki_summary_md && subject.wiki_summary_md.trim()) { const wikiPreamble = `[SKEPTICISM NOTE: The following is from Wikipedia and secondary sources. Treat as background reference, not first-person memory. Verify contested facts against primary corpus.]\n\n`; const wikiChunks = chunkText(wikiPreamble + subject.wiki_summary_md); for (const chunk of wikiChunks) { allChunks.push({ chunk_id: `${slug}-${bundleId}-wiki-${String(chunkSeq++).padStart(5, "0")}`, subject_slug: slug, artifact_id: `${slug}::wiki_secondary_sources`, title: "Wikipedia & Secondary Sources — Skepticism-Weighted", pub_date: null, artifact_type: "wiki_secondary", priority: "background", text: chunk, }); } await broadcastStewardLog(slug, `Injected Wikipedia/secondary sources (${wikiChunks.length} chunks) with skepticism weighting.`); } for (const artifactId of approvedIds) { const { rows: arts } = await pool.query( `SELECT title, source_url, pub_date, artifact_type, content_text FROM personaforge_artifacts WHERE artifact_id = $1`, [artifactId] ); if (!arts[0]) continue; const art = arts[0]; try { await broadcastStewardLog(slug, `Fetching: ${art.title}`); // Use inline content_text if present (injected artifacts), otherwise fetch from URL const text = art.content_text || (art.source_url ? await fetchEssayTextBuild(art.source_url) : ""); if (!text || text.split(/\s+/).length < 50) { await broadcastStewardLog(slug, `Skipped (too short): ${art.title}`); errors++; continue; } const chunks = chunkText(text); const wc = text.split(/\s+/).length; for (const chunk of chunks) { allChunks.push({ chunk_id: `${slug}-${bundleId}-${String(chunkSeq++).padStart(5, "0")}`, subject_slug: slug, artifact_id: artifactId, title: art.title, pub_date: art.pub_date || null, artifact_type: art.artifact_type || "essay", text: chunk, }); } await pool.query( `UPDATE personaforge_artifacts SET chunk_count = $1, word_count = $2 WHERE artifact_id = $3`, [chunks.length, wc, artifactId] ); processed++; } catch (e: any) { await broadcastStewardLog(slug, `Error on ${art.title}: ${e.message}`); errors++; } } await broadcastStewardLog(slug, `All artifacts processed. ${allChunks.length} chunks total. Uploading to S3…`); try { const prefix = `staging/${slug}/v${subject.corpus_version}/${bundleId}`; const ndjson = allChunks.map(c => JSON.stringify(c)).join("\n"); await s3.send(new PutObjectCommand({ Bucket: S3_BUCKET, Key: `${prefix}/chunks/index.ndjson`, Body: ndjson, ContentType: "application/x-ndjson", })); const manifest = { bundle_id: bundleId, subject_slug: slug, subject_name: subject.subject_name, domain: subject.domain, corpus_version: subject.corpus_version, chunk_count: allChunks.length, artifact_count: processed, approved_only: true, built_at: new Date().toISOString(), }; await s3.send(new PutObjectCommand({ Bucket: S3_BUCKET, Key: `${prefix}/manifest.json`, Body: JSON.stringify(manifest, null, 2), ContentType: "application/json", })); await pool.query( `UPDATE personaforge_subjects SET build_status = 'built', chunk_count = $2, updated_at = NOW() WHERE slug = $1`, [slug, allChunks.length] ); await broadcastStewardLog(slug, `Build complete. ${allChunks.length} chunks uploaded. Bundle: ${bundleId}`); if (errors > 0) await broadcastStewardLog(slug, `Note: ${errors} artifacts had errors and were skipped.`); } catch (e: any) { await broadcastStewardLog(slug, `S3 upload error: ${e.message}`); await pool.query(`UPDATE personaforge_subjects SET build_status = 'error' WHERE slug = $1`, [slug]); } } async function fetchEssayTextBuild(url: string): Promise { try { const html = await fetch(url, { headers: { "User-Agent": "WellSpr.ing PersonaForge/1.0" }, signal: AbortSignal.timeout(20000), }).then(r => r.text()); return html .replace(/]*>[\s\S]*?<\/script>/gi, " ") .replace(/]*>[\s\S]*?<\/style>/gi, " ") .replace(/<[^>]+>/g, " ") .replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/ /g, " ") .replace(/\s+/g, " ").trim(); } catch { return ""; } }