diff --git a/.github/workflows/site-quality.yml b/.github/workflows/site-quality.yml new file mode 100644 index 00000000..40f238d8 --- /dev/null +++ b/.github/workflows/site-quality.yml @@ -0,0 +1,31 @@ +name: OVK site quality + +on: + pull_request: + paths: + - "site/**" + - ".github/workflows/site-quality.yml" + push: + branches: [main] + paths: + - "site/**" + - ".github/workflows/site-quality.yml" + +permissions: + contents: read + +jobs: + site-contract: + name: Site resource contract + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + - name: Validate JavaScript syntax + run: | + node --check site/assets/catalog.js + node --check site/assets/app.js + - name: Validate public resource paths and basic accessibility contract + run: node site/tests/catalog_contract.mjs diff --git a/site/README.md b/site/README.md new file mode 100644 index 00000000..65314788 --- /dev/null +++ b/site/README.md @@ -0,0 +1,40 @@ +# OVK Resource Explorer (development preview) + +A responsive, dependency-free public-facing discovery interface for Open Verification Kernel. It is a **curated index** of actual repository documentation, code examples and intent templates, with descriptive search, keyboard navigation, clear evidence boundaries and copyable local development instructions. + +## Local preview + +Open `site/index.html` in a browser. No package installation, API key, login, or build step is necessary. For local HTTP development: + +```bash +python -m http.server 8080 --directory site +# Open http://localhost:8080/ +``` + +This is not currently a live hosted service. GitHub Pages is not enabled; deploying is a separate maintainer decision. The site performs no verification, stores no user data, and makes no GitHub API requests. It links to GitHub source files; two draft-only documents have exact candidate-commit links and are labeled as drafts. Resource search is client-side over `assets/catalog.js` (51 selected entries), **not** a full-text search over the whole repository or an evidence search API. + +## Accessibility and behavior + +- Responsive navigation and category filter scrolling; mobile and desktop layouts. +- Keyboard shortcut `Ctrl/⌘+K` or `/` to focus search, `Escape` to clear, `ArrowDown` to focus the first result, `Enter` to focus the top result. +- Search category, title, description, source path and topic keywords; all words must match some field. URLs preserve `q` and `category` where history access is available. +- Live result counts, explicit no-results recovery, focus indicators, reduced-motion support, semantic landmarks, and real anchors. +- Search terms are escaped via `textContent`, not interpolated into HTML. Links use full public GitHub paths and `rel="noopener noreferrer"`. + +## Testing + +```bash +node --check site/assets/catalog.js +node --check site/assets/app.js +python site/tests/browser_smoke.py +``` + +The browser smoke test runs Chromium/Playwright with local HTML/CSS/JS injection to avoid network restrictions. It checks search, filtering, keyboard, copy, responsive layout, and script errors. Use a real browser to inspect layout manually before publishing. + +## Editorial and publication policy + +1. Add only resources whose paths exist in the repository at their declared ref. Check both the title and description against the source; avoid invented capability maturity. +2. Link `main` resources to `main`; link unmerged draft resources to an exact commit with an explicit Development draft label. Remove or update draft index entries when their status changes. +3. The `Current release status` page is authoritative for deployment maturity; a page redesign does not certify verifiers, authorize a signed release, or address repository-ref governance. +4. The site does not display real PR/evidence results or upload source snippets. Adding such features requires an evidence-authentication and privacy review. +5. Do not activate public hosting without maintainer review of the domain, headers, privacy policy, intended public scope, and deployment provenance. diff --git a/site/assets/app.js b/site/assets/app.js new file mode 100644 index 00000000..d926f1a3 --- /dev/null +++ b/site/assets/app.js @@ -0,0 +1,176 @@ +(() => { + 'use strict'; + const source = Array.isArray(window.OVK_CATALOG) ? window.OVK_CATALOG : []; + const categories = ['All resources', 'Guides', 'Checkers', 'Evidence', 'Architecture', 'Trust & release', 'Research', 'Examples', 'Templates', 'Overview']; + const repo = 'https://github.com/fraware/open-verification-kernel/blob/main/'; + const development = new Set(['docs/PR_SOURCE_BINDING_V1.md','docs/SOURCE_SCOPED_JOB_SUMMARY.md']); + const draftRef = 'https://github.com/fraware/open-verification-kernel/blob/837c931d059f74dcda06432d83f5bde759f53d0a/'; + const entries = source.map(([title, description, category, path, keywords], i) => ({id:i,title, description, category, path, keywords, draft:development.has(path), href:(development.has(path)?draftRef:repo)+path})); + const els = { + input: document.getElementById('resourceSearch'), list: document.getElementById('resultList'), quick: document.getElementById('quickResults'), + cats: document.getElementById('categoryList'), count: document.getElementById('resultsCount'), + queryContext: document.getElementById('queryContext'), clear: document.getElementById('clearSearch'), + empty: document.getElementById('emptyState'), reset: document.getElementById('resetAll'), + more: document.getElementById('showMore'), total: document.getElementById('totalCount'), + menu: document.getElementById('menuToggle'), mobile: document.getElementById('mobileNav'), + copy: document.getElementById('copyCode'), copyLabel: document.getElementById('copyLabel'), + quickCode: document.getElementById('quickStartCode'), + }; + const state = { query: '', category: 'All resources', limit: 12, suggestionsOpen: false }; + const norm = str => String(str ?? '').normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); + const words = (value) => value.trim().split(/\s+/).filter(Boolean); + function score(item, queryTerms) { + if (!queryTerms.length) return 1; + const title = norm(item.title), desc = norm(item.description), keys = norm(item.keywords), path = norm(item.path), category = norm(item.category); + let sum = 0; + for (const word of queryTerms) { + const aliases = {authentication:'authorization',authn:'authorization',rbac:'authorization',workflows:'workflow',pipelines:'workflow',proofs:'proof',certificates:'certificate'}; + const raw = norm(word); const t = aliases[raw] || raw; + if (title === t) sum += 30; + else if (title.startsWith(t)) sum += 17; + else if (title.includes(t)) sum += 12; + else if (keys.includes(t)) sum += 8; + else if (category.includes(t)) sum += 6; + else if (desc.includes(t)) sum += 5; + else if (path.includes(t)) sum += 3; + else return 0; + } + return sum; + } + function linkify(text, queryTerms) { + const fragment = document.createDocumentFragment(); + if (!queryTerms.length) { fragment.appendChild(document.createTextNode(text)); return fragment; } + const sorted = [...new Set(queryTerms)].sort((a,b) => b.length-a.length); + let at = 0; + const lower = norm(text); + while (at < text.length) { + let bestPos = -1, bestToken = ''; + for (const token of sorted) { + const pos = lower.indexOf(norm(token), at); + if (pos >= 0 && (bestPos < 0 || pos < bestPos || pos === bestPos && token.length > bestToken.length)) { bestPos = pos; bestToken = token; } + } + if (bestPos < 0) { fragment.appendChild(document.createTextNode(text.slice(at))); break; } + if (bestPos > at) fragment.appendChild(document.createTextNode(text.slice(at,bestPos))); + const mark = document.createElement('mark'); + mark.textContent = text.slice(bestPos,bestPos+bestToken.length); + fragment.appendChild(mark); + at = bestPos+bestToken.length; + } + return fragment; + } + function element(tag, className, text) { + const node = document.createElement(tag); + if (className) node.className = className; + if (typeof text === 'string') node.textContent = text; + return node; + } + function renderFilters() { + const fragment = document.createDocumentFragment(); + for (const cat of categories) { + const button = element('button','category-button'); + button.type = 'button'; button.setAttribute('aria-pressed',String(state.category === cat)); + const label = element('span','',cat); const count = element('span','cat-count',String(cat === 'All resources' ? entries.length : entries.filter(item=>item.category===cat).length)); + button.append(label,count); + button.addEventListener('click',() => {state.category = cat; state.limit = 12; render(); syncUrl(); document.getElementById('explore').scrollIntoView({block:'start',behavior: matchMedia('(prefers-reduced-motion: reduce)').matches?'instant':'smooth'});}); + fragment.append(button); + } + els.cats.replaceChildren(fragment); + } + function filtered() { + const ts = words(state.query); + return entries.map(item => ({item, rank:score(item,ts)})) + .filter(x => x.rank > 0 && (state.category==='All resources'||x.item.category===state.category)) + .sort((a,b)=> b.rank-a.rank || a.item.title.localeCompare(b.item.title)) + .map(x=>x.item); + } + function renderCard(item, ts) { + const a = element('a','resource-card'); a.href=item.href; a.target='_blank'; a.rel='noopener noreferrer'; + a.setAttribute('aria-label',`${item.title} — ${item.category} (opens GitHub in a new tab)`); + const outer = element('div',''); const top = element('div','card-top'); + top.append(element('span','card-category',item.category + (item.draft?' · Development draft':'')),element('span','card-arrow','↗')); + const h3 = element('h3'); h3.append(linkify(item.title,ts)); + const p = element('p'); p.append(linkify(item.description,ts)); + outer.append(top,h3,p); + const path = element('div','card-path',item.path); + a.append(outer,path);return a; + } + function renderQuick(found) { + const showing = Boolean(state.query.trim()) && state.suggestionsOpen; + els.quick.hidden = !showing; + els.input.setAttribute('aria-expanded',String(showing)); + if(!showing){els.quick.replaceChildren();return;} + const frag = document.createDocumentFragment(); + const label = element('div','quick-heading',`TOP MATCHES · ${found.length} FOUND`); + frag.append(label); + for(const item of found.slice(0,4)){ + const anchor = element('a','quick-link');anchor.href=item.href;anchor.target='_blank';anchor.rel='noopener noreferrer'; + anchor.setAttribute('aria-label',`${item.title} (opens GitHub in a new tab)`); + const name = element('span','',item.title);const meta=element('small','',item.category+(item.draft?' · Draft':'')); + const wrap=element('span','quick-link-text');wrap.append(name,meta); + anchor.append(wrap,element('span','quick-arrow','↗'));frag.append(anchor); + } + if(!found.length)frag.append(element('p','quick-empty','No matches. Try a shorter or different term.')); + const view=element('button','quick-all',`View all ${found.length} ${found.length===1?'result':'results'} ↓`); + view.type='button';view.addEventListener('click',()=>{state.suggestionsOpen=false;renderQuick(found);document.getElementById('explore').scrollIntoView({block:'start',behavior:'smooth'});}); + frag.append(view);els.quick.replaceChildren(frag); + } + function render() { + const found = filtered(), tokens=words(state.query); + renderQuick(found); + const fragment=document.createDocumentFragment(); + for(const item of found.slice(0,state.limit)) fragment.append(renderCard(item,tokens)); + els.list.replaceChildren(fragment); + els.count.textContent = `${found.length} ${found.length===1?'resource':'resources'} found`; + els.queryContext.textContent = state.query ? `for “${state.query}”` : ''; + els.empty.hidden = found.length>0; + els.more.hidden = found.length<=state.limit; + if(!els.more.hidden) els.more.textContent=`Show more resources (${found.length-state.limit} remaining) ↓`; + els.clear.hidden=!state.query && state.category==='All resources'; + renderFilters(); + } + function syncUrl() { + const url=new URL(location.href); + if(state.query) url.searchParams.set('q',state.query); else url.searchParams.delete('q'); + if(state.category!=='All resources')url.searchParams.set('category',state.category);else url.searchParams.delete('category'); + try { history.replaceState(null,'',url.pathname+url.search+url.hash); } + catch (error) { if (error.name !== 'SecurityError') throw error; } // Embedded/file previews may disallow URL edits. + } + function setQuery(value,{focus=true,scroll=true}={}) { + state.query=value.trimStart().slice(0,160);state.limit=12;state.suggestionsOpen=Boolean(state.query);els.input.value=state.query;render();syncUrl(); + if(scroll)document.getElementById('explore').scrollIntoView({block:'start',behavior:matchMedia('(prefers-reduced-motion: reduce)').matches?'instant':'smooth'}); + if(focus) els.input.focus({preventScroll:true}); + } + els.input.addEventListener('input',()=>setQuery(els.input.value,{focus:false,scroll:false})); + els.input.addEventListener('keydown',(e)=>{ + if(e.key==='Escape'){ e.preventDefault();if(state.query)setQuery('',{focus:true,scroll:false});else els.input.blur();} + if(e.key==='Enter'){const first=els.quick.querySelector('.quick-link')||els.list.querySelector('.resource-card');if(first && state.query){e.preventDefault();first.focus();}} + if(e.key==='ArrowDown'){const first=els.quick.querySelector('.quick-link')||els.list.querySelector('.resource-card');if(first){e.preventDefault();first.focus();}} + }); + document.addEventListener('keydown',(e)=>{ + if((e.key==='k'&&(e.metaKey||e.ctrlKey)) || (e.key==='/' && !['INPUT','TEXTAREA','SELECT'].includes(document.activeElement.tagName) && !document.activeElement.isContentEditable)){ + e.preventDefault();els.input.focus({preventScroll:false});els.input.select(); + } + }); + document.addEventListener('pointerdown',(e)=>{if(!e.target.closest('.hero-search-group')&&state.suggestionsOpen){state.suggestionsOpen=false;renderQuick(filtered());}}); + els.quick.addEventListener('keydown',(e)=>{if(!['ArrowDown','ArrowUp','Escape'].includes(e.key))return;e.preventDefault();if(e.key==='Escape'){state.suggestionsOpen=false;renderQuick(filtered());els.input.focus();return;}const links=[...els.quick.querySelectorAll('a,button')];const i=links.indexOf(document.activeElement);const next=e.key==='ArrowDown'?i+1:i-1;if(next<0)els.input.focus();else if(nextsetQuery(b.dataset.query)); + els.clear.addEventListener('click',()=>{state.category='All resources';setQuery('',{scroll:false});}); + els.reset.addEventListener('click',()=>{state.category='All resources';setQuery('',{scroll:false});}); + els.more.addEventListener('click',()=>{state.limit+=12;render();}); + els.menu.addEventListener('click',()=>{const opening=els.menu.getAttribute('aria-expanded')!=='true';els.menu.setAttribute('aria-expanded',String(opening));els.menu.setAttribute('aria-label',opening?'Close navigation':'Open navigation');els.mobile.hidden=!opening;}); + els.mobile.querySelectorAll('a').forEach(a=>a.addEventListener('click',()=>{els.mobile.hidden=true;els.menu.setAttribute('aria-expanded','false');els.menu.setAttribute('aria-label','Open navigation');})); + els.copy.addEventListener('click',async()=>{ + const code=els.quickCode.textContent; + try { + if(navigator.clipboard && window.isSecureContext)await navigator.clipboard.writeText(code); + else {const selection=window.getSelection();const range=document.createRange();range.selectNodeContents(els.quickCode);selection.removeAllRanges();selection.addRange(range);const didCopy=document.execCommand('copy');selection.removeAllRanges();if(!didCopy)throw Error('clipboard unavailable');} + els.copyLabel.textContent='Copied';els.copy.setAttribute('aria-label','Copied local commands'); + } catch(_){els.copyLabel.textContent='Select code';els.copy.setAttribute('aria-label','Select code to copy');} + }); + const params=new URLSearchParams(location.search); + state.query=(params.get('q')||'').slice(0,160); + const cat=params.get('category');if(categories.includes(cat))state.category=cat; + els.input.value=state.query;state.suggestionsOpen=Boolean(state.query); + els.total.textContent=String(entries.length); + render(); +})(); diff --git a/site/assets/catalog.js b/site/assets/catalog.js new file mode 100644 index 00000000..29c83733 --- /dev/null +++ b/site/assets/catalog.js @@ -0,0 +1,55 @@ +/* Curated paths verified against the OVK repository tree, October 2026. + Deliberately indexes documentation/examples, not live evidence or certification. */ +window.OVK_CATALOG = [ + ['Project overview','Start here: what OVK does, where it applies, and its verification boundaries.','Overview','README.md','start intro purpose verification kernel'], + ['System specification','Architecture, intent compilation, backend routing and normalized outcomes.','Architecture','docs/SYSTEM_SPEC.md','system spec overview model'], + ['Architecture','How changes become intents, proof obligations and evidence.','Architecture','docs/ARCHITECTURE.md','pipeline components router kernel'], + ['Get started with OVK','Installation and integration guidance for local and GitHub workflows.','Guides','docs/INTEGRATION.md','install setup github action CLI'], + ['Current release status','Publication state, release identity, source-profile maturity and outstanding gates.','Trust & release','docs/CURRENT_RELEASE_STATUS.md','version available tag signed install production'], + ['Backend capabilities','Maturity and semantics of OPA, Z3, CBMC, Cedar, Lean and more.','Checkers','docs/BACKENDS.md','solver backend native experimental support'], + ['Verification lanes','Five bounded checks for authorization, CI secrets, self-protection, infrastructure and deployments.','Checkers','docs/LANES.md','CI security code workflow protection'], + ['Source profile hardening','Supported extraction and trust assumptions for source profiles.','Trust & release','docs/SOURCE_PROFILE_HARDENING.md','coverage AST parsing strict'], + ['Formal specification','The mathematical and engineering claims at the center of the project.','Architecture','docs/FORMAL_SPEC.md','formal methods invariants'], + ['Evidence artifacts','Inspect bundles, generated files and evidence output conventions.','Evidence','docs/ARTIFACTS.md','json report receipts provenance'], + ['Evidence and claims IR','Intermediary representation of claims and verifier obligations.','Evidence','docs/ASSURANCE_IR.md','IR compilation claims obligations'], + ['Verification policy','Configure routing, budget, enforcement and unknown handling.','Guides','docs/POLICY.md','policy yaml configuration defaults'], + ['Contribution guide','How to add a checker, template, evidence test, or documentation.','Guides','docs/CONTRIBUTING.md','contribute development'], + ['Threat model','Boundaries, adversaries and evidence trust assumptions.','Trust & release','docs/THREAT_MODEL.md','threat security assumptions adversarial'], + ['Trusted computing base','Inventory of components trusted by verification and publication.','Trust & release','docs/TRUSTED_COMPUTING_BASE.md','TCB trust dependencies'], + ['Attributable publication','The release identity and publication evidence necessary for trusted artifacts.','Trust & release','docs/ATTRIBUTABLE_PUBLICATION.md','signed release tag provenance'], + ['Release procedure','Pre-tag validation, immutable source identity, and publication controls.','Trust & release','docs/RELEASE.md','publish packaging changelog'], + ['Reproducibility baseline','Cross-platform reproducibility contract and test matrix.','Trust & release','docs/REPRO_BASELINE.md','Windows Ubuntu macOS python'], + ['PR source binding','Difference between contributor head and synthetic merge-preview source identities.','Evidence','docs/PR_SOURCE_BINDING_V1.md','merge preview git source SHA receipt diff'], + ['Source-scoped PR report','Read-only job summary, report portability and explicit reliance limits (draft).','Evidence','docs/SOURCE_SCOPED_JOB_SUMMARY.md','markdown share reviewer source binding advisory'], + ['Evidence quality and release gate','How candidate evidence is qualified before independent reliance.','Trust & release','docs/ASSURANCE_PRODUCT_QUALIFICATION.md','profile strict no-go review'], + ['Reviewing PR evidence','Reviewer workflow for claims, gaps and evidence interpretation.','Guides','docs/PR_ASSURANCE_REVIEW.md','PR code review'], + ['External pilot playbook','Consented, non-blocking evaluation protocol using real pull requests.','Guides','docs/EXTERNAL_PILOT_PLAYBOOK.md','external adoption reviewer prospective'], + ['Consumer validation checklist','Independent integration and reviewer validation checklist.','Guides','docs/CONSUMER_VALIDATION_CHECKLIST.md','pilot customer setup'], + ['Agent repair loop','How agents inspect a failure and propose a bounded repair.','Guides','docs/AGENT_REPAIR_LOOP.md','AI agent fix retry'], + ['Adapter contract','Required semantics and capability declaration for new checkers.','Architecture','docs/ADAPTER_CONTRACT.md','backend interface integration'], + ['FastAPI protected effects','Authorization integrity model for supported FastAPI source constructs.','Checkers','docs/FASTAPI_PROTECTED_EFFECTS.md','python routes protected operations guard'], + ['FastAPI dependency effects','Dependency-based authorization semantics and supported assumptions.','Checkers','docs/FASTAPI_DEPENDENCY_EFFECTS.md','fastapi dependency injection'], + ['Automatic PR verification','Model for PR-level check compilation and execution.','Architecture','docs/AUTOMATIC_PR_ASSURANCE.md','pull request inference planner'], + ['Proof transport and evidence reuse','Applicability constraints for carrying evidence across revisions.','Research','docs/PROTECTED_EFFECT_EVIDENCE_REUSE.md','repair preservation transport'], + ['Guarantee graph','Relationships between claims, dependency paths and supporting evidence.','Research','docs/GUARANTEE_GRAPH.md','graph dependency invalidation'], + ['Guarantee ledger','Recorded guarantee states and evidence continuity.','Research','docs/GUARANTEE_ASSURANCE_LEDGER.md','ledger applicability state'], + ['Incremental verification','Evidence-sensitive rechecking under changes to source and assumptions.','Research','docs/INCREMENTAL_ASSURANCE.md','incremental minimal repair reuse'], + ['Persistent semantic summary cache','Cache validity across source and semantic context changes.','Research','docs/PERSISTENT_SEMANTIC_SUMMARY_CACHE.md','cache invalidation'], + ['Contract impact graph','Dependency and impact tracking for changed verification contracts.','Research','docs/CONTRACT_IMPACT_GRAPH.md','impact invalidation'], + ['Benchmark guide','Internal benchmark methodology, interpretation, and limitations.','Research','docs/BENCHMARK.md','FormalPR benchmark evaluation'], + ['FormalPR holdout governance','Label separation and external result integrity requirements.','Trust & release','docs/FORMALPR_HOLDOUT_GOVERNANCE.md','independent validation benchmark'], + ['External validation','Scope of outside validation and limits on observed evidence.','Trust & release','docs/EXTERNAL_VALIDATION.md','review calibration independent'], + ['Roadmap','Engineering and research direction for the verification infrastructure.','Research','docs/ROADMAP.md','vision roadmap priorities'], + ['Risk: authorization bypass','Input example demonstrating a changed administration authorization boundary.','Examples','examples/auth_regression/input_admin_bypass.json','auth privilege guard fail'], + ['Example: protected admin route','Input example for an authorized route and its protection conditions.','Examples','examples/auth_regression/input_admin_protected.json','auth protected pass'], + ['Example: unsafe CI secrets','GitHub Actions example with exposure to an untrusted PR context.','Examples','examples/ci_secrets/workflow_secrets_on_pr.diff','secrets github workflow secret fork'], + ['Example: CI secrets input','Structured CI secret lane input for a bounded checker.','Examples','examples/ci_secrets/input_secrets_safe.json','workflow safety'], + ['Example: infrastructure exposure','Input example covering public/sensitive infrastructure resources.','Examples','examples/infrastructure_exposure/input_private_sensitive_resource.json','cloud firewall Terraform'], + ['Example: advisory GitHub Action','Copyable current released-version advisory example, with limitations.','Examples','examples/github_workflows/external_consumer.yml','workflow GitHub CI install'], + ['Example: external pilot workflow','Advisory workflow used as a basis for consented external evaluation.','Examples','examples/github_workflows/pilot_fork_adopter.yml','fork reviewer PR'], + ['Example: verification manifest','Multi-lane manifest illustration (caller-scoped inputs).','Examples','examples/verification_manifests/full_mvp.json','all checkers multi-lane'], + ['Security disclosure','Guidelines for reporting vulnerabilities in OVK.','Trust & release','SECURITY.md','report security bugs'], + ['Template: no admin bypass','Reusable intent for an authorization-bypass claim, subject to its source profile.','Templates','templates/authorization/no_admin_route_bypass.intent.json','authorization specification'], + ['Template: protect own CI gate','Reusable intent for preventing an agent from disabling its own CI gate.','Templates','templates/ci_cd/agent_cannot_disable_own_gate.intent.json','agent self protection'], + ['Template: Cedar cross-account deny','Cedar policy intent for cross-account authorization.','Templates','templates/authorization/cedar_cross_account_deny.intent.json','cedar authorization identity'], +]; diff --git a/site/assets/favicon.svg b/site/assets/favicon.svg new file mode 100644 index 00000000..c0ed571f --- /dev/null +++ b/site/assets/favicon.svg @@ -0,0 +1 @@ + diff --git a/site/assets/styles.css b/site/assets/styles.css new file mode 100644 index 00000000..a330e05d --- /dev/null +++ b/site/assets/styles.css @@ -0,0 +1,29 @@ +:root{font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#192034;background:#f8f9fb;font-synthesis:none;-webkit-font-smoothing:antialiased;--ink:#192034;--sub:#626d80;--line:#e4e8ee;--light:#f8f9fb;--white:#fff;--accent:#465cc7;--accent-light:#edf0ff;--focus:#405bd0} +*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:95px}body{margin:0;min-width:320px}button,input{font:inherit}button{cursor:pointer}a{color:inherit;text-decoration:none}a:hover{text-decoration-color:currentColor}a:focus-visible,button:focus-visible,input:focus-visible{outline:3px solid var(--focus);outline-offset:3px}::selection{background:#dce3ff;color:#131928}.container{max-width:1224px;margin:0 auto;padding-left:36px;padding-right:36px}.sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skip-link{position:fixed;z-index:100;top:12px;left:12px;padding:10px 16px;background:#fff;border:2px solid var(--accent);transform:translateY(-200%)}.skip-link:focus{transform:translateY(0)}[hidden]{display:none!important} +.site-header{position:sticky;top:0;z-index:50;background:rgba(248,249,251,.96);border-bottom:1px solid #e8ebf0;backdrop-filter:blur(16px)}.topbar{height:84px;display:flex;align-items:center;justify-content:space-between;gap:30px}.brand{display:flex;align-items:center;gap:11px;flex-shrink:0}.brand-mark{height:37px;width:37px}.brand-lockup{display:flex;flex-direction:column;gap:1px}.brand-lockup strong{font-size:16px;font-weight:760;letter-spacing:-.35px}.brand-lockup small{font-size:10px;letter-spacing:.02em;color:#768093;font-weight:510}.desktop-nav{display:flex;align-items:center;gap:33px;margin-left:auto}.desktop-nav a{font-size:13px;font-weight:590;color:#525d70;transition:color .15s}.desktop-nav a:hover,.desktop-nav a:focus-visible{color:#12182a}.header-actions{display:flex;align-items:center;gap:15px}.header-github{display:inline-flex;align-items:center;gap:7px;border:1px solid #d5dbe6;background:#fff;border-radius:9px;padding:10px 15px;font-size:12px;font-weight:680;white-space:nowrap;box-shadow:0 1px 2px rgba(0,0,0,.02)}.header-github:hover{border-color:#98a5bb}.header-github span{font-size:15px;color:#677388}.mobile-menu-toggle,.mobile-nav{display:none} +.hero{padding-top:90px;padding-bottom:98px;display:grid;grid-template-columns:minmax(0,1.25fr) minmax(340px,.86fr);align-items:center;gap:74px}.eyebrow,.section-kicker{color:#4d61b7;font-weight:770;letter-spacing:.15em;font-size:10px;margin:0 0 22px}.eyebrow{display:flex;align-items:center;gap:10px;white-space:nowrap}.eyebrow-dot{width:6px;height:6px;border-radius:50%;background:#5c72e0}.eyebrow-slash{color:#abb4c2;font-weight:500;margin:0 1px}.hero h1{font-size:clamp(44px,4.8vw,68px);letter-spacing:-.069em;font-weight:735;line-height:1.09;margin:0;max-width:750px}.hero h1 span{color:#4a61c9}.hero-intro{margin:26px 0 31px;line-height:1.7;color:#5e697b;max-width:560px;font-size:15px}.hero-search-group{max-width:570px}.search-field{display:flex;align-items:center;gap:13px;height:63px;border:1px solid #cad1dd;border-radius:12px;background:#fff;box-shadow:0 4px 12px #11182708,0 13px 34px #11182708;padding:0 16px;transition:border-color .16s,box-shadow .16s}.search-field:focus-within{border-color:#5267cf;box-shadow:0 0 0 4px rgba(83,105,208,.11),0 9px 28px #1118270d}.search-icon{color:#748198;flex-shrink:0}.search-field input{flex:1;min-width:0;height:100%;border:none;outline:none;font-size:14px;color:#192034;background:transparent}.search-field input:focus-visible{outline:none}.search-field input::placeholder{color:#919aac}.keyboard-hint{font-size:11px;color:#7a8598;border:1px solid #e3e7ed;box-shadow:0 1px 0 #e0e4ea;border-radius:5px;padding:4px 6px;white-space:nowrap;font-family:inherit}.search-helper{margin:12px 2px 0;color:#8791a1;font-size:12px}.search-helper button{padding:0;border:none;color:#5365ac;background:none;text-decoration:underline;text-decoration-color:#cdd4eb;text-underline-offset:3px}.search-helper button:hover{color:#253eaf}.hero-footnote{margin-top:42px;font-size:11px;color:#8791a2;display:flex;align-items:center;gap:11px;white-space:nowrap}.hero-footnote .line{height:1px;width:27px;background:#9eaacc}.footnote-dot{font-size:14px;color:#bcc5d1} +.hero-visual{border:1px solid #dfe4ec;background:#fff;border-radius:15px;box-shadow:0 24px 56px #1720380c,0 2px 7px #17203808;transform:translateY(7px);overflow:hidden}.visual-title{display:flex;align-items:center;gap:10px;height:70px;padding:0 25px;color:#30394e;font-weight:690;font-size:13px}.visual-icon{width:29px;height:29px;border:1px solid #d9def2;border-radius:8px;display:grid;place-items:center;background:#f4f6ff;color:#5365c7}.visual-icon svg{width:17px}.visual-note{font-size:9px;letter-spacing:.09em;color:#929bac;margin-left:auto;font-weight:740}.visual-rule{height:1px;background:#edf0f4;margin:0 25px}.visual-flow{padding:28px 26px 29px}.visual-flow-item{display:grid;align-items:center;grid-template-columns:40px 1fr auto;gap:10px;font-size:13px}.flow-count{font-size:11px;font-weight:690;color:#7785a6;border:1px solid #e4e8f0;background:#f9fafc;width:34px;height:34px;border-radius:9px;display:grid;place-items:center}.flow-label{font-weight:665}.flow-meta{font-size:11px;color:#858fa1}.flow-join{height:19px;margin-left:16px;border-left:1px dashed #aeb9d7}.visual-status{display:flex;gap:11px;align-items:flex-start;margin:24px 25px 30px;padding:17px;background:#f5f7fc;border:1px solid #eaedf8;border-radius:10px}.status-mark{width:8px;height:8px;background:#6479bf;border-radius:50%;flex-shrink:0;margin-top:5px;box-shadow:0 0 0 3px #e5eaf9}.visual-status strong{font-size:12px;font-weight:735}.visual-status p{font-size:11px;line-height:1.7;color:#687589;margin:6px 0 0}.visual-bottom{background:#f9fafc;border-top:1px solid #eef0f3;padding:17px 25px;display:flex;justify-content:space-between;color:#8994a6;font-size:10px;font-weight:620;letter-spacing:.03em}.visual-bottom span:last-child{font-size:14px;color:#586ec1} +.value-strip{background:#f1f3f7;border-top:1px solid #e7eaf0;border-bottom:1px solid #e7eaf0}.strip-grid{display:grid;grid-template-columns:repeat(3,1fr)}.strip-item{display:flex;align-items:flex-start;gap:17px;padding:26px 12px}.strip-item:first-child{padding-left:0}.strip-item+ .strip-item{border-left:1px solid #dfe3eb;padding-left:42px}.strip-item>strong{color:#7282a8;font-size:11px;font-weight:760;margin-top:5px}.strip-item h2{font-size:13px;letter-spacing:-.01em;font-weight:730;margin:0 0 6px}.strip-item p{font-size:12px;color:#758093;margin:0} +.explorer-section{padding-top:107px;padding-bottom:110px}.section-heading{display:flex;justify-content:space-between;align-items:end;gap:25px;margin-bottom:39px}.section-kicker{margin-bottom:15px}.section-heading h2,.start-text h2,.trust-layout h2{font-size:clamp(30px,3.3vw,42px);line-height:1.18;letter-spacing:-.053em;font-weight:735;margin:0}.section-heading p:not(.section-kicker){font-size:14px;color:#727d8f;margin:13px 0 0;line-height:1.65}.section-counter{text-align:right;display:flex;flex-direction:column;flex-shrink:0;gap:4px;color:#919aaa;font-size:11px;padding-bottom:1px}.section-counter strong{font-size:28px;letter-spacing:-.07em;color:#30394e;font-weight:690;font-variant-numeric:tabular-nums} +.explore-layout{display:grid;grid-template-columns:215px minmax(0,1fr);gap:54px}.filter-sidebar{border-top:1px solid #e4e8ee;padding-top:22px}.sidebar-overline{font-size:10px;color:#9ba5b6;font-weight:770;letter-spacing:.13em;margin:0 0 15px 11px}.category-list{display:flex;flex-direction:column;gap:3px}.category-button{width:100%;display:flex;justify-content:space-between;align-items:center;text-align:left;border:1px solid transparent;border-radius:8px;padding:11px 12px;color:#6d7787;background:transparent;font-size:12px;font-weight:570;transition:background .12s,color .12s}.category-button:hover{background:#eef1f8;color:#2a3452}.category-button[aria-pressed=true]{color:#314ea3;background:#e9edf9;border-color:#e0e6f8;font-weight:730}.category-button .cat-count{font-size:11px;color:#9aa5b5;font-variant-numeric:tabular-nums}.category-button[aria-pressed=true] .cat-count{color:#5166ad}.sidebar-help{margin-top:39px;border-top:1px solid #e6eaf0;padding:24px 9px;display:flex;gap:13px;color:#8d9bb0;font-size:12px}.sidebar-help>span{color:#5269bf;font-size:17px}.sidebar-help p{margin:0;line-height:1.8}.sidebar-help a{color:#405cae;text-decoration:underline;text-decoration-color:#c3ccea;text-underline-offset:3px}.library-main{min-width:0}.library-toolbar{height:49px;display:flex;align-items:flex-start;justify-content:space-between;border-bottom:1px solid #e5e9ee;gap:12px;font-size:12px;color:#6c7688}.library-toolbar>div{display:flex;gap:10px;flex-wrap:wrap}.library-toolbar #resultsCount{font-weight:710;color:#34405c}.query-context{color:#8993a2;overflow-wrap:anywhere}.clear-action{border:0;background:transparent;color:#526ac6;font-size:12px;padding:0;white-space:nowrap}.resource-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:13px;margin-top:18px}.resource-card{display:flex;flex-direction:column;justify-content:space-between;min-height:174px;background:#fff;border:1px solid #e3e8ef;border-radius:11px;padding:20px 21px;transition:border-color .15s,box-shadow .15s,transform .15s}.resource-card:hover{border-color:#aab7dd;box-shadow:0 7px 25px #1825470b;transform:translateY(-2px)}.resource-card:focus-visible{outline-offset:2px}.card-top{display:flex;justify-content:space-between;align-items:center;gap:10px}.card-category{font-size:10px;font-weight:710;letter-spacing:.035em;color:#6679b1}.card-arrow{color:#9ba6b9;font-size:19px;font-weight:300}.resource-card:hover .card-arrow{color:#4b64bb}.resource-card h3{font-size:16px;line-height:1.35;letter-spacing:-.025em;font-weight:720;margin:16px 0 8px}.resource-card p{font-size:12px;line-height:1.65;color:#768194;margin:0;flex:1}.resource-card .card-path{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:10px;line-height:1.5;color:#a1aab9;margin-top:20px;overflow-wrap:anywhere}.resource-card mark{color:inherit;background:#ebedff;border-radius:2px;padding:0 1px}.show-more{margin:25px auto 0;display:block;background:#fff;border:1px solid #d9dfea;border-radius:9px;padding:13px 23px;font-size:12px;font-weight:690;color:#32415e}.show-more:hover{border-color:#a4b2d3}.show-more span{margin-left:9px;color:#7184bd}.index-note{font-size:11px;line-height:1.7;color:#929bab;margin:27px 0 0;max-width:700px}.empty-state{text-align:center;background:#fff;border:1px dashed #d4dbea;margin-top:17px;padding:52px 18px;border-radius:13px}.empty-symbol{font-size:39px;color:#b3bed5}.empty-state h3{font-size:17px;margin:12px 0 7px}.empty-state p{max-width:410px;margin:0 auto 18px;color:#798699;line-height:1.6;font-size:12px}.small-button{padding:10px 16px;background:#eef1fc;border:1px solid #cbd3f5;border-radius:7px;color:#334fa2;font-weight:690;font-size:12px} +.how-section{padding:94px 0 111px;background:#fff;border-top:1px solid #e8ebf0;border-bottom:1px solid #e9edf2}.how-section .section-heading{margin-bottom:36px}.steps-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.step-card{border:1px solid #e5e9ef;background:#fafbfc;border-radius:12px;padding:24px;min-height:247px}.step-number{font-size:10px;color:#657bbc;letter-spacing:.11em;font-weight:760}.step-symbol{font-family:ui-monospace,Consolas,monospace;margin:24px 0 22px;display:grid;place-items:center;width:49px;height:49px;border:1px solid #e1e6ef;border-radius:12px;color:#4b62b6;font-size:22px;background:#fff}.step-card h3{font-size:16px;margin:0 0 10px;letter-spacing:-.02em}.step-card p{font-size:12px;color:#768193;line-height:1.75;margin:0;max-width:295px} +.start-section{display:grid;grid-template-columns:1fr 1.17fr;gap:70px;align-items:center;padding-top:118px;padding-bottom:120px}.start-text h2{max-width:400px}.start-text>p:not(.section-kicker){font-size:14px;color:#717c8e;max-width:440px;line-height:1.8;margin:19px 0 25px}.text-link{font-size:13px;font-weight:690;color:#425eaf;text-decoration:underline;text-decoration-color:#bdc9ed;text-underline-offset:5px}.text-link span{margin-left:6px}.code-panel{min-width:0;border-radius:12px;background:#1c2435;border:1px solid #344053;box-shadow:0 20px 38px #12203f1c;overflow:hidden;color:#dee4ed}.code-panel-head{padding:19px 23px;border-bottom:1px solid #333e51;display:flex;justify-content:space-between;align-items:center;gap:10px;font-size:10px;letter-spacing:.09em;font-weight:710;color:#a7b4c8}.terminal-dot{display:inline-block;background:#8ca0ec;height:6px;width:6px;border-radius:50%;margin-right:9px}.code-panel-head button{border:1px solid #526076;background:#2c374b;color:#e5ebf7;border-radius:6px;padding:7px 10px;font-size:11px;letter-spacing:0}.code-panel-head button:hover{background:#394960}.code-panel pre{margin:0;padding:28px 23px 33px;overflow-x:auto;line-height:2;font-size:11px;tab-size:2;color:#e2e9f8}.code-panel code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.code-panel-footer{border-top:1px solid #333e51;padding:13px 23px;color:#9caac1;font-size:10px;line-height:1.6} +.trust-section{background:#edf1f7;border-top:1px solid #e3e8f0;padding:87px 0 92px}.trust-layout{display:grid;grid-template-columns:1fr 1fr;gap:90px}.trust-layout h2{max-width:420px}.trust-layout p:not(.section-kicker){color:#69778b;max-width:448px;line-height:1.8;font-size:14px}.trust-details{border-top:1px solid #d4dbe7}.trust-row{display:flex;align-items:center;justify-content:space-between;gap:18px;border-bottom:1px solid #d4dbe7;padding:20px 0;font-size:12px}.trust-row span{color:#68778f}.trust-row strong{color:#293855;font-weight:680;text-align:right}.trust-details>a{display:inline-block;margin-top:28px;color:#344ea3;text-decoration:underline;text-underline-offset:5px;text-decoration-color:#aebde7;font-weight:680;font-size:12px}.trust-details>a span{margin-left:4px} +.site-footer{background:#fff;padding:54px 0 22px}.footer-main{display:flex;justify-content:space-between;gap:35px}.footer-brand{font-size:17px;font-weight:785;letter-spacing:-.02em}.footer-brand span{font-size:11px;color:#818b9c;margin-left:9px;font-weight:510;letter-spacing:0}.footer-main p{color:#8b95a4;font-size:12px;margin:14px 0}.footer-links{display:flex;align-items:flex-start;gap:28px;font-size:12px;color:#5a6576}.footer-links a:hover{color:#334fa9}.footer-bottom{margin-top:43px;padding-top:20px;border-top:1px solid #edf0f4;display:flex;justify-content:space-between;gap:20px;color:#99a3b1;font-size:10px;line-height:1.65} +@media(min-width:1500px){.hero{padding-top:105px;padding-bottom:113px}} +@media(max-width:1050px){.container{padding-left:28px;padding-right:28px}.hero{gap:36px;grid-template-columns:1.1fr .9fr}.hero h1{font-size:clamp(41px,5vw,60px)}.desktop-nav{gap:17px}.explore-layout{gap:26px;grid-template-columns:190px minmax(0,1fr)}.start-section{gap:35px}.trust-layout{gap:45px}} +@media(max-width:760px){.container{padding-left:20px;padding-right:20px}.topbar{height:72px}.desktop-nav{display:none}.brand-lockup small{font-size:9px}.mobile-menu-toggle{display:flex;background:transparent;border:0;padding:8px;flex-direction:column;gap:4px;width:39px;justify-content:center}.mobile-menu-toggle span{height:2px;width:23px;background:#2a3449;border-radius:2px}.mobile-nav:not([hidden]){display:flex;flex-direction:column;padding-top:0;padding-bottom:18px;gap:0}.mobile-nav a{padding:12px 0;border-top:1px solid #e5eaf1;font-size:13px;color:#39455b}.hero{padding-top:57px;padding-bottom:61px;grid-template-columns:1fr;gap:42px}.hero h1{font-size:clamp(39px,8.4vw,55px)}.hero-intro{margin-top:19px}.hero-visual{max-width:510px;transform:none}.hero-footnote{margin-top:28px}.strip-grid{grid-template-columns:1fr}.strip-item,.strip-item:first-child{padding:17px 0;gap:14px}.strip-item+.strip-item{border-top:1px solid #dfe3eb;border-left:0;padding-left:0}.section-heading{align-items:flex-start}.explorer-section{padding-top:70px;padding-bottom:80px}.explore-layout{grid-template-columns:1fr;gap:20px}.filter-sidebar{border-top:none;padding-top:0}.sidebar-overline{margin-left:0;margin-bottom:9px}.category-list{flex-direction:row;overflow-x:auto;scrollbar-width:thin;padding-bottom:8px;gap:5px}.category-button{width:auto;white-space:nowrap;gap:12px;border:1px solid #e3e8ef;background:#fff;padding:10px 12px}.sidebar-help{display:none}.library-toolbar{height:43px}.resource-grid{gap:12px}.how-section{padding:75px 0 85px}.steps-grid{grid-template-columns:1fr}.step-card{min-height:unset}.start-section{grid-template-columns:1fr;padding-top:82px;padding-bottom:88px;gap:35px}.trust-section{padding:75px 0}.trust-layout{grid-template-columns:1fr;gap:28px}.footer-main{flex-direction:column;gap:17px}.footer-bottom{flex-direction:column;gap:8px}} +@media(max-width:520px){.header-github{padding:9px 11px}.brand-lockup small{display:none}.eyebrow{font-size:9px;letter-spacing:.095em}.hero h1{font-size:39px;letter-spacing:-.058em}.search-field{height:59px;padding:0 12px}.search-field input{font-size:13px}.keyboard-hint{display:none}.hero-footnote{white-space:normal;line-height:1.5}.hero-visual{margin-top:3px}.section-heading h2,.start-text h2,.trust-layout h2{font-size:32px}.section-counter{display:none}.resource-grid{grid-template-columns:1fr}.resource-card{min-height:160px}.library-toolbar{height:auto;min-height:47px}.code-panel pre{font-size:10px;padding:22px 16px}.code-panel-head,.code-panel-footer{padding-left:16px;padding-right:16px}.trust-row{align-items:flex-start;flex-direction:column;gap:6px}.trust-row strong{text-align:left}.footer-links{gap:20px}} +@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*::before,*::after{transition:none!important;animation:none!important}.resource-card:hover{transform:none}} +/* Prevent intrinsic widths of horizontal category filters from stretching the mobile page. */ +@media(max-width:760px){.explore-layout{grid-template-columns:minmax(0,1fr)}.filter-sidebar,.library-main{min-width:0;max-width:100%}.category-list{min-width:0;max-width:100%;overflow-x:auto;overscroll-behavior-inline:contain}} + +/* Immediate results keep search useful without navigating below the hero. */ +.hero-search-group{position:relative}.quick-results{position:absolute;z-index:25;top:67px;left:0;right:0;padding:10px;background:#fff;border:1px solid #dce2ef;border-radius:12px;box-shadow:0 19px 46px #17254224;max-height:380px;overflow:auto}.quick-heading{font-size:10px;color:#8a95aa;font-weight:760;letter-spacing:.1em;padding:10px 12px 12px}.quick-link{display:flex;justify-content:space-between;align-items:center;gap:18px;padding:11px 12px;border-radius:7px}.quick-link:hover,.quick-link:focus-visible{background:#f2f5fd}.quick-link-text{min-width:0;display:flex;flex-direction:column;gap:4px;font-size:13px;font-weight:660}.quick-link-text small{color:#818da0;font-size:11px;font-weight:520}.quick-arrow{font-size:16px;color:#657ac4}.quick-empty{font-size:12px;color:#68788d;padding:8px 12px}.quick-all{width:100%;text-align:center;background:#f5f7fc;border:1px solid #e5eaf5;color:#435fb0;border-radius:7px;margin-top:8px;padding:12px;font-weight:710;font-size:12px}.quick-all:hover{background:#ebeffb} +@media(max-width:760px){.quick-results{top:65px}} + +/* Narrow screens should not inherit a long eyebrow's min-content grid width. */ +@media(max-width:520px){.hero{grid-template-columns:minmax(0,1fr)}.hero-content,.hero-search-group{min-width:0;max-width:100%}.eyebrow{white-space:normal;line-height:1.6}} +/* High-contrast secondary text for readability in documentation workflows. */ +.hero-footnote,.search-helper,.section-counter,.sidebar-overline,.index-note,.footer-bottom,.visual-note,.visual-flow .flow-meta,.resource-card .card-path,.category-button .cat-count,.quick-heading,.quick-link-text small{color:#647186} +.resource-card p,.step-card p,.strip-item p,.section-heading p:not(.section-kicker),.empty-state p,.visual-status p,.hero-intro{color:#617085} diff --git a/site/index.html b/site/index.html new file mode 100644 index 00000000..eee6dbd7 --- /dev/null +++ b/site/index.html @@ -0,0 +1,124 @@ + + + + + + + + + + + + + Explore OVK — Open Verification Kernel + + + + + + + + + +
+
+
+

