diff --git a/.github/workflows/adversarial.yml b/.github/workflows/adversarial.yml index 5d1c0d4..61d64d2 100644 --- a/.github/workflows/adversarial.yml +++ b/.github/workflows/adversarial.yml @@ -22,7 +22,8 @@ jobs: - name: Checkout exact revision uses: actions/checkout@v7 with: - ref: ${{ github.sha }} + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false - name: Install JavaScript test runtime uses: actions/setup-node@v6 with: @@ -38,9 +39,16 @@ jobs: if: always() uses: actions/upload-artifact@v7 with: - name: wcode-native-layout-${{ github.sha }} - path: target/wcode-*-audit.json - if-no-files-found: warn + name: wcode-native-layout-${{ github.run_id }}-${{ github.run_attempt }} + path: | + target/wcode-webkit-layout.json + target/wcode-browser-audit.json + target/wcode-layout-fixture.html + target/wcode-browser-fixture.html + src/ui/intelligence_web/styles/ + tests/unit/ui/browser_webkit.swift + tests/unit/ui/layout_webkit.swift + if-no-files-found: error retention-days: 14 adversarial: @@ -52,7 +60,8 @@ jobs: - name: Checkout exact revision uses: actions/checkout@v7 with: - ref: ${{ github.sha }} + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Cache Cargo @@ -65,14 +74,18 @@ jobs: - name: Compile adversarial Rust cases run: cargo test --locked --lib --no-run - name: Run all 30 rounds against stable inputs - run: node tests/release_audit.cjs + run: | + git rev-parse HEAD > target/wcode-audit-revision.txt + node tests/release_audit.cjs - name: Retain revision-bound audit results if: always() uses: actions/upload-artifact@v7 with: - name: wcode-adversarial-30-${{ github.sha }} + name: wcode-adversarial-30-${{ github.run_id }}-${{ github.run_attempt }} path: | + target/wcode-audit-revision.txt target/wcode-adversarial-30.json - target/wcode-*-audit.json - if-no-files-found: warn + target/wcode-webkit-layout.json + target/wcode-browser-audit.json + if-no-files-found: error retention-days: 14 diff --git a/docs/manual/releases/v0.7.5.md b/docs/manual/releases/v0.7.5.md index bdfd1ce..9a0d3b9 100644 --- a/docs/manual/releases/v0.7.5.md +++ b/docs/manual/releases/v0.7.5.md @@ -27,6 +27,8 @@ Unregistered hosts, invalid origins and unauthenticated requests still fail clos Code statistics use shrinkable label/value tracks and a separate progress row instead of fixed widths that overlap adjacent columns. Verification previews use content-driven height, wrapping long test names and a dedicated fail-fast row layout. The evidence inspector stacks at constrained widths. English descriptive copy no longer exposes internal translation keys. +Long evidence identifiers, policy/model chips and diagnostic lines wrap inside the inspector rather than forcing a horizontal scrollbar or extending beyond their grid row. Regression checks cover the inspector's inner sections as well as the outer card; the fix preserves complete text rather than hiding overflow. + Refresh errors distinguish authorization, Host/origin denial, timeout, malformed responses and rendering exceptions without placing internal paths or stacks in page text. Full project snapshots have a bounded 120-second deadline; lightweight cached requests retain 30 seconds. Rendering failure cannot certify the new snapshot revision. Adaptive checks remain a read-only planning preview, not execution from the Observatory. ## Repository integrity and fail-closed release gates @@ -53,4 +55,6 @@ Run `cargo test --locked`, `cargo clippy --locked --all-targets -- -D warnings`, `node tests/release_audit.cjs` runs 30 distinct adversarial rounds on macOS, with independent Rust and Web lanes, nonempty-test checks, bounded execution and a before/after input digest. Results are recorded in `target/wcode-adversarial-30.json`. The full production-page WebKit audit covers 96 combinations of 12 widths (320–1920 pixels), English/Chinese, light/dark and proof/overview. Portable rendering-failure scenarios are included in `cargo test`; the actual WebKit audit also runs in macOS CI. +The full-page runner hosts WebKit in a real window, waits for fonts and animation frames, and validates the requested viewport. Navigation failures and timeouts produce partial failure reports instead of a silent hang. The independent adversarial workflow preserves the synthetic HTML, shipped CSS, layout reports, exact checked-out revision and 30-round results for diagnosis; failed geometry checks are not skipped to publish. + Publication follows reviewed commit → non-force push → successful CI for that exact SHA → version tag → distribution builds and smoke tests. After installation, restart the running wcode process, reconnect MCP and reopen the Observatory; rebuilding a binary does not update an already-running server. diff --git a/docs/manual/releases/v0.7.5.zh-CN.md b/docs/manual/releases/v0.7.5.zh-CN.md index 37d5cfa..511b9b0 100644 --- a/docs/manual/releases/v0.7.5.zh-CN.md +++ b/docs/manual/releases/v0.7.5.zh-CN.md @@ -27,6 +27,8 @@ v0.7.5 加固入口所有权、TUI 按键分发、观测台渲染与仓库验证 代码统计改用可收缩的名称/数值列,进度条单独占一行,不再因固定列宽覆盖相邻统计。验证预览按内容计算高度,长测试名自动换行,快速失败列表使用独立布局。受限宽度下证据检查器移到下方。英文说明不再暴露内部翻译键名。 +过长的证据标识、策略/模型标签和诊断文本会在检查器内部换行,不再撑出横向滚动条或超出网格行。回归检查同时覆盖外层卡片和内部各节,修复保留完整文本,不以隐藏溢出内容掩盖问题。 + 刷新错误区分授权、Host/Origin 拒绝、超时、响应异常和渲染异常,不在页面直接显示内部路径或堆栈。完整项目快照请求使用 120 秒有界等待,轻量缓存请求保持 30 秒。渲染失败不能把新快照版本记为成功;自适应检查仍是只读规划预览,不会从观测台直接执行。 ## 仓库完整性与失败关闭的发布门禁 @@ -53,4 +55,6 @@ wcode help-all --json macOS 上运行 `node tests/release_audit.cjs` 可执行 30 轮不同场景的对抗检查:Rust 与 Web 两条独立验证线并行,每项要求实际命中测试、执行有界,并比对检查前后的输入摘要。结果保存在 `target/wcode-adversarial-30.json`。完整生产页面的 WebKit 审计覆盖十二种宽度(320–1920 像素)、中英文、明暗主题与证据/总览页面的 96 种组合。跨平台渲染故障场景已接入 `cargo test`,macOS CI 也执行真实 WebKit 审计。 +完整页面测试将 WebKit 挂载到真实窗口,等待字体和动画帧就绪,并核对实际视口宽度。导航失败或超时会输出部分失败报告,不再静默卡住。独立对抗检查工作流保留合成测试页、随包 CSS、布局报告、实际检出的提交号和 30 轮结果,便于诊断;不会跳过布局失败来发布。 + 发布顺序为审查提交 → 非强制推送 → 精确 SHA 的 CI 全绿 → 版本标签 → 分发包构建与冒烟测试。安装后必须重启正在运行的 wcode、重新连接 MCP 并重新打开观测台;构建新二进制不会自动更新旧服务进程。 diff --git a/src/ui/intelligence_web/styles/data.css b/src/ui/intelligence_web/styles/data.css index 5598570..b7e6a1e 100644 --- a/src/ui/intelligence_web/styles/data.css +++ b/src/ui/intelligence_web/styles/data.css @@ -19,3 +19,8 @@ .lane-spacer{height:14px;}.card-copy{margin-top:6px;}.card-gap{margin-top:10px;}.compact-banner{margin:14px 0 0;}.quality-summary{border-top:0;}.quality-summary .pills{margin-top:0;}.risk-heading{margin-top:16px!important;} footer{margin-top:8px;padding:0 4px;color:var(--muted);font-size:12px;} code{font:13px var(--font-mono);color:var(--soft);} + +/* Unbounded identifiers must wrap before creating an inspector scrollbar. */ +.evidence-inspector-state{flex-wrap:wrap;min-width:0;} +.evidence-inspector-state code,.evidence-inspector-card .inspector-chip{min-width:0;max-width:100%;white-space:normal;overflow-wrap:anywhere;} +.evidence-inspector-section pre{overflow-wrap:anywhere;} diff --git a/tests/unit/ui/browser_webkit.swift b/tests/unit/ui/browser_webkit.swift index 768fdda..0c8303c 100644 --- a/tests/unit/ui/browser_webkit.swift +++ b/tests/unit/ui/browser_webkit.swift @@ -17,7 +17,7 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { const describe=el=>{ if(!el)return null; const b=r(el),s=getComputedStyle(el); - return {tag:el.tagName,id:el.id,className:el.className,left:b.left,right:b.right,top:b.top,bottom:b.bottom,width:b.width,height:b.height,scrollWidth:el.scrollWidth,clientWidth:el.clientWidth,display:s.display,position:s.position,minWidth:s.minWidth,maxWidth:s.maxWidth,gridTemplateColumns:s.gridTemplateColumns,gridTemplateRows:s.gridTemplateRows,margin:s.margin,padding:s.padding,overflow:s.overflow,transform:s.transform}; + return {tag:el.tagName,id:el.id,className:el.className,left:b.left,right:b.right,top:b.top,bottom:b.bottom,width:b.width,height:b.height,scrollWidth:el.scrollWidth,clientWidth:el.clientWidth,scrollHeight:el.scrollHeight,clientHeight:el.clientHeight,display:s.display,position:s.position,minWidth:s.minWidth,maxWidth:s.maxWidth,gridTemplateColumns:s.gridTemplateColumns,gridTemplateRows:s.gridTemplateRows,margin:s.margin,padding:s.padding,overflow:s.overflow,transform:s.transform}; }; const check=(ok,label,el=null,parent=null)=>{ if(ok)return; @@ -27,7 +27,7 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { check(window.__layoutReady===true,'production boot failed'); check(document.documentElement.scrollWidth<=innerWidth+1,'page overflow',document.documentElement); check(document.querySelectorAll('[role="tab"][aria-selected="true"]').length===1,'tab selection'); - for(const selector of ['.bar-row','.frontier-row','.evidence-ledger-head','.evidence-ledger-row','.evidence-inspector-identity','.proof-signal-card','.workspace-context','.global-bar']){ + for(const selector of ['.bar-row','.frontier-row','.evidence-ledger-head','.evidence-ledger-row','.evidence-inspector-identity','.proof-signal-card','.workspace-context','.global-bar','.evidence-inspector-card','.evidence-inspector-section','.evidence-inspector-card .inspector-chip-list','.evidence-inspector-section pre']){ document.querySelectorAll(selector).forEach((el,i)=>{ if(!visible(el))return; check(el.scrollWidth<=el.clientWidth+1,selector+' content overflow '+i,el,el.parentElement); @@ -48,7 +48,11 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { } const header=document.querySelector('.global-bar'); const headerChildren=header&&header.scrollWidth>header.clientWidth+1?[...header.querySelectorAll('*')].filter(el=>visible(el)&&(r(el).right>r(header).right+1||r(el).left({width,matches:matchMedia(`(max-width:${width}px)`).matches}))}; })() """# override init(){ @@ -74,7 +78,8 @@ final class BrowserAudit: NSObject, WKNavigationDelegate { func finish(_ reason:String? = nil){ guard !finished else{return};finished=true let failures=reports.reduce(0){$0+(($1["errors"] as? [String])?.count ?? 1)} + (reason == nil && reports.count == scenarios.count ? 0:1) - let report:[String:Any]=["suite":"full-browser-adversarial","failures":failures,"cases":reports.count,"expected_cases":scenarios.count,"results":reports,"runner_error":reason ?? ""] + let failedCases=reports.filter{!(($0["errors"] as? [String])?.isEmpty ?? false)}.count + let report:[String:Any]=["suite":"full-browser-adversarial","failures":failures,"failed_cases":failedCases,"cases":reports.count,"expected_cases":scenarios.count,"results":reports,"runner_error":reason ?? ""] do { let data=try JSONSerialization.data(withJSONObject:report,options:[.prettyPrinted,.sortedKeys]) try data.write(to:URL(fileURLWithPath:"target/wcode-browser-audit.json"),options:.atomic)