From c2b84a1f26e9ed6bd9637d99426712a59a61f5fb Mon Sep 17 00:00:00 2001 From: Keysat Date: Mon, 8 Jun 2026 18:48:24 -0500 Subject: [PATCH] =?UTF-8?q?architect:=20LP=20Objections=20page=20=E2=80=94?= =?UTF-8?q?=20UI=20trigger=20for=20the=20grounding=20pass=20(v0.1.0:66)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New admin "LP Objections" page (frontend ObjectionsPage + nav). Pick a segment (or All LPs) and Run grounding: the Architect mines matched LP emails + notes on the local model, scrubs every identifier through the redaction boundary, and asks Claude for the recurring objections + honest rebuttals (substantiated/hand-wavy flagged). Renders the de-identified draft + an "N identifiers protected" badge; fail-closed statuses (local_model_unavailable / scrub_unavailable / claude_not_configured / rehydrate_failed) show a clear message. Uses the existing /api/architect/ground route. Verified in preview: page + segment selector + Run; the local minimize/scrub legs actually ran against real Spark on synthetic input and fail-closed correctly at the (key-less) Claude step; success rendering verified with a mocked ok response. NOT yet deployed — start-cli RPC to the box hit a transient transport error post a StartOS hiccup (curl works, start-cli doesn't); CRM healthy at v0.1.0:65 meanwhile. Co-Authored-By: Claude Opus 4.8 --- frontend/index.html | 93 ++++++++++++++++++++++++ start9/0.4/startos/utils.ts | 5 +- start9/0.4/startos/versions/index.ts | 5 +- start9/0.4/startos/versions/v0.1.0.66.ts | 19 +++++ 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 start9/0.4/startos/versions/v0.1.0.66.ts diff --git a/frontend/index.html b/frontend/index.html index c4e41b9..5e8ff43 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -9948,6 +9948,92 @@ ); }; + const ObjectionsPage = ({ token, user, onShowToast }) => { + const isAdmin = user?.role === 'admin'; + const [segment, setSegment] = useState(''); + const [running, setRunning] = useState(false); + const [result, setResult] = useState(null); + const SEGMENTS = [ + ['', 'All LPs'], + ['btc_native_hnwi', 'Bitcoin-native HNWIs'], + ['institution', 'Institutions'], + ['family_office', 'Family offices'], + ['smaller_accredited', 'Smaller accredited ($100k)'], + ['ai_energy_operator', 'AI & energy operators'], + ]; + const FAIL = { + local_model_unavailable: 'The local model (Spark) is unavailable, so nothing was sent to Claude. Try again once it is reachable.', + scrub_unavailable: 'The redaction boundary could not be prepared, so nothing was sent to Claude.', + claude_not_configured: 'The Architect (Claude) is not configured on the server.', + rehydrate_failed: 'Claude returned an unexpected placeholder; the draft was quarantined for safety. Nothing de-anonymized was shown.', + }; + + const run = async () => { + if (running) return; + try { + setRunning(true); + setResult(null); + const res = await api('/api/architect/ground', { + method: 'POST', + body: JSON.stringify(segment ? { segment_key: segment } : {}), + }, token); + setResult(res.data || res); + } catch (err) { + const msg = getErrorMessage(err, 'Grounding failed'); + setResult({ status: 'error', reason: msg }); + onShowToast(msg, 'error'); + } finally { + setRunning(false); + } + }; + + if (!isAdmin) return
Admin only.
; + const ok = result && result.status === 'ok'; + + return ( +
+

LP Objections

+
+
+ The Architect reads your matched LP emails and notes on the local model, removes every identifier through the redaction boundary, and asks Claude for the recurring objections and the strongest honest rebuttals. Only de-identified themes ever leave Ten31 — no names, firms, amounts, or addresses. Results are a draft for your review. +
+
+ + +
+
+ + {running &&
} + + {result && !running && ( +
+ {ok ? ( + <> +
+ Recurring objections & rebuttals + {result.scrub_stats && result.scrub_stats.tokens != null + ? {result.scrub_stats.tokens} identifiers protected : null} +
+
{result.draft}
+
+ Draft for review. Use these to pressure-test the thesis in the Workshop. +
+ + ) : ( +
+ {FAIL[result.status] || result.reason || 'Grounding did not complete.'} +
+ )} +
+ )} +
+ ); + }; + const EmailCapturePage = ({ token, user, onShowToast }) => { const isAdmin = user?.role === 'admin'; const [status, setStatus] = useState(null); @@ -10751,6 +10837,11 @@ + {user?.role === 'admin' && ( + + )} @@ -10786,6 +10877,7 @@ {page === 'communications' && 'Communications'} {page === 'thesis' && 'Thesis'} {page === 'thesis-workshop' && 'Thesis Workshop'} + {page === 'objections' && 'LP Objections'} {page === 'system-status' && 'System Status'} {page === 'email-capture' && 'Email Capture'} {page === 'feature-requests' && 'Feature Requests'} @@ -10818,6 +10910,7 @@ {page === 'communications' && } {page === 'thesis' && } {page === 'thesis-workshop' && } + {page === 'objections' && } {page === 'system-status' && } {page === 'email-capture' && } {page === 'feature-requests' && } diff --git a/start9/0.4/startos/utils.ts b/start9/0.4/startos/utils.ts index 3130be4..5a5a3f2 100644 --- a/start9/0.4/startos/utils.ts +++ b/start9/0.4/startos/utils.ts @@ -30,8 +30,9 @@ export const PACKAGE_TITLE = 'Ten31 Database' // * 0.1.0:62 (fix backfill crash on no-Reply-To emails; Sync now retries errored mailboxes) // * 0.1.0:63 (System Status: storage usage — DB, attachments, backups, disk free) // * 0.1.0:64 (email-activity agent: propose->review->approve grid notes; sync ~15 min) -// * Current: 0.1.0:65 (Email Capture: per-mailbox captured/matched counts) -export const PACKAGE_VERSION = '0.1.0:65' +// * 0.1.0:65 (Email Capture: per-mailbox captured/matched counts) +// * Current: 0.1.0:66 (LP Objections page: UI trigger for the Architect grounding pass) +export const PACKAGE_VERSION = '0.1.0:66' export const DATA_MOUNT_PATH = '/data' export const WEB_PORT = 8080 diff --git a/start9/0.4/startos/versions/index.ts b/start9/0.4/startos/versions/index.ts index d51faa9..e36858d 100644 --- a/start9/0.4/startos/versions/index.ts +++ b/start9/0.4/startos/versions/index.ts @@ -26,8 +26,9 @@ import { v_0_1_0_62 } from './v0.1.0.62' import { v_0_1_0_63 } from './v0.1.0.63' import { v_0_1_0_64 } from './v0.1.0.64' import { v_0_1_0_65 } from './v0.1.0.65' +import { v_0_1_0_66 } from './v0.1.0.66' export const versionGraph = VersionGraph.of({ - current: v_0_1_0_65, - other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52, v_0_1_0_53, v_0_1_0_54, v_0_1_0_55, v_0_1_0_56, v_0_1_0_57, v_0_1_0_58, v_0_1_0_59, v_0_1_0_60, v_0_1_0_61, v_0_1_0_62, v_0_1_0_63, v_0_1_0_64], + current: v_0_1_0_66, + other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52, v_0_1_0_53, v_0_1_0_54, v_0_1_0_55, v_0_1_0_56, v_0_1_0_57, v_0_1_0_58, v_0_1_0_59, v_0_1_0_60, v_0_1_0_61, v_0_1_0_62, v_0_1_0_63, v_0_1_0_64, v_0_1_0_65], }) diff --git a/start9/0.4/startos/versions/v0.1.0.66.ts b/start9/0.4/startos/versions/v0.1.0.66.ts new file mode 100644 index 0000000..ba7aade --- /dev/null +++ b/start9/0.4/startos/versions/v0.1.0.66.ts @@ -0,0 +1,19 @@ +import { VersionInfo } from '@start9labs/start-sdk' + +// LP Objections page: a UI trigger for the Architect's grounding pass. An admin picks +// a segment (or All LPs) and runs grounding — the Architect mines matched LP emails and +// notes on the local model, removes every identifier through the redaction boundary, +// and asks Claude for the recurring objections + honest rebuttals. Only de-identified +// themes leave Ten31. Uses the existing /api/architect/ground route; no schema change. +export const v_0_1_0_66 = VersionInfo.of({ + version: '0.1.0:66', + releaseNotes: { + en_US: [ + 'New LP Objections page: run the Architect grounding pass over your matched LP email', + 'to surface the recurring objections (and the strongest honest rebuttals) for any', + 'segment. Everything sensitive is de-identified on your own hardware before Claude sees', + 'it — only objection themes leave Ten31. Results are a draft for your review.', + ].join(' '), + }, + migrations: { up: async () => {}, down: async () => {} }, +})