OPEN VERIFICATION KERNEL / RESOURCE EXPLORER

+

Understand what
the evidence establishes.

+

Find the verification claim, source profile, checker, or research you need. Every resource links to its source. Every result has a scope.

+ +
Curated repository documentation · No account or API key needed
+
+
+
How to read a resultILLUSTRATIVE
+
+
+
01Changed sourceInput
+
+
02Bounded claimScope
+
+
03Checker evidenceResult
+
+
+
Review the evidence, not a badge.

See assumptions, unsupported inputs, and provenance before relying on a result.

+
Source → Claim → Evidence
+
+
+ +
+
+
01

Understand

What each supported check means

+
02

Locate

Specifications, evidence and examples

+
03

Apply

Use an advisory workflow responsibly

+
+
+ +
+

RESOURCE LIBRARY

Find your starting point.

Search a curated index of source-linked OVK resources.

—indexed resources
+
+ +
+
Loading resources…
+
+ + +

This search indexes a curated set of repository files. It does not search private evidence, execute a verifier, or assert that a resource is production-qualified.

+
+
+
+ +
+

THE MODEL

From a change to a scoped claim.

A simple way to understand what OVK records—and where human judgment still matters.

+
+
01 / INPUT

Identify the change

Connect the proposed diff to the exact source revision and the constructs being examined.

