From fc09003390e7aabf3066fff1eefed35dcb4bcb8d Mon Sep 17 00:00:00 2001 From: Jer Miller Date: Sun, 12 Jul 2026 06:16:22 -0600 Subject: [PATCH] feat(thinking): live enable confidential processing Replace the confidential teaser with a real setup view, live lane card, bounded cancellable polling, and a recheck action that returns the refreshed providers payload. The off lane now reports off instead of verifying, verified requires current owner-safe provenance, and last-known verified provenance is exposed separately so it is never rendered as current verification. Move the confidential trust beats and state/action copy into shared constants, update the init route to confidential-setup, and refresh the API baseline from the providers route. The client treats early_access as terminal and keeps the lane off instead of rendering it as an error or provisioning success; blocked attestation states expose check again and continue to fail closed without inference content. Co-Authored-By: Claude Opus 4.8 (1M context) --- solstone/apps/network/workspace.html | 2 +- solstone/apps/thinking/copy.py | 88 ++++- solstone/apps/thinking/routes.py | 107 +++++- solstone/apps/thinking/static/thinking.js | 347 +++++++++++++++++- solstone/apps/thinking/tests/conftest.py | 4 +- solstone/apps/thinking/tests/js_extract.py | 51 +++ .../test_confidential_attestation_payload.py | 110 +++++- .../tests/test_confidential_routes.py | 80 +++- .../tests/test_providers_payload_extended.py | 1 + .../test_thinking_confidential_poll_js.py | 184 ++++++++++ .../tests/test_thinking_install_poll_js.py | 68 +--- .../thinking/tests/test_workspace_html.py | 128 ++++++- solstone/apps/thinking/workspace.html | 46 ++- solstone/convey/root.py | 2 +- tests/baselines/api/thinking/providers.json | 5 +- tests/test_init.py | 2 +- tests/test_settings_provider_card_js.py | 17 +- tests/test_thinking_call_parity.py | 5 +- 18 files changed, 1109 insertions(+), 138 deletions(-) create mode 100644 solstone/apps/thinking/tests/js_extract.py create mode 100644 solstone/apps/thinking/tests/test_thinking_confidential_poll_js.py diff --git a/solstone/apps/network/workspace.html b/solstone/apps/network/workspace.html index 77b1b27a1..cd5003139 100644 --- a/solstone/apps/network/workspace.html +++ b/solstone/apps/network/workspace.html @@ -595,7 +595,7 @@ let lastPosture = null; let reachRevealed = false; let linkInitialized = false; - // Mirror of operations.TERMINAL_PHASES (solstone/think/services/operations.py) — keep in lockstep. + // Private-link product phases that finish the setup poll. const PRIVATE_LINK_TERMINAL_PHASES = new Set(['enabled', 'needs_subscription', 'revoked', 'error']); const PRIVATE_LINK_POLL_TIMEOUT_MS = 900000; let privateLinkRetryAction = null; diff --git a/solstone/apps/thinking/copy.py b/solstone/apps/thinking/copy.py index 17f862e03..e8e7b17cd 100644 --- a/solstone/apps/thinking/copy.py +++ b/solstone/apps/thinking/copy.py @@ -21,8 +21,7 @@ LANES = [ "id": "confidential", "label": "Confidential processing", "sub": "operated by sol pbc", - "tag": "not open yet", - "description": "coming — scouts get it first.", + "description": "sol pbc runs the model on confidential GPUs.", }, { "id": "byo", @@ -34,25 +33,53 @@ LANES = [ ), }, ] +CONFIDENTIAL_TRUST_HEADING = "confidential processing" +CONFIDENTIAL_TRUST_SUB = "operated by sol pbc" +CONFIDENTIAL_TRUST_EGRESS = "only the thinking leaves — the text and images sol works through. your journal stays on this computer, and your recordings stay here too (speech becomes text on your device first)." +CONFIDENTIAL_TRUST_CLAIMS = ( + "no content is retained · no human reviews it · nothing is used to train" +) +CONFIDENTIAL_TRUST_FAIL_CLOSED = "your journal must verify the service before anything is sent — if it can't verify, it doesn't send." +CONFIDENTIAL_TRUST_SUBSTRATE = "sol pbc runs the model itself on confidential GPUs in Microsoft Azure. the hardware boundary keeps the cloud host excluded from what's processed — no third-party AI provider is in the path." +CONFIDENTIAL_EARLY_ACCESS = "confidential processing is coming — scouts get it first." +CONFIDENTIAL_TRUST_BEATS = { + "heading": CONFIDENTIAL_TRUST_HEADING, + "sub": CONFIDENTIAL_TRUST_SUB, + "egress": CONFIDENTIAL_TRUST_EGRESS, + "claims": CONFIDENTIAL_TRUST_CLAIMS, + "attestation": CONFIDENTIAL_TRUST_FAIL_CLOSED, + "substrate": CONFIDENTIAL_TRUST_SUBSTRATE, +} CONFIDENTIAL_LANE_DETAIL = { - "heading": "confidential processing", - "sub": "operated by sol pbc · not open yet", - "mechanism": ( - "let sol " - "think off your device — on confidential hardware sol pbc runs " - "that keeps nothing." - ), - "egress": ( - "when it opens, sol will send only the thinking off your device — never " - "your journal, which stays here on this computer. it runs on confidential " - "hardware sol pbc operates." - ), - "claims": "no content is retained · no human reviews it · nothing is used to train", - "attestation": "your journal checks the hardware before it sends anything.", - "early_access": ( - "scouts get confidential processing first. it isn't open yet — when it " - "is, the scout program is the way in." - ), + "heading": CONFIDENTIAL_TRUST_HEADING, + "sub": CONFIDENTIAL_TRUST_SUB, + "mechanism": CONFIDENTIAL_TRUST_SUBSTRATE, + "egress": CONFIDENTIAL_TRUST_EGRESS, + "claims": CONFIDENTIAL_TRUST_CLAIMS, + "attestation": CONFIDENTIAL_TRUST_FAIL_CLOSED, + "early_access": CONFIDENTIAL_EARLY_ACCESS, +} +CONFIDENTIAL_SETUP = { + "trust_beats": dict(CONFIDENTIAL_TRUST_BEATS), +} +CONFIDENTIAL_ATTESTATION_STATES = { + "off": "", + "verifying": "checking the hardware…", + "verified": "checked {checked}", + "failed": "couldn't verify the service — sol isn't sending.", + "stale": "your journal needs to re-check the service before sending.", + "unreachable": "can't reach confidential processing right now — sol isn't sending.", +} +CONFIDENTIAL_OPERATION_STATES = { + "starting": "opening your browser to confirm…", + "waiting": "finish turning it on in your browser", + "early_access": CONFIDENTIAL_EARLY_ACCESS, + "repair_needed": "couldn't verify the service — sol isn't sending.", +} +CONFIDENTIAL_ACTIONS = { + "off": "turn on confidential processing →", + "enabled": "turn off", + "recheck": "check again", } ACTIVE_LANE_LABELS = { "none": "not thinking yet", @@ -112,6 +139,21 @@ GLANCE = { "your journal" ), }, + "confidential_checking": { + "label": "sol is waiting on", + "value": "confidential processing", + "detail": CONFIDENTIAL_ATTESTATION_STATES["verifying"], + }, + "confidential_verified": { + "label": "sol is thinking with", + "value": "confidential processing", + "detail": CONFIDENTIAL_ATTESTATION_STATES["verified"], + }, + "confidential_blocked": { + "label": "sol is holding", + "value": "confidential processing", + "detail": "{message}", + }, "none": { "value": "not thinking yet", "detail": ( @@ -230,6 +272,12 @@ def thinking_copy_payload() -> dict[str, Any]: "confidential": { "lane_detail": dict(CONFIDENTIAL_LANE_DETAIL), "more_label": CONFIDENTIAL_MORE_LABEL, + "setup": { + "trust_beats": dict(CONFIDENTIAL_SETUP["trust_beats"]), + }, + "attestation_states": dict(CONFIDENTIAL_ATTESTATION_STATES), + "operation_states": dict(CONFIDENTIAL_OPERATION_STATES), + "actions": dict(CONFIDENTIAL_ACTIONS), }, "glance": dict(GLANCE), "byo_setup": dict(BYO_SETUP), diff --git a/solstone/apps/thinking/routes.py b/solstone/apps/thinking/routes.py index 2e2919373..2deff0115 100644 --- a/solstone/apps/thinking/routes.py +++ b/solstone/apps/thinking/routes.py @@ -62,8 +62,16 @@ from solstone.think.providers.local_endpoint import ( normalize_local_endpoint_url, resolve_local_endpoint, ) -from solstone.think.services import operations, scout, scout_handoff, spp, spp_handoff +from solstone.think.services import ( + operations, + scout, + scout_handoff, + spp, + spp_handoff, + spp_transport, +) from solstone.think.services.constants import SERVICE_SPP +from solstone.think.services.spp_attest.cadence import AttestationSession from solstone.think.utils import CorruptConfigError, get_journal from solstone.think.utils import get_config as get_journal_config @@ -103,6 +111,7 @@ _CONFIDENTIAL_PHASE_TO_PRODUCT = { "starting": "starting", "waiting": "waiting", "enabled": "not_verified", + "early_access": "early_access", "error": "repair_needed", } @@ -116,30 +125,85 @@ def _remap_confidential_operation(raw: dict[str, Any] | None) -> dict[str, Any] return payload -def _confidential_attestation_payload(now: datetime) -> dict[str, Any]: +def _owner_safe_attestation_provenance( + session: AttestationSession, +) -> dict[str, Any] | None: + verdict = session.verdict + checked_at = verdict.checked_at + if not verdict.legs or not verdict.substrate.strip() or checked_at is None: + return None + return { + "legs": list(verdict.legs), + "substrate": verdict.substrate, + "checked_at": checked_at.astimezone(timezone.utc).isoformat(), + } + + +def _confidential_attestation_payload( + now: datetime, + *, + configured: bool | None = None, +) -> dict[str, Any]: + if configured is None: + configured = spp.confidential_provenance() is not None + if not configured: + return { + "state": "off", + "provenance": None, + "last_verified": None, + "reason": "confidential_not_configured", + } + state = spp.get_attestation_state() + last_verified = ( + _owner_safe_attestation_provenance(state.last_verified) + if state.last_verified is not None + else None + ) if state.failure is not None: - return {"state": "failed", "provenance": None, "reason": "attestation_failed"} + if state.failure.kind == "unreachable": + return { + "state": "unreachable", + "provenance": None, + "last_verified": last_verified, + "reason": "attestation_unreachable", + } + return { + "state": "failed", + "provenance": None, + "last_verified": last_verified, + "reason": "attestation_failed", + } session = state.session if session is None: return { "state": "verifying", "provenance": None, + "last_verified": last_verified, "reason": "attestation_not_yet_verified", } if session.status(now) == "stale": - return {"state": "stale", "provenance": None, "reason": "attestation_stale"} + return { + "state": "stale", + "provenance": None, + "last_verified": last_verified, + "reason": "attestation_stale", + } - checked_at = session.verdict.checked_at.astimezone(timezone.utc).isoformat() + provenance = _owner_safe_attestation_provenance(session) + if provenance is None: + return { + "state": "failed", + "provenance": None, + "last_verified": last_verified, + "reason": "attestation_failed", + } return { "state": "verified", - "provenance": { - "legs": list(session.verdict.legs), - "substrate": session.verdict.substrate, - "checked_at": checked_at, - }, + "provenance": provenance, + "last_verified": provenance, "reason": None, } @@ -328,7 +392,8 @@ def _active_lane_payload( operations.operation_for_service(SERVICE_SPP) ), "confidential_attestation": _confidential_attestation_payload( - datetime.now(timezone.utc) + datetime.now(timezone.utc), + configured=confidential_provenance_present, ), } @@ -489,6 +554,11 @@ def _provider_payload(config: dict[str, Any], local_model_id: str) -> dict[str, } +def _default_provider_payload() -> dict[str, Any]: + local_model_id = local_bootstrap.accepted_request_model(None) or LOCAL_MODEL + return _provider_payload(get_journal_config(), local_model_id) + + def _local_model_error(model: str) -> Any: return error_response( INVALID_REQUEST_VALUE, @@ -673,6 +743,21 @@ def confidential_disable() -> Any: return _thinking_operation_failed() +@thinking_bp.route("/api/confidential/recheck", methods=["POST"]) +def confidential_recheck() -> Any: + try: + if spp.confidential_provenance() is None: + return error_response( + INVALID_OPERATION_FOR_STATE, + detail="confidential processing is not set up.", + ) + spp_transport.recheck_confidential_attestation() + return jsonify(_default_provider_payload()) + except Exception: + logger.exception("error rechecking confidential processing") + return _thinking_operation_failed() + + @thinking_bp.route("/api/keys", methods=["GET", "PUT"]) def keys() -> Any: try: diff --git a/solstone/apps/thinking/static/thinking.js b/solstone/apps/thinking/static/thinking.js index f5012c258..c7080f494 100644 --- a/solstone/apps/thinking/static/thinking.js +++ b/solstone/apps/thinking/static/thinking.js @@ -10,6 +10,7 @@ scout: null, install: null, installPollGeneration: 0, + confidentialPollGeneration: 0, confidentialDetailOpen: false, selectedByoProvider: '', byoMode: 'pick', @@ -18,11 +19,13 @@ let copy = {}; let scoutCopy = {}; const scoutTerminalPhases = new Set(['invited', 'requested', 'ended', 'repair_needed']); + const confidentialTerminalPhases = new Set(['not_verified', 'repair_needed', 'early_access']); const installInFlightStates = new Set(['resolving', 'downloading', 'verifying', 'installing']); const installTerminalStates = new Set(['idle', 'installed', 'failed']); const pollIntervalMs = 1500; const scoutPollMaxMs = 15 * 60 * 1000; - const views = new Set(['main', 'byo-setup', 'local-setup', 'lane-switch']); + const confidentialPollMaxMs = 15 * 60 * 1000; + const views = new Set(['main', 'byo-setup', 'confidential-setup', 'local-setup', 'lane-switch']); const providerEnv = { anthropic: 'ANTHROPIC_API_KEY', google: 'GOOGLE_API_KEY', @@ -173,6 +176,140 @@ return true; } + function confidentialOperationIsTerminal(operation) { + return !operation || confidentialTerminalPhases.has(operation.phase || ''); + } + + function confidentialOperationRender(operation, text) { + const phase = operation?.phase || ''; + const states = text?.operation_states || {}; + if (phase === 'starting' || phase === 'waiting') { + return {message: states[phase] || '', tone: '', active: true}; + } + if (phase === 'early_access') { + return {message: states.early_access || '', tone: '', active: false}; + } + if (phase === 'not_verified') { + return {message: '', tone: '', active: false}; + } + if (phase === 'repair_needed') { + return {message: states.repair_needed || '', tone: 'error', active: false}; + } + return {message: operation?.guidance || '', tone: '', active: false}; + } + + function confidentialAttestationRender(attestation, text, checkedLabel = '') { + const stateName = attestation?.state || 'off'; + const states = text?.attestation_states || {}; + const actions = text?.actions || {}; + if (stateName === 'verified' && attestation?.provenance) { + return { + pill: 'active', + tone: 'hot', + message: formatCopy(states.verified || '', {checked: checkedLabel}), + action: actions.enabled || '', + recheck: false, + disabled: false, + }; + } + if (stateName === 'failed' || stateName === 'stale' || stateName === 'unreachable') { + return { + pill: stateName === 'unreachable' ? 'unreachable' : 'not ready', + tone: 'bad', + message: states[stateName] || '', + action: actions.enabled || '', + recheck: true, + disabled: false, + }; + } + if (stateName === 'verifying') { + return { + pill: 'checking', + tone: '', + message: states.verifying || '', + action: actions.enabled || '', + recheck: false, + disabled: false, + }; + } + return { + pill: 'off', + tone: '', + message: '', + action: actions.off || '', + recheck: false, + disabled: false, + }; + } + + function confidentialGlanceForAttestation(attestation, text, checkedLabel = '') { + const glance = text?.glance || {}; + const stateName = attestation?.state || 'off'; + if (stateName === 'verified' && attestation?.provenance) { + const row = glance.confidential_verified || {}; + return { + label: row.label || glance.lane_label || '', + value: row.value || '', + detail: formatCopy(row.detail || '', {checked: checkedLabel}), + }; + } + if (stateName === 'verifying') { + const row = glance.confidential_checking || {}; + return { + label: row.label || '', + value: row.value || '', + detail: row.detail || '', + }; + } + const row = glance.confidential_blocked || {}; + const message = text?.confidential?.attestation_states?.[stateName] || ''; + return { + label: row.label || '', + value: row.value || '', + detail: formatCopy(row.detail || '', {message}), + }; + } + + async function pollConfidentialUntilTerminal({ + fetchStatus, + sleepFn, + applyStatus, + isCurrent, + intervalMs, + maxElapsedMs, + initialStatus = null, + nowFn = Date.now, + }) { + const started = nowFn(); + if (initialStatus) { + applyStatus(initialStatus); + if (confidentialOperationIsTerminal(initialStatus.active_lane?.confidential_operation)) return initialStatus; + } + while (isCurrent() && nowFn() - started < maxElapsedMs) { + const status = await fetchStatus(); + applyStatus(status); + if (confidentialOperationIsTerminal(status?.active_lane?.confidential_operation)) return status; + await sleepFn(intervalMs); + } + if (!isCurrent()) return null; + throw new Error('confidential setup timed out'); + } + + function handleConfidentialPollError({ + generation, + currentGeneration, + clearOperation, + stopPoll, + showError, + error, + }) { + if (generation !== currentGeneration()) return false; + clearOperation(); + stopPoll(); + showError(error?.message || ''); + return true; + } + function formatCopy(template, values = {}) { return String(template || '').replace(/\{(\w+)\}/g, (_, key) => values[key] ?? ''); } @@ -275,6 +412,9 @@ setMessage('localSetupMessage', err.message, 'error'); }); } + if (target !== 'confidential-setup') { + stopConfidentialPoll(); + } document.querySelectorAll('#providers [data-view]').forEach((section) => { section.hidden = section.dataset.view !== target; }); @@ -462,12 +602,12 @@ setText('thinkingActiveValue', row.value || ''); setText('thinkingActiveDetail', row.detail || ''); } else if (brain.kind === 'confidential') { - setText('thinkingActiveLane', 'sol is waiting on'); - setText('thinkingActiveValue', 'confidential verification'); - setText( - 'thinkingActiveDetail', - 'hardware attestation is not yet verified, so thinking is blocked before anything leaves your journal.', - ); + const attestation = activeLanePayload().confidential_attestation || {}; + const checked = relativeTime(attestation.provenance?.checked_at || ''); + const row = confidentialGlanceForAttestation(attestation, copy, checked); + setText('thinkingActiveLane', row.label || ''); + setText('thinkingActiveValue', row.value || ''); + setText('thinkingActiveDetail', row.detail || ''); } else if (brain.kind === 'advanced') { setText('thinkingActiveLane', 'sol is thinking with'); setText('thinkingActiveValue', 'an advanced split'); @@ -541,6 +681,108 @@ }; } + function confidentialCheckedLabel(attestation) { + return relativeTime(attestation?.provenance?.checked_at || ''); + } + + function renderConfidentialTrust() { + const beats = copy.confidential?.setup?.trust_beats || {}; + setText('confidentialSetupHeading', beats.heading || ''); + setText('confidentialSetupSub', beats.sub || ''); + setText('confidentialTrustHeading', beats.heading || ''); + setText('confidentialTrustSub', beats.sub || ''); + setText('confidentialTrustEgress', beats.egress || ''); + setText('confidentialTrustClaims', beats.claims || ''); + setText('confidentialTrustFailClosed', beats.attestation || ''); + setText('confidentialTrustSubstrate', beats.substrate || ''); + } + + function renderConfidentialCard() { + const active = activeLanePayload(); + const lane = laneCopy('confidential'); + const attestation = active.confidential_attestation || {state: 'off'}; + const operation = active.confidential_operation; + const confidentialCopy = copy.confidential || {}; + const checked = confidentialCheckedLabel(attestation); + const rendered = confidentialAttestationRender(attestation, confidentialCopy, checked); + const operationRendered = confidentialOperationRender(operation, confidentialCopy); + const operationActive = !!operation && !confidentialOperationIsTerminal(operation); + + setCardActive('confidential', activeBrain().kind === 'confidential'); + setPill( + 'confidentialLanePill', + operationActive ? operation.phase || '' : rendered.pill, + operationActive ? '' : rendered.tone, + ); + setText( + 'confidentialLaneDescription', + operationRendered.message || rendered.message || lane.description || '', + ); + setText( + 'confidentialLaneStatus', + operationActive + ? 'continue in browser →' + : attestation.state === 'off' + ? confidentialCopy.actions?.off || '' + : rendered.recheck + ? confidentialCopy.actions?.recheck || '' + : 'manage →', + ); + } + + function renderConfidentialSetup() { + renderConfidentialTrust(); + const active = activeLanePayload(); + const attestation = active.confidential_attestation || {state: 'off'}; + const operation = active.confidential_operation; + const configured = !!active.confidential_provenance_configured; + const confidentialCopy = copy.confidential || {}; + const checked = confidentialCheckedLabel(attestation); + const rendered = confidentialAttestationRender(attestation, confidentialCopy, checked); + const operationRendered = confidentialOperationRender(operation, confidentialCopy); + const operationActive = !!operation && !confidentialOperationIsTerminal(operation); + const phase = operation?.phase || ''; + + setText('confidentialSetupTitle', activeLaneLabel('confidential')); + setPill( + 'confidentialSetupPill', + operationActive ? phase : rendered.pill, + operationActive ? '' : rendered.tone, + ); + setText('confidentialSetupState', operationRendered.message || rendered.message); + const last = attestation.last_verified?.checked_at + ? relativeTime(attestation.last_verified.checked_at) + : ''; + setText( + 'confidentialSetupMeta', + attestation.state === 'verified' && checked + ? `checked ${checked}` + : last + ? `last checked ${last}` + : '', + ); + setMessage( + 'confidentialLaneOperation', + operationRendered.message || '', + operationRendered.tone, + ); + setLink( + 'confidentialLaneOperationLink', + operation?.portal_url || '', + 'continue in browser →', + ); + setText( + 'confidentialNotice', + phase === 'early_access' ? confidentialCopy.operation_states?.early_access || '' : '', + ); + setButtonState('confidentialEnable', !configured && !operationActive, operationActive); + setButtonText('confidentialEnable', confidentialCopy.actions?.off || ''); + setButtonState('confidentialDisable', configured, operationActive || !configured); + setButtonText('confidentialDisable', confidentialCopy.actions?.enabled || ''); + setButtonState('confidentialRecheck', configured && rendered.recheck, operationActive); + setButtonText('confidentialRecheck', confidentialCopy.actions?.recheck || ''); + } + function renderMainLanes() { const brain = activeBrain(); const localLane = laneCopy('local'); @@ -598,9 +840,7 @@ setText('localLaneStatus', 'set up →'); } - setCardActive('confidential', brain.kind === 'confidential'); - setPill('confidentialLanePill', confidentialLane.tag || ''); - setText('confidentialLaneDescription', confidentialLane.description || ''); + renderConfidentialCard(); renderConfidentialDetailPanel(); const configured = configuredProviders(); @@ -1159,14 +1399,19 @@ renderByo(); renderLocalEndpoint(); renderScout(); + renderConfidentialSetup(); renderLocal(); renderLaneSwitch(); } - async function refreshProviders() { + async function refreshProvidersPayload() { const model = $('localModelSelect')?.value; const suffix = model ? `?local_model=${encodeURIComponent(model)}` : ''; - state.providers = await api(`api/providers${suffix}`); + return api(`api/providers${suffix}`); + } + + async function refreshProviders() { + state.providers = await refreshProvidersPayload(); renderAll(); } @@ -1218,6 +1463,50 @@ state.installPollGeneration += 1; } + function stopConfidentialPoll() { + state.confidentialPollGeneration += 1; + } + + function applyConfidentialProviders(payload, generation) { + if (generation !== undefined && generation !== state.confidentialPollGeneration) return false; + state.providers = payload || state.providers; + renderAll(); + return true; + } + + function startConfidentialPoll(initialStatus = null) { + stopConfidentialPoll(); + const generation = state.confidentialPollGeneration; + return pollConfidentialUntilTerminal({ + fetchStatus: refreshProvidersPayload, + sleepFn: sleep, + applyStatus: (payload) => applyConfidentialProviders(payload, generation), + isCurrent: () => generation === state.confidentialPollGeneration, + intervalMs: pollIntervalMs, + maxElapsedMs: confidentialPollMaxMs, + initialStatus, + }) + .then((payload) => { + if (generation !== state.confidentialPollGeneration) return; + if (payload !== null) stopConfidentialPoll(); + }) + .catch((err) => { + handleConfidentialPollError({ + generation, + currentGeneration: () => state.confidentialPollGeneration, + clearOperation: () => { + if (state.providers.active_lane) { + state.providers.active_lane.confidential_operation = null; + } + renderAll(); + }, + stopPoll: stopConfidentialPoll, + showError: (message) => setMessage('confidentialLaneOperation', message, 'error'), + error: err, + }); + }); + } + async function fetchInstallStatus(model = selectedLocalModelId()) { if (!model) return null; return api(`api/local/bootstrap/status?model=${encodeURIComponent(model)}`); @@ -1362,6 +1651,35 @@ showView('main'); } + async function enableConfidential() { + setMessage('confidentialLaneOperation', ''); + let start; + try { + start = await api('api/confidential/enable', {method: 'POST'}); + } catch (err) { + setMessage('confidentialLaneOperation', err.message, 'error'); + return; + } + if (state.providers.active_lane) { + state.providers.active_lane.confidential_operation = start?.operation || null; + renderAll(); + } + openConsentTab(start?.operation); + await startConfidentialPoll(); + } + + async function recheckConfidential() { + setMessage('confidentialLaneOperation', copy.confidential?.attestation_states?.verifying || ''); + state.providers = await api('api/confidential/recheck', {method: 'POST'}); + renderAll(); + } + + async function disableConfidential() { + await api('api/confidential/disable', {method: 'POST'}); + await Promise.all([refreshProviders(), refreshKeys(), refreshLocalAvailability()]); + showView('main'); + } + async function saveByoKey() { const provider = laneProvider('byo'); const value = $('byoKeyInput')?.value || ''; @@ -1570,8 +1888,12 @@ $('scoutRefresh')?.addEventListener('click', () => refreshScoutOp().catch((err) => setMessage('scoutLaneOperation', err.message, 'error'))); $('scoutDisable')?.addEventListener('click', () => disableScout().catch((err) => setMessage('scoutLaneOperation', err.message, 'error'))); $('scoutCheck')?.addEventListener('click', () => checkScout().catch((err) => setMessage('scoutLaneOperation', err.message, 'error'))); + $('confidentialEnable')?.addEventListener('click', () => enableConfidential().catch((err) => setMessage('confidentialLaneOperation', err.message, 'error'))); + $('confidentialRecheck')?.addEventListener('click', () => recheckConfidential().catch((err) => setMessage('confidentialLaneOperation', err.message, 'error'))); + $('confidentialDisable')?.addEventListener('click', () => disableConfidential().catch((err) => setMessage('confidentialLaneOperation', err.message, 'error'))); $('localRefresh')?.addEventListener('click', () => { stopInstallPoll(); + stopConfidentialPoll(); Promise.all([ refreshProviders(), refreshLocalAvailability(), @@ -1582,6 +1904,7 @@ $('localActivate')?.addEventListener('click', () => activateLane('local').catch((err) => setMessage('localSetupMessage', err.message, 'error'))); $('localModelSelect')?.addEventListener('change', () => { stopInstallPoll(); + stopConfidentialPoll(); state.install = null; renderAll(); Promise.all([ diff --git a/solstone/apps/thinking/tests/conftest.py b/solstone/apps/thinking/tests/conftest.py index 39effdf6a..4e95b1dfe 100644 --- a/solstone/apps/thinking/tests/conftest.py +++ b/solstone/apps/thinking/tests/conftest.py @@ -27,9 +27,9 @@ def _clear_service_operations(): @pytest.fixture(autouse=True) def _clear_spp_attestation_state(): - spp.clear_attestation_state() + spp.delete_attestation_state() yield - spp.clear_attestation_state() + spp.delete_attestation_state() @pytest.fixture diff --git a/solstone/apps/thinking/tests/js_extract.py b/solstone/apps/thinking/tests/js_extract.py new file mode 100644 index 000000000..7a8c40b7f --- /dev/null +++ b/solstone/apps/thinking/tests/js_extract.py @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +from __future__ import annotations + +import re + + +def extract_js_const(source: str, const_name: str) -> str: + match = re.search( + rf" const {re.escape(const_name)} = new Set\(\[[^\]]+\]\);", + source, + ) + if match is None: + raise AssertionError(f"could not extract {const_name}") + return match.group(0).strip() + + +def extract_js_function(source: str, function_name: str) -> str: + markers = [ + f" function {function_name}", + f" async function {function_name}", + ] + starts = [source.index(marker) for marker in markers if marker in source] + if not starts: + raise AssertionError(f"could not extract {function_name}") + start = min(starts) + 2 + brace = source.index(") {", start) + 2 + depth = 0 + in_string: str | None = None + escaped = False + for index in range(brace, len(source)): + char = source[index] + if in_string: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == in_string: + in_string = None + continue + if char in {"'", '"', "`"}: + in_string = char + continue + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return source[start : index + 1] + raise AssertionError(f"could not extract {function_name}") diff --git a/solstone/apps/thinking/tests/test_confidential_attestation_payload.py b/solstone/apps/thinking/tests/test_confidential_attestation_payload.py index 650863a7c..29e6b31b2 100644 --- a/solstone/apps/thinking/tests/test_confidential_attestation_payload.py +++ b/solstone/apps/thinking/tests/test_confidential_attestation_payload.py @@ -17,9 +17,26 @@ ATTESTED_SUBSTRATE = "AMD SEV-SNP + NVIDIA GH100 A01 GSP BROM" HOSTILE_ARCH = "" -def _client(settings_env): +def _client(settings_env, *, confidential: bool = False): journal_path, config = settings_env() config["setup"] = {"completed_at": "2026-05-23T00:00:00Z"} + if confidential: + config.setdefault("services", {})["confidential"] = { + "enabled_at": "2026-05-24T00:00:00Z", + "account_id": "acct-secret", + "endpoint_url": "https://spp.example.test/v1", + "served_model_id": "confidential-model", + "credential_created_at": "2026-05-24T00:00:00Z", + "credential_fingerprint_sha256": "fingerprint-secret", + "prior_generate_provider": "google", + "prior_cogitate_provider": "openai", + "prior_local_endpoint": None, + } + config.setdefault("providers", {})["local"] = { + "endpoint_url": "https://spp.example.test", + "served_model_id": "confidential-model", + "credential": "credential-secret", + } (journal_path / "config" / "journal.json").write_text( json.dumps(config, indent=2) + "\n", encoding="utf-8", @@ -85,14 +102,30 @@ def _providers(client) -> dict: return payload -def test_active_lane_confidential_attestation_defaults_to_verifying(settings_env): +def test_active_lane_confidential_attestation_defaults_to_off(settings_env): client = _client(settings_env) payload = _providers(client) + assert payload["active_lane"]["confidential_attestation"] == { + "state": "off", + "provenance": None, + "last_verified": None, + "reason": "confidential_not_configured", + } + + +def test_active_lane_confidential_attestation_configured_without_session_verifies( + settings_env, +): + client = _client(settings_env, confidential=True) + + payload = _providers(client) + assert payload["active_lane"]["confidential_attestation"] == { "state": "verifying", "provenance": None, + "last_verified": None, "reason": "attestation_not_yet_verified", } @@ -100,7 +133,7 @@ def test_active_lane_confidential_attestation_defaults_to_verifying(settings_env def test_active_lane_confidential_attestation_verified_session_serializes_safe_provenance( settings_env, ): - client = _client(settings_env) + client = _client(settings_env, confidential=True) now = datetime.now(timezone.utc) session = _session( now=now, @@ -122,6 +155,11 @@ def test_active_lane_confidential_attestation_verified_session_serializes_safe_p "substrate": ATTESTED_SUBSTRATE, "checked_at": now.isoformat(), }, + "last_verified": { + "legs": ["cpu", "gpu"], + "substrate": ATTESTED_SUBSTRATE, + "checked_at": now.isoformat(), + }, "reason": None, } serialized = response.get_data(as_text=True) @@ -140,7 +178,7 @@ def test_active_lane_confidential_attestation_verified_session_serializes_safe_p def test_active_lane_confidential_attestation_does_not_serialize_unverified_arch( settings_env, ): - client = _client(settings_env) + client = _client(settings_env, confidential=True) now = datetime.now(timezone.utc) session = _session( now=now, @@ -161,7 +199,7 @@ def test_active_lane_confidential_attestation_does_not_serialize_unverified_arch def test_active_lane_confidential_attestation_stale_session(settings_env): - client = _client(settings_env) + client = _client(settings_env, confidential=True) now = datetime.now(timezone.utc) session = _session( now=now, @@ -176,18 +214,76 @@ def test_active_lane_confidential_attestation_stale_session(settings_env): assert payload["active_lane"]["confidential_attestation"] == { "state": "stale", "provenance": None, + "last_verified": { + "legs": ["cpu", "gpu"], + "substrate": ATTESTED_SUBSTRATE, + "checked_at": now.isoformat(), + }, "reason": "attestation_stale", } def test_active_lane_confidential_attestation_failed_state(settings_env): - client = _client(settings_env) - spp.record_attestation_failed("gpu_nonce_mismatch") + client = _client(settings_env, confidential=True) + spp.record_attestation_failed("failed", "gpu_nonce_mismatch") + + payload = _providers(client) + + assert payload["active_lane"]["confidential_attestation"] == { + "state": "failed", + "provenance": None, + "last_verified": None, + "reason": "attestation_failed", + } + + +def test_active_lane_confidential_attestation_unreachable_state_preserves_last_verified( + settings_env, +): + client = _client(settings_env, confidential=True) + now = datetime.now(timezone.utc) + session = _session( + now=now, + started_at=now - timedelta(minutes=1), + tpm_heartbeat_at=now - timedelta(minutes=1), + gpu_reattest_at=now - timedelta(minutes=1), + ) + spp.record_attestation_verified(session) + spp.record_attestation_failed("unreachable", "gateway_unreachable") + + payload = _providers(client) + + assert payload["active_lane"]["confidential_attestation"] == { + "state": "unreachable", + "provenance": None, + "last_verified": { + "legs": ["cpu", "gpu"], + "substrate": ATTESTED_SUBSTRATE, + "checked_at": now.isoformat(), + }, + "reason": "attestation_unreachable", + } + + +def test_active_lane_confidential_attestation_missing_provenance_degrades_to_failed( + settings_env, +): + client = _client(settings_env, confidential=True) + now = datetime.now(timezone.utc) + session = _session( + now=now, + started_at=now - timedelta(minutes=1), + tpm_heartbeat_at=now - timedelta(minutes=1), + gpu_reattest_at=now - timedelta(minutes=1), + substrate="", + ) + spp.record_attestation_verified(session) payload = _providers(client) assert payload["active_lane"]["confidential_attestation"] == { "state": "failed", "provenance": None, + "last_verified": None, "reason": "attestation_failed", } diff --git a/solstone/apps/thinking/tests/test_confidential_routes.py b/solstone/apps/thinking/tests/test_confidential_routes.py index 6c2bba68b..ebab6d1fd 100644 --- a/solstone/apps/thinking/tests/test_confidential_routes.py +++ b/solstone/apps/thinking/tests/test_confidential_routes.py @@ -6,13 +6,15 @@ from __future__ import annotations import json import threading import time +from datetime import datetime, timezone from pathlib import Path import pytest from solstone.convey import create_app from solstone.think.journal_config import write_journal_config -from solstone.think.services import operations, spp, spp_handoff +from solstone.think.services import operations, spp, spp_handoff, spp_transport +from solstone.think.services.spp_attest.cadence import AttestationSession def _payload(suffix: str = "one") -> dict[str, str]: @@ -116,10 +118,42 @@ def test_enable_confidential_returns_operation_and_lands_not_verified( assert payload["active_lane"]["confidential_attestation"] == { "state": "verifying", "provenance": None, + "last_verified": None, "reason": "attestation_not_yet_verified", } +def test_enable_confidential_early_access_stays_off( + thinking_client, + monkeypatch: pytest.MonkeyPatch, +) -> None: + def runner(**_kwargs): + return operations.HandoffResult("early_access", None, False) + + monkeypatch.setattr(spp_handoff, "run_confidential_handoff", runner) + + response = thinking_client.post("/app/thinking/api/confidential/enable") + + assert response.status_code == 202 + _wait_until( + lambda: ( + _providers(thinking_client)["active_lane"]["confidential_operation"][ + "phase" + ] + == "early_access" + ) + ) + payload = _providers(thinking_client) + assert payload["active_lane"]["confidential_enabled"] is False + assert payload["active_lane"]["confidential_provenance_configured"] is False + assert payload["active_lane"]["confidential_attestation"] == { + "state": "off", + "provenance": None, + "last_verified": None, + "reason": "confidential_not_configured", + } + + def test_service_busy_for_second_confidential_operation( thinking_client, monkeypatch: pytest.MonkeyPatch, @@ -157,6 +191,15 @@ def test_disable_confidential_restores_synchronously( thinking_client, journal_copy: Path ): spp.provision_confidential_handoff(_payload("disable")) + now = datetime.now(timezone.utc) + spp.record_attestation_verified( + AttestationSession( + verdict=object(), + started_at=now, + tpm_heartbeat_at=now, + gpu_reattest_at=now, + ) + ) response = thinking_client.post("/app/thinking/api/confidential/disable") @@ -169,6 +212,41 @@ def test_disable_confidential_restores_synchronously( assert config["providers"]["cogitate"]["provider"] == "openai" assert config["providers"]["local"] == {} assert "confidential" not in config["services"] + state = spp.get_attestation_state() + assert state.session is None + assert state.failure is None + assert state.last_verified is None + + +def test_recheck_confidential_rejects_when_off(thinking_client) -> None: + response = thinking_client.post("/app/thinking/api/confidential/recheck") + + assert response.status_code == 400 + assert response.get_json()["reason_code"] == "invalid_operation_for_state" + + +def test_recheck_confidential_returns_refreshed_provider_state( + thinking_client, + monkeypatch: pytest.MonkeyPatch, +) -> None: + spp.provision_confidential_handoff(_payload("recheck")) + + def recheck() -> None: + spp.record_attestation_failed("failed", "gpu_nonce_mismatch") + + monkeypatch.setattr(spp_transport, "recheck_confidential_attestation", recheck) + + response = thinking_client.post("/app/thinking/api/confidential/recheck") + + assert response.status_code == 200 + payload = response.get_json() + assert payload["active_lane"]["confidential_enabled"] is True + assert payload["active_lane"]["confidential_attestation"] == { + "state": "failed", + "provenance": None, + "last_verified": None, + "reason": "attestation_failed", + } def test_confidential_routes_and_provider_payload_are_secret_free( diff --git a/solstone/apps/thinking/tests/test_providers_payload_extended.py b/solstone/apps/thinking/tests/test_providers_payload_extended.py index 5a9d8ce65..9a525ecac 100644 --- a/solstone/apps/thinking/tests/test_providers_payload_extended.py +++ b/solstone/apps/thinking/tests/test_providers_payload_extended.py @@ -359,6 +359,7 @@ def test_thinking_status_payloads_are_secret_free_with_scout_provenance( "confidential_attestation": { "state": "verifying", "provenance": None, + "last_verified": None, "reason": "attestation_not_yet_verified", }, } diff --git a/solstone/apps/thinking/tests/test_thinking_confidential_poll_js.py b/solstone/apps/thinking/tests/test_thinking_confidential_poll_js.py new file mode 100644 index 000000000..4c7013c62 --- /dev/null +++ b/solstone/apps/thinking/tests/test_thinking_confidential_poll_js.py @@ -0,0 +1,184 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +from __future__ import annotations + +import json +import shutil +import subprocess +from pathlib import Path + +import pytest + +from solstone.apps.thinking import copy as thinking_copy +from solstone.apps.thinking.tests.js_extract import ( + extract_js_const, + extract_js_function, +) + +STATIC = Path(__file__).resolve().parents[1] / "static" / "thinking.js" + + +def _node_script(body: str) -> str: + source = STATIC.read_text(encoding="utf-8") + parts = [ + extract_js_const(source, "confidentialTerminalPhases"), + extract_js_function(source, "formatCopy"), + extract_js_function(source, "confidentialOperationIsTerminal"), + extract_js_function(source, "confidentialOperationRender"), + extract_js_function(source, "confidentialAttestationRender"), + extract_js_function(source, "confidentialGlanceForAttestation"), + extract_js_function(source, "pollConfidentialUntilTerminal"), + extract_js_function(source, "handleConfidentialPollError"), + "function assert(condition, message) { if (!condition) throw new Error(message); }", + f"const copy = {json.dumps(thinking_copy.thinking_copy_payload())};", + body, + ] + return "\n".join(parts) + + +def _run_node(script: str) -> None: + node = shutil.which("node") + if node is None: + pytest.skip("node is not available") + result = subprocess.run( + [node, "-e", script], + check=True, + text=True, + capture_output=True, + ) + assert "PASS" in result.stdout + + +def test_confidential_render_mappings_are_pure() -> None: + _run_node( + _node_script( + """ +const confidentialCopy = copy.confidential; +const verified = confidentialAttestationRender({ + state: 'verified', + provenance: {checked_at: '2026-07-12T12:00:00+00:00'}, +}, confidentialCopy, 'just now'); +assert(verified.pill === 'active', 'verified pill'); +assert(verified.tone === 'hot', 'verified tone'); +assert(verified.message === 'checked just now', 'verified message'); + +const failed = confidentialAttestationRender({state: 'failed'}, confidentialCopy); +assert(failed.tone === 'bad', 'failed tone'); +assert(failed.recheck === true, 'failed recheck'); +assert(failed.message === "couldn't verify the service — sol isn't sending.", 'failed message'); + +const starting = confidentialOperationRender({phase: 'starting'}, confidentialCopy); +assert(starting.message === 'opening your browser to confirm…', 'starting message'); +assert(starting.active === true, 'starting active'); +const early = confidentialOperationRender({phase: 'early_access'}, confidentialCopy); +assert(early.message === 'confidential processing is coming — scouts get it first.', 'early access copy'); +assert(confidentialOperationIsTerminal({phase: 'early_access'}), 'early access terminal'); + +const glance = confidentialGlanceForAttestation({state: 'verified', provenance: {}}, copy, '1 min ago'); +assert(glance.label === 'sol is thinking with', 'verified glance label'); +assert(glance.detail === 'checked 1 min ago', 'verified glance detail'); +const blocked = confidentialGlanceForAttestation({state: 'unreachable'}, copy); +assert(blocked.label === 'sol is holding', 'blocked glance label'); +assert(blocked.detail === "can't reach confidential processing right now — sol isn't sending.", 'blocked glance detail'); +console.log('PASS'); +""" + ) + ) + + +def test_confidential_poll_stops_at_early_access_terminal() -> None: + _run_node( + _node_script( + """ +async function main() { + const statuses = ['starting', 'waiting', 'early_access'].map((phase) => ({ + active_lane: {confidential_operation: {phase}}, + })); + let fetchCalls = 0; + const sleeps = []; + const applied = []; + async function fetchStatus() { + return statuses[fetchCalls++]; + } + async function sleepFn(ms) { + sleeps.push(ms); + } + function applyStatus(status) { + applied.push(status.active_lane.confidential_operation.phase); + } + const result = await pollConfidentialUntilTerminal({ + fetchStatus, + sleepFn, + applyStatus, + isCurrent: () => true, + intervalMs: 1500, + maxElapsedMs: 15000, + }); + assert(result.active_lane.confidential_operation.phase === 'early_access', 'terminal result'); + assert(fetchCalls === 3, 'fetch count'); + assert(JSON.stringify(applied) === JSON.stringify(['starting', 'waiting', 'early_access']), 'applied phases'); + assert(sleeps.length === 2, 'sleep count'); + console.log('PASS'); +} +main().catch((error) => { console.error(error.stack || error); process.exit(1); }); +""" + ) + ) + + +def test_confidential_poll_timeout_and_error_handler_clear_current_generation() -> None: + _run_node( + _node_script( + """ +async function main() { + let now = 0; + let fetchCalls = 0; + let cleared = false; + let stopped = false; + let errorMessage = ''; + try { + await pollConfidentialUntilTerminal({ + fetchStatus: async () => { + fetchCalls += 1; + return {active_lane: {confidential_operation: {phase: 'waiting'}}}; + }, + sleepFn: async (ms) => { now += ms; }, + applyStatus: () => {}, + isCurrent: () => true, + intervalMs: 1500, + maxElapsedMs: 2000, + nowFn: () => now, + }); + throw new Error('poll should have timed out'); + } catch (error) { + const handled = handleConfidentialPollError({ + generation: 1, + currentGeneration: () => 1, + clearOperation: () => { cleared = true; }, + stopPoll: () => { stopped = true; }, + showError: (message) => { errorMessage = message; }, + error, + }); + assert(handled === true, 'current generation handled'); + } + assert(fetchCalls === 2, 'bounded fetches'); + assert(cleared === true, 'cleared'); + assert(stopped === true, 'stopped'); + assert(errorMessage === 'confidential setup timed out', 'timeout message'); + + const stale = handleConfidentialPollError({ + generation: 1, + currentGeneration: () => 2, + clearOperation: () => { throw new Error('stale clear'); }, + stopPoll: () => { throw new Error('stale stop'); }, + showError: () => { throw new Error('stale error'); }, + error: new Error('ignored'), + }); + assert(stale === false, 'stale generation ignored'); + console.log('PASS'); +} +main().catch((error) => { console.error(error.stack || error); process.exit(1); }); +""" + ) + ) diff --git a/solstone/apps/thinking/tests/test_thinking_install_poll_js.py b/solstone/apps/thinking/tests/test_thinking_install_poll_js.py index eb534c8a7..e9eb6f1d7 100644 --- a/solstone/apps/thinking/tests/test_thinking_install_poll_js.py +++ b/solstone/apps/thinking/tests/test_thinking_install_poll_js.py @@ -4,7 +4,6 @@ from __future__ import annotations import json -import re import shutil import subprocess from pathlib import Path @@ -13,68 +12,27 @@ import pytest from solstone.apps.thinking import copy as thinking_copy from solstone.apps.thinking.install_copy import INSTALL_FAILED_NO_PROGRESS +from solstone.apps.thinking.tests.js_extract import ( + extract_js_const, + extract_js_function, +) STATIC = Path(__file__).resolve().parents[1] / "static" / "thinking.js" -def _extract_js_const(source: str, const_name: str) -> str: - match = re.search( - rf" const {re.escape(const_name)} = new Set\(\[[^\]]+\]\);", - source, - ) - if match is None: - raise AssertionError(f"could not extract {const_name}") - return match.group(0).strip() - - -def _extract_js_function(source: str, function_name: str) -> str: - markers = [ - f" function {function_name}", - f" async function {function_name}", - ] - starts = [source.index(marker) for marker in markers if marker in source] - if not starts: - raise AssertionError(f"could not extract {function_name}") - start = min(starts) + 2 - brace = source.index(") {", start) + 2 - depth = 0 - in_string: str | None = None - escaped = False - for index in range(brace, len(source)): - char = source[index] - if in_string: - if escaped: - escaped = False - elif char == "\\": - escaped = True - elif char == in_string: - in_string = None - continue - if char in {"'", '"', "`"}: - in_string = char - continue - if char == "{": - depth += 1 - elif char == "}": - depth -= 1 - if depth == 0: - return source[start : index + 1] - raise AssertionError(f"could not extract {function_name}") - - def _node_script(body: str) -> str: source = STATIC.read_text(encoding="utf-8") parts = [ - _extract_js_const(source, "installInFlightStates"), - _extract_js_const(source, "installTerminalStates"), - _extract_js_function(source, "installIsInFlight"), - _extract_js_function(source, "installIsTerminal"), - _extract_js_function(source, "formatInstallBytes"), - _extract_js_function(source, "installCopyForStatus"), - _extract_js_function(source, "pollLocalInstallUntilTerminal"), - _extract_js_function(source, "handleInstallPollError"), + extract_js_const(source, "installInFlightStates"), + extract_js_const(source, "installTerminalStates"), + extract_js_function(source, "installIsInFlight"), + extract_js_function(source, "installIsTerminal"), + extract_js_function(source, "formatInstallBytes"), + extract_js_function(source, "installCopyForStatus"), + extract_js_function(source, "pollLocalInstallUntilTerminal"), + extract_js_function(source, "handleInstallPollError"), "const pollIntervalMs = 1500;", - _extract_js_function(source, "startInstallPoll"), + extract_js_function(source, "startInstallPoll"), "function assert(condition, message) { if (!condition) throw new Error(message); }", f"const text = {json.dumps(thinking_copy.LOCAL_INSTALL)};", f"const installFailedNoProgress = {json.dumps(INSTALL_FAILED_NO_PROGRESS)};", diff --git a/solstone/apps/thinking/tests/test_workspace_html.py b/solstone/apps/thinking/tests/test_workspace_html.py index 54a277f65..99bb50acf 100644 --- a/solstone/apps/thinking/tests/test_workspace_html.py +++ b/solstone/apps/thinking/tests/test_workspace_html.py @@ -58,17 +58,20 @@ def test_workspace_renders_each_lane(settings_env): for view in ( "main", "byo-setup", + "confidential-setup", "local-setup", "lane-switch", ): assert f'data-view="{view}"' in html - assert 'data-view="confidential-setup"' not in html - assert 'data-open-view="confidential-setup"' not in html - assert 'id="confidentialLaneOperation"' not in html - assert 'id="confidentialLaneStatus"' not in html + assert 'data-open-view="confidential-setup"' in html + assert 'id="confidentialLaneOperation"' in html + assert 'id="confidentialLaneOperationLink"' in html + assert 'id="confidentialLaneStatus"' in html + assert 'id="confidentialEnable"' in html + assert 'id="confidentialRecheck"' in html + assert 'id="confidentialDisable"' in html assert 'id="confidentialLaneMore"' in html assert 'id="lane-detail-confidential"' in html - assert "set up confidential" not in html assert 'data-open-view="byo-setup"' in html assert 'data-open-view="local-setup"' in html assert "data-switch-lane" in html @@ -151,7 +154,7 @@ def test_scout_consent_static_behavior_is_wired() -> None: assert "!!actions.enable && !operationActive" in js -def test_confidential_teaser_static_behavior_is_wired() -> None: +def test_confidential_live_static_behavior_is_wired() -> None: html = WORKSPACE.read_text(encoding="utf-8") js = STATIC.read_text(encoding="utf-8") card = re.search(r'
]*)>', html) @@ -162,15 +165,17 @@ def test_confidential_teaser_static_behavior_is_wired() -> None: assert 'aria-controls="lane-detail-confidential"' in html assert 'aria-expanded="false"' in html assert 'id="lane-detail-confidential"' in html - assert "data-lane" not in card.group(1) - assert 'role="button"' not in card.group(1) - assert "tabindex" not in card.group(1) - assert "api/confidential" not in js + assert 'data-lane="confidential"' in card.group(1) + assert 'role="button"' in card.group(1) + assert 'tabindex="0"' in card.group(1) + assert "api/confidential/enable" in js + assert "api/confidential/recheck" in js + assert "api/confidential/disable" in js assert "function openConsentTab(operation)" in js assert "confidentialProvenancePresent" in js assert "renderConfidentialDetailPanel" in js - assert "switchLane('confidential')" not in js - assert "confidential-setup" not in js + assert "function pollConfidentialUntilTerminal(" in js + assert "confidential-setup" in js def test_copy_payload_round_trips_apostrophes() -> None: @@ -190,9 +195,12 @@ def test_thinking_copy_lanes_use_init_deck() -> None: assert lane.get("sub") assert lane.get("description") assert by_id["byo"]["label"] == "your own AI engine" - assert by_id["confidential"]["description"] == "coming — scouts get it first." - assert by_id["confidential"]["tag"] == "not open yet" - assert [lane["id"] for lane in lanes if "tag" in lane] == ["confidential"] + assert ( + by_id["confidential"]["description"] + == "sol pbc runs the model on confidential GPUs." + ) + assert "tag" not in by_id["confidential"] + assert [lane["id"] for lane in lanes if "tag" in lane] == [] def test_thinking_deck_copy_constants() -> None: @@ -224,6 +232,21 @@ def test_thinking_deck_copy_constants() -> None: "your journal" ), }, + "confidential_checking": { + "label": "sol is waiting on", + "value": "confidential processing", + "detail": "checking the hardware…", + }, + "confidential_verified": { + "label": "sol is thinking with", + "value": "confidential processing", + "detail": "checked {checked}", + }, + "confidential_blocked": { + "label": "sol is holding", + "value": "confidential processing", + "detail": "{message}", + }, "none": { "value": "not thinking yet", "detail": ( @@ -300,6 +323,53 @@ def test_thinking_deck_copy_constants() -> None: ), } assert thinking_copy.CONFIDENTIAL_MORE_LABEL == "how it works →" + assert thinking_copy.CONFIDENTIAL_TRUST_BEATS == { + "heading": "confidential processing", + "sub": "operated by sol pbc", + "egress": ( + "only the thinking leaves — the text and images sol works through. " + "your journal stays on this computer, and your recordings stay here " + "too (speech becomes text on your device first)." + ), + "claims": "no content is retained · no human reviews it · nothing is used to train", + "attestation": ( + "your journal must verify the service before anything is sent — if " + "it can't verify, it doesn't send." + ), + "substrate": ( + "sol pbc runs the model itself on confidential GPUs in Microsoft " + "Azure. the hardware boundary keeps the cloud host excluded from " + "what's processed — no third-party AI provider is in the path." + ), + } + assert thinking_copy.CONFIDENTIAL_LANE_DETAIL == { + "heading": thinking_copy.CONFIDENTIAL_TRUST_HEADING, + "sub": thinking_copy.CONFIDENTIAL_TRUST_SUB, + "mechanism": thinking_copy.CONFIDENTIAL_TRUST_SUBSTRATE, + "egress": thinking_copy.CONFIDENTIAL_TRUST_EGRESS, + "claims": thinking_copy.CONFIDENTIAL_TRUST_CLAIMS, + "attestation": thinking_copy.CONFIDENTIAL_TRUST_FAIL_CLOSED, + "early_access": "confidential processing is coming — scouts get it first.", + } + assert thinking_copy.CONFIDENTIAL_ATTESTATION_STATES == { + "off": "", + "verifying": "checking the hardware…", + "verified": "checked {checked}", + "failed": "couldn't verify the service — sol isn't sending.", + "stale": "your journal needs to re-check the service before sending.", + "unreachable": "can't reach confidential processing right now — sol isn't sending.", + } + assert thinking_copy.CONFIDENTIAL_OPERATION_STATES == { + "starting": "opening your browser to confirm…", + "waiting": "finish turning it on in your browser", + "early_access": "confidential processing is coming — scouts get it first.", + "repair_needed": "couldn't verify the service — sol isn't sending.", + } + assert thinking_copy.CONFIDENTIAL_ACTIONS == { + "off": "turn on confidential processing →", + "enabled": "turn off", + "recheck": "check again", + } def test_thinking_copy_payload_carries_confidential_lane_detail() -> None: @@ -311,6 +381,18 @@ def test_thinking_copy_payload_carries_confidential_lane_detail() -> None: assert ( payload["confidential"]["more_label"] == thinking_copy.CONFIDENTIAL_MORE_LABEL ) + assert payload["confidential"]["setup"] == { + "trust_beats": dict(thinking_copy.CONFIDENTIAL_SETUP["trust_beats"]) + } + assert payload["confidential"]["attestation_states"] == dict( + thinking_copy.CONFIDENTIAL_ATTESTATION_STATES + ) + assert payload["confidential"]["operation_states"] == dict( + thinking_copy.CONFIDENTIAL_OPERATION_STATES + ) + assert payload["confidential"]["actions"] == dict( + thinking_copy.CONFIDENTIAL_ACTIONS + ) def test_thinking_copy_payload_shape_carries_deck_blocks() -> None: @@ -358,8 +440,20 @@ def test_thinking_copy_avoids_forbidden_terms() -> None: ): assert re.search(rf"\b{re.escape(term)}\b", combined, re.IGNORECASE) is None - for phrase in ("this machine", "this device"): - assert re.search(rf"\b{re.escape(phrase)}\b", combined, re.IGNORECASE) is None + for phrase in ( + "this machine", + "this device", + "sealed", + "sealed engine", + "not sol pbc's to read", + "only you can read it", + "checks the hardware before it sends", + "verified ✓", + ): + pattern = ( + rf"\b{re.escape(phrase)}\b" if phrase[-1].isalnum() else re.escape(phrase) + ) + assert re.search(pattern, combined, re.IGNORECASE) is None def test_thinking_copy_avoids_banned_absolute_claims() -> None: diff --git a/solstone/apps/thinking/workspace.html b/solstone/apps/thinking/workspace.html index b7fde9eeb..8418b94f7 100644 --- a/solstone/apps/thinking/workspace.html +++ b/solstone/apps/thinking/workspace.html @@ -509,13 +509,14 @@
-
+

+
@@ -582,10 +583,47 @@
- + + + -