console.log(JSON.stringify({schema:1,harness:'prospectos-frontend-final-acceptance',pass:false,error:'--base-url and --start-server are mutually exclusive'},null,2));
check('dom.critical-ids','critical operator DOM IDs are present',all(expectedIds,id=>htmlIds.has(id)),listMissing(expectedIds,id=>htmlIds.has(id)).join(', '));
check('routes.contracts','all critical API route contracts are referenced by the client',all(routeContracts,route=>js.includes(route)),listMissing(routeContracts,route=>js.includes(route)).join(', '));
check('routes.authenticated','protected API requests include cookie credentials',js.includes("credentials:'include'")&&js.includes('jsonRequest'));
check('auth.display-name-greeting','authenticated display name drives the greeting and identity',js.includes('currentUser.display_name')&&js.includes("$('userGreetingName').textContent")&&!html.includes('Good morning, Alex'));
check('sources.registry-only','sources contain governed registry configuration, not discovery criteria or prompt/query controls',all(['sourceOwner','sourceTermsUrl','sourceTermsStatus','sourceRateLimit','sourceDailyQuota','sourceProviderSettings','sourceSaveBtn','sourceTestBtn','sourceEnableBtn','sourceStatus'],id=>htmlIds.has(id))&&!html.includes('id="discoveryForm"')&&!html.includes('name="query"')&&!js.includes('function runDiscovery('));
check('sources.registry-payload','source save sends only registry/configuration fields and explicitly starts disabled',all(['terms_status:fields.terms_status','daily_quota:Number(fields.daily_quota)','provider_settings:fields.provider_settings','enabled:false'],token=>js.includes(token))&&!/criteria\s*:|query\s*:|prompt\s*:/i.test(js.match(/async function saveSource[\s\S]*?(?=\n function findRegisteredSource)/)?.[0]||''));
check('sources.lifecycle-states','source save, test, and enable controls expose loading, success, error, and disabled explanations',all(['Saving source configuration…','Source configuration saved.','Testing source…','Enable is unavailable until terms, owner, limits, and a successful test are recorded.','Unable to save source configuration.'],token=>`${html}\n${js}`.includes(token)));
check('sources.optional-gated','source registry reads optional adapter readiness without treating it as an enabled source',all(['/api/v1/sources/adapters','optional:true','available:Boolean'],token=>`${html}\n${js}`.includes(token)));
check('discovery.payload-contract','discovery submits canonical criteria and does not send source configuration fields',all(['category:fields.category.trim()','language:fields.language.trim()','website_analysis:','include_websites:','exclude_websites:','max_results:Number(fields.max_results)','source_ids:selectedSourceIds()'],token=>js.includes(token))&&!/terms_status|provider_settings|owner/.test(js.match(/async function submitDirectDiscovery[\s\S]*?(?=\n\n function parseCsv)/)?.[0]||''));
check('discovery.run-controls','run history, events, provenance, retry, and only route-backed cancel controls are represented',all(['data-run-action="cancel"','data-run-action="retry"','live-log','source-health','provenance','/api/v1/discovery-runs/${encodeURIComponent(run.id)}/cancel','/api/v1/jobs/${encodeURIComponent(run.job_id)}/retry'],token=>`${html}\n${js}\n${css}`.includes(token))&&!`${html}\n${js}`.includes('data-run-action="pause"')&&!`${html}\n${js}`.includes('data-run-action="resume"'));
check('discovery.control-states','discovery controls communicate loading, success, error, and disabled explanations',all(['Starting bounded discovery job…','Discovery accepted. Tracking job status and partial results below.','Select at least one enabled source before starting a discovery run.','No enabled sources are available. Configure, test, and enable a source first.','Unable to load discovery runs'],token=>`${html}\n${js}`.includes(token)));
check('prospects.filter-contract','prospect explorer exposes source, geography, category, and contact filters',all(['sourceFilter','geographyFilter','categoryFilter','contactStatusFilter','contact_status'],token=>`${html}\n${js}\n${css}`.includes(token)));
check('routes.no-arbitrary-target-fetch','browser does not fetch arbitrary target URLs directly',!/fetch\s*\(\s*(?:url|target|website|href)\b/i.test(js));
check('crm.stage-contract','CRM stage definitions exactly match the canonical pipeline',JSON.stringify(crmStages)===JSON.stringify(canonicalStages),`actual=${JSON.stringify(crmStages)}`);
check('crm.stage-control','detail stage control exposes only canonical stages',canonicalStages.every(stage=>pipelineControl.includes(`value="${stage}"`))&&!pipelineControl.includes('value="review"')&&!pipelineControl.includes('value="suppressed"'));
check('crm.interaction-form-contract','interaction form uses kind/body and canonical outcomes',interactionControl.includes('name="kind"')&&interactionControl.includes('name="body"')&&canonicalOutcomes.every(outcome=>interactionControl.includes(`value="${outcome}"`))&&!interactionControl.includes('name="type"')&&!interactionControl.includes('name="summary"'));
check('crm.interaction-payload-contract','interaction writes use the backend kind/body payload contract',/JSON\.stringify\(\{\s*kind:\s*data\.kind,\s*body:\s*data\.body/.test(js)&&!/JSON\.stringify\(\{\.\.\.data,outreach:false\}\)/.test(js));
check('manifest.schema','asset manifest has a supported schema and asset lists',Boolean(manifest&&manifest.schema===1&&Array.isArray(manifest.entrypoints)&&Array.isArray(manifest.publicAssets)&&manifest.integrity&&manifestAssets.length),manifest?'':'manifest unavailable');
if(manifestAssets){
check('manifest.assets-exist','every manifest asset exists and is non-empty',all(manifestAssets,asset=>files[asset]?.length>0),listMissing(manifestAssets,asset=>files[asset]?.length>0).join(', '));
check('assets.linked-local','all local HTML assets exist and are non-empty',all(linkedAssets,asset=>files[asset]?.length>0),listMissing(linkedAssets,asset=>files[asset]?.length>0).join(', '));
check('css.responsive','responsive CSS covers mobile layouts and critical grids',/@media\s*\(\s*max-width\s*:\s*700px\s*\)/.test(css)&&all(['.saved-view-controls','.website-scan-grid','.crm-two-col','.reports-grid','.outreach-panel','.pipeline-board','.ai-provider-grid'],selector=>css.includes(selector)));
constsafetyCopy=['No outreach from this workspace.','Suppression always wins.','Sending is disabled by default.','Approval does not send a message.','no outreach will be sent','AUTOMATED_OUTREACH_ENABLED=false'];
check('safety.no-send-copy','persistent no-send and approval safety copy is present',all(safetyCopy,phrase=>combined.includes(phrase)),listMissing(safetyCopy,phrase=>combined.includes(phrase)).join(' | '));
check('safety.no-send-controls','static HTML exposes no send, delivery, or campaign control',!buttonLabels.some(label=>/\b(send|deliver|campaign|scheduledelivery)\b/i.test(label)));
check('safety.approval-gated','approval actions require explicit human confirmation and remain non-delivering',all(approvalTokens,token=>js.includes(token)),listMissing(approvalTokens,token=>js.includes(token)).join(', '));
constsuppressionTokens=['statusOf',"st==='suppressed'","renderAiState('suppressed')","renderOutreachState('suppressed')",'suppression','Do not contact'];
check('safety.suppression-precedence','suppression disables AI/outreach and preserves do-not-contact state',all(suppressionTokens,token=>combined.includes(token)),listMissing(suppressionTokens,token=>combined.includes(token)).join(', '));
check('safety.source-disabled','discovery remains disabled until an approved source is enabled',all(['enabled:false','No enabled source. Configure, test, and enable a source before discovery.','No enabled sources are available. Configure, test, and enable a source first.'],token=>combined.includes(token)));
check('safety.no-external-network','client has no direct provider or target network origins',!/(?:fetch|XMLHttpRequest|WebSocket)\s*\(\s*[`'\"]https?:\/\//i.test(js));
check('ai-provider.write-only','AI provider keys are write-only and cleared after save',all(['type="password"','autocomplete="new-password"',"input.value=''",'input[type="password"]'],token=>combined.includes(token))&&!/localStorage[^\n]*(?:nous|firecrawl|api[_-]?key)/i.test(combined));
check('ai-provider.admin-states','AI provider settings expose safe status states and admin-only messaging',all(['/api/v1/ai/provider-config','/api/v1/admin/ai-provider-config/test','Not configured','Invalid configuration','Administrator access required'],token=>combined.includes(token)));