+
02 / CONTRACT

State the bounded claim

Declare the property, supported source profile, assumptions, and selected checker.

+
03 / EVIDENCE

Inspect the result

Review supporting observations, unresolved coverage, and applicability before making a decision.

+
+
+ +
+

GET STARTED

Start with a local advisory check.

Inspect the open-source development tree and run an example without changing repository branch protections or granting write permissions.

Read the integration guide
+
LOCAL DEVELOPMENT EXAMPLE
git clone https://github.com/fraware/open-verification-kernel.git
+cd open-verification-kernel
+pip install -e '.[dev]'
+ovk check --changed-files examples/multi_surface/pr_combined.diff --advisory
+
+ +

TRUST & RELEASE

Clear limits are part of the product.

OVK reports evidence for bounded properties. A local result, a passing CI job, or a reusable report does not establish whole-system correctness or authorization to merge.

Published sourceVerify the release identity
Unknown or unsupportedKeep human review in control
Evidence across changesCheck applicability before reuse
Read current release and qualification status
+
+ + + diff --git a/site/tests/browser_smoke.py b/site/tests/browser_smoke.py new file mode 100644 index 00000000..1e78cce6 --- /dev/null +++ b/site/tests/browser_smoke.py @@ -0,0 +1,70 @@ +"""Smoke-test the no-backend site without relying on local network access. + +Requires Python playwright and an installed Chromium executable. This is a +local/optional test; the static UI itself has no Python or browser dependency. +""" +from pathlib import Path +import os +from playwright.sync_api import sync_playwright + +ROOT = Path(__file__).resolve().parents[1] + + +def setup(page): + page.set_content((ROOT / "index.html").read_text(), wait_until="load") + page.add_style_tag(content=(ROOT / "assets/styles.css").read_text()) + page.add_script_tag(content=(ROOT / "assets/catalog.js").read_text()) + page.add_script_tag(content=(ROOT / "assets/app.js").read_text()) + + +def main(): + with sync_playwright() as p: + executable = os.environ.get("OVK_SITE_CHROMIUM", "/usr/bin/chromium") + kwargs = {"executable_path": executable} if Path(executable).exists() else {} + browser = p.chromium.launch(headless=True, args=["--no-sandbox"], **kwargs) + errors = [] + desktop = browser.new_page(viewport={"width": 1440, "height": 900}) + desktop.on("pageerror", lambda e: errors.append(str(e))) + setup(desktop) + assert desktop.locator("#totalCount").inner_text() == "51" + assert desktop.locator(".resource-card").count() == 12 + desktop.locator("#resourceSearch").fill("source binding") + assert desktop.locator("#quickResults").is_visible() + assert desktop.locator("#quickResults .quick-link").count() >= 1 + titles = desktop.locator(".resource-card h3").all_text_contents() + assert "PR source binding" in titles + assert "Source-scoped PR report" in titles + assert "Development draft" in desktop.locator(".resource-card").first.inner_text() + desktop.locator("#resourceSearch").fill("wordthatexistsnowhere") + assert desktop.locator("#emptyState").is_visible() + desktop.locator("#resetAll").click() + assert desktop.locator("#resourceSearch").input_value() == "" + desktop.keyboard.press("Control+k") + assert desktop.locator("#resourceSearch").evaluate("el => document.activeElement === el") + desktop.keyboard.type("FastAPI") + assert desktop.locator(".resource-card").count() >= 1 + desktop.keyboard.press("Escape") + assert desktop.locator("#resourceSearch").input_value() == "" + desktop.locator("#copyCode").click() + assert desktop.locator("#copyLabel").inner_text() in ("Copied", "Select code") + + mobile = browser.new_page(viewport={"width": 390, "height": 844}) + mobile.on("pageerror", lambda e: errors.append(str(e))) + setup(mobile) + mobile.locator("#menuToggle").click() + assert mobile.locator("#mobileNav").is_visible() + mobile.locator('#mobileNav a[href="#explore"]').click() + assert not mobile.locator("#mobileNav").is_visible() + mobile.locator("#resourceSearch").fill("ci secrets") + assert mobile.locator(".resource-card").count() >= 1 + assert mobile.evaluate("document.documentElement.scrollWidth - innerWidth") <= 0 + # The narrowest supported mobile layout must also stay within the viewport. + mobile.set_viewport_size({"width": 320, "height": 800}) + assert mobile.evaluate("document.documentElement.scrollWidth - innerWidth") <= 0 + assert not errors, errors + browser.close() + print("PASS — desktop/mobile layout, search, accessible navigation, JS execution") + + +if __name__ == "__main__": + main() diff --git a/site/tests/catalog_contract.mjs b/site/tests/catalog_contract.mjs new file mode 100644 index 00000000..9f98f1e7 --- /dev/null +++ b/site/tests/catalog_contract.mjs @@ -0,0 +1,47 @@ +// Dependency-free structural tests for the OVK public resource index. +// Run from repository root: node site/tests/catalog_contract.mjs +import assert from 'node:assert/strict'; +import { readFileSync, existsSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { runInNewContext } from 'node:vm'; + +const root = resolve(fileURLToPath(new URL('../../', import.meta.url))); +const read = (p) => readFileSync(resolve(root,p),'utf8'); +const context = {window: {}}; +runInNewContext(read('site/assets/catalog.js'), context, {timeout: 1000}); +const items = Array.from(context.window.OVK_CATALOG ?? []); +assert.equal(items.length,51, 'curated catalog count unexpectedly changed; review links and description'); +const categories = new Set(['Overview','Architecture','Guides','Trust & release','Checkers','Evidence','Research','Examples','Templates']); +const drafts = new Set(['docs/PR_SOURCE_BINDING_V1.md','docs/SOURCE_SCOPED_JOB_SUMMARY.md']); +const seen = new Set(); +for (const entry of items) { + assert.equal(entry.length,5,'resource entry needs title, description, category, path and keywords'); + const [title,description,category,path,keywords] = entry; + for(const part of [title,description,category,path,keywords]){ + assert.equal(typeof part,'string'); + assert.ok(part.trim(),'empty catalog field'); + } + assert.ok(categories.has(category),`unknown category: ${category}`); + assert.ok(!seen.has(path),`duplicate path: ${path}`); + assert.ok(!path.startsWith('/')&&!path.includes('..'),`non-repository path: ${path}`); + seen.add(path); + if(!drafts.has(path)){ + assert.ok(existsSync(resolve(root,path)),`broken repository resource path: ${path}`); + } +} +assert.ok([...drafts].every(p=>seen.has(p)), 'development-only resources must be labeled in UI'); +const html=read('site/index.html'),script=read('site/assets/app.js'),css=read('site/assets/styles.css'); +for(const marker of [ + 'id="resourceSearch"','id="categoryList"','id="quickResults"', + 'id="resultList"','id="emptyState"','id="totalCount"', + 'id="menuToggle"','id="copyCode"','href="#main"' +])assert.ok(html.includes(marker),`missing HTML control ${marker}`); +for(const asset of ['assets/catalog.js','assets/app.js','assets/styles.css','assets/favicon.svg']) + assert.ok(existsSync(resolve(root,'site',asset)),`missing asset ${asset}`); +assert.ok(script.includes('Development draft'), 'draft status must be disclosed'); +assert.ok(script.includes('837c931d059f74dcda06432d83f5bde759f53d0a'), 'draft links must pin actual source SHA'); +assert.ok(script.includes('textContent'), 'render search terms safely as text'); +assert.ok(!script.includes('.innerHTML'), 'do not interpolate user search into HTML'); +assert.ok(css.includes('prefers-reduced-motion'), 'motion reduction support missing'); +console.log(`PASS site resource contract (${items.length} indexed source paths; ${drafts.size} pinned drafts)`);