diff --git a/.github/workflows/commercial-research-beta.yml b/.github/workflows/commercial-research-beta.yml index 26207696..4c8ce74d 100644 --- a/.github/workflows/commercial-research-beta.yml +++ b/.github/workflows/commercial-research-beta.yml @@ -10,7 +10,7 @@ permissions: jobs: local-engineering-gate: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 60 env: PYTHONDONTWRITEBYTECODE: "1" PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} diff --git a/.superpowers/sdd/2026-08-16-evidence-one-pager/design-qa.md b/.superpowers/sdd/2026-08-16-evidence-one-pager/design-qa.md new file mode 100644 index 00000000..b497ba20 --- /dev/null +++ b/.superpowers/sdd/2026-08-16-evidence-one-pager/design-qa.md @@ -0,0 +1,124 @@ +# Evidence One-Pager design QA + +## Result + +`final result: passed` + +All saved images were inspected. There are zero open P0, P1, or P2 findings. +One P3 observation is recorded as intentional polish rather than a blocking +defect. + +## Scope and method + +- Implementation HEAD: `129129b493265b227ebcbb6f8670a74931df0ec0`. +- State inspected: the deterministic standalone complete-state fixture created + with the existing `_synthetic_brief("complete")` test seam. +- Fixture HTML: + `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/complete.html`, + SHA-256 + `861a8e7048eb00db3fee216a42b1cd989d7236ea6f3351094524bb1c7d870b9d`. +- Accepted screenshots were captured with the repository's Playwright/Chrome + setup, Google Chrome, browser zoom `100%`, DPR `1`, visual viewport scale + `1`, and screenshot `scale="device"`. +- The desktop implementation and the approved reference were compared at the + same native `2310x1504` pixel dimensions. Neither panel was cropped, resized, + stretched, or content-aware edited. +- The current captures were accepted only after their PNG signatures, exact + IHDR dimensions, viewport metadata, overflow, and browser-error fields were + verified. + +## Artifact identities + +| Artifact | Exact path | SHA-256 | Verified geometry | +| --- | --- | --- | --- | +| Approved reference | `/var/folders/cw/xfqgmp_57rn7nn3fq68z_6280000gn/T/codex-clipboard-80b40520-4c8b-493e-89af-a87e159e329b.png` | `d467ce50f7803b3a269b5cfd748a87c1ce4a269345943ca6993d365056c72d59` | PNG IHDR `2310x1504` | +| Desktop implementation | `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/implementation-desktop-2310x1504.png` | `6395a7b396e0cf014a61d31c1f1ca87ddad03bdfca1dc66d9dd14c5f10091990` | PNG IHDR and CSS viewport `2310x1504` | +| Phone implementation | `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/implementation-phone-390x844.png` | `47c9232768b6030f42c6d5dbfe2c1757f2dcd9097efeaee0ccd9fc1cc297aa47` | PNG IHDR and CSS viewport `390x844` | +| Labeled comparison | `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/reference-vs-implementation-2310x1504.png` | `e2514378e1201fc74b5649d1943f6bb6b343370cc7f974c20527ccbd046b34c1` | PNG IHDR `4700x1624`; equal `2310x1504` panels, `80px` gutter, `120px` label band | + +Supporting evidence: + +- Capture metadata: + `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/capture-metadata.json`, + SHA-256 + `9c177008c08fc4f4280a64d72463c51eea3011a84b7407b17219f4d7e96cf6c8`. +- Composite metadata: + `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/composite-metadata.json`, + SHA-256 + `9f58c1f02499bc588dbf0494719089028c6720f7a6a1a45cdad57c05e961f171`. +- Pixel verification: + `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/pixel-verification.log`, + SHA-256 + `481d3326573303ebd884cb72a4539512b0f8e5c59cbc248daa67530aabf7d1bd`. + It records both composite panels as raw-pixel-equal to their source images + and confirms that labels sit outside the image content. +- Hash ledger: + `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/visual-hashes.tsv`, + SHA-256 + `72990560d766305dff6e23e83de3b40f5fb10972deb39da1ca341efbeff18f7e`. + +## Findings by severity + +| Severity | Count | Finding and disposition | +| --- | ---: | --- | +| P0 | 0 | No blocker, unusable flow, or evidence-boundary breach observed. | +| P1 | 0 | No major hierarchy, readability, clipping, overflow, or interaction defect observed. | +| P2 | 0 | No material responsive, spacing, state, or visual-consistency defect observed. | +| P3 | 1 | At `2310px` wide, the implementation retains the existing centered, narrower Research Brief shell and therefore uses more surrounding whitespace than the dense reference canvas. This is intentional: the approved design preserves the existing report, avoids a fixed dense dashboard canvas, and permits normal scrolling. No change is required for this slice. | + +## Visual assessment + +- The summary has a distinct dark surface, amber emphasis, legible hierarchy, + explicit state text, semantic cards, and a clear handoff to the complete + Research Brief below it. +- State meaning does not depend on color alone. Visible state labels and card + text remain present in the complete-state capture. +- The complete report remains in the same offline document and is visibly + reachable by normal vertical scrolling. +- The phone capture reflows to one column. Identity copy wraps without + collision, and measured document and one-pager horizontal overflow are both + `0px`. +- Desktop and phone captures recorded no console errors and no page errors. + +## Intentional differences from the reference + +The implementation follows the reference's visual hierarchy without copying +claims that the frozen evidence does not support: + +- no `Certified` badge or certification claim; +- no target price, upside, current-price, or spot-price framing; +- no probability, percentile, or confidence-distribution claim; +- no capital-allocation instruction; and +- no buy, sell, own, invest, or other action language. + +Instead, it shows already-frozen evidence, scenario assumptions, independent +state disclosures, blockers, process status, and the next research task. The +scenario naming uses `Bull`, not `Blue Sky`, and the artifact remains English +only for this bounded slice. + +## Phone, zoom, and contrast evidence + +- Direct capture evidence: desktop `2310x1504@1` and phone `390x844@1`, both + at browser zoom `100%`, visual viewport scale `1`, with zero document or + one-pager overflow. +- The bridged standalone aa6 browser packet, result SHA-256 + `2eba7b2a354ba0bcc2cdedb5119f07e21879023722472c79fa64f0b45fcd80b2`, + covers the complete 24-cell matrix at + `100%`, `200%`, and `400%` zoom with zero failed assertions. +- The fresh Research accessibility packet on implementation HEAD, SHA-256 + `52c62631274d7f28027f648a98144133cf271b9bf6594cba550d6498bcfd5946`, + covers existing desktop and phone + routes plus all three Workbench one-pager cells. The Workbench cells recorded + minimum text/link contrast `16.644346951453382`, minimum boundary contrast + `3.6594143300026367`, a `44px` download target, zero overflow, and no + console/page errors. +- The same browser evidence includes forced-colors and print assertions. These + are deterministic automated checks, not a claim of human or screen-reader + validation and not a complete WCAG conformance audit. + +## Final decision + +The inspected desktop and phone implementation is visually coherent, +responsive, and faithful to the approved evidence boundary. With every P0, +P1, and P2 closed, the visual QA gate passes. The single P3 note is an +intentional shell-width difference and remains non-blocking. diff --git a/.superpowers/sdd/2026-08-16-evidence-one-pager/final-report.md b/.superpowers/sdd/2026-08-16-evidence-one-pager/final-report.md new file mode 100644 index 00000000..2ee1686e --- /dev/null +++ b/.superpowers/sdd/2026-08-16-evidence-one-pager/final-report.md @@ -0,0 +1,547 @@ +# Evidence One-Pager final implementation report + +## Status + +`REVIEW READY` + +Task 6 Steps 1-7 are complete. Independent review returned Spec Compliance +PASS, Task Quality PASS, and READY with Critical `0`, Important `0`, and Minor +`1`. Step 8 preparation is authorized; the final slice has not been staged and +no final implementation commit has been made. + +## Repository identity and bounded scope + +- Worktree: + `/Users/yjian070/Documents/New project/.worktrees/evidence-one-pager`. +- Branch: `codex/evidence-one-pager`. +- Named repository base and current `origin/main`: + `9147a47c327774e31e5ad76a370561b572d3ccbd`. +- Design commit: + `07e4e3a3696b5b2b8e765ca1cd76295d1adefccb`. +- Plan and implementation base: + `ae6520793f769c738f29284c710fe50cb43c3b1d`. +- Task 6 starting HEAD: + `1b3101cc817446982a0dc46c5c98d859c333618f`. +- Current HEAD: + `129129b493265b227ebcbb6f8670a74931df0ec0` + (`Stabilize authoring accessibility evidence`). +- Task 6 documentation commit: + `64631bba43b7217bf0aa50db64e8d9b429c79f63` + (`Document the Evidence One-Pager boundary`). +- Current divergence: `origin/main...HEAD` is `0 22` (behind `0`, ahead + `22`). The named base remains an ancestor of HEAD. + +The feature remains additive and research-only. It prepends a portable summary +to the existing offline HTML Research Brief, uses already-frozen evidence and +existing scenario/change truth, preserves the complete report below the +summary, and adds no route, source, calculation engine, readiness promotion, +recommendation, target price, probability, or second download. Withheld states +remain independently visible. + +## Exact intentional final paths + +The complete branch diff currently contains these 17 committed repository +paths relative to `origin/main`: + +- `Makefile` +- `README.md` +- `ROADMAP.md` +- `docs/superpowers/plans/2026-08-16-evidence-one-pager.md` +- `docs/superpowers/specs/2026-08-16-evidence-one-pager-design.md` +- `src/company_workbench_html.py` +- `src/company_workbench_html_browser_gate.py` +- `src/dashboard.py` +- `src/research_accessibility_browser_gate.py` +- `tests/test_company_workbench_html.py` +- `tests/test_company_workbench_html_browser_gate.py` +- `tests/test_dashboard_helpers.py` +- `tests/test_dashboard_render_smoke.py` +- `tests/test_launchers.py` +- `tests/test_public_v1_release_docs.py` +- `tests/test_research_accessibility_browser_gate.py` +- `tests/test_research_mode_dashboard_contract.py` + +These two intended Step 7 report paths now exist but are ignored by the SDD +workspace rule and remain unstaged until the Step 8 reviewed-path procedure: + +- `.superpowers/sdd/2026-08-16-evidence-one-pager/design-qa.md` +- `.superpowers/sdd/2026-08-16-evidence-one-pager/final-report.md` + +No other final repository path is intended. The final manifest must cover all +19 paths only after the pending reviewer verdict is written into this report. + +## Commit ledger + +The branch contains these 22 commits after `origin/main`, in order: + +1. `07e4e3a3696b5b2b8e765ca1cd76295d1adefccb` — Design evidence one-pager +2. `ae6520793f769c738f29284c710fe50cb43c3b1d` — Plan Evidence One-Pager implementation +3. `81680cde5226b3a348b3c2a57020c98fc0cdd569` — Freeze Workbench change answer for portable summary +4. `d78b47438ad1b6a505ade453a9034ae033eef01e` — Render evidence one-pager from frozen research truth +5. `76e89d2c0ef5ee725a9e75a273874b8e46a8ed3c` — Fix evidence one-pager state semantics +6. `b61a56b5aa22e55bec6df472f43cb15aef6030ff` — Prepend one-pager without hiding full research brief +7. `6827e61dcbf2d8d4b208e70fdb500410617600d7` — Cover stale one-pager snapshot identity fallback +8. `89a9b5ec607649b1c3d1ee6a69f5c908cc72b137` — Wire one-pager to existing Workbench change truth +9. `bb4340ccc9ba9a1ba0a297d621c558c7cccb44b2` — Fix one-pager print card contrast +10. `a8818235c1e353b4bbc3ad2bee7e9a7a825b1215` — Verify one-pager truth across browser states +11. `a49d709fc01d24ec4877b455a2740286f7c55a6c` — Fix one-pager in-app text contrast +12. `3187295cd13e5b8b57c8242d974dc032625e5a4d` — Fix Workbench HTML download target height +13. `54ce27f2877c963098bbd598869390f06934f59c` — Match one-pager accessibility labels to rendered states +14. `4284cf41a475ec50e7c067ac55615a3924a493b9` — Reject visually hidden one-pager evidence +15. `3b029ca1e282014dec1ba24806541e2132411e89` — Verify scroll-reachable one-pager occlusion +16. `fb214bffa0cc3d21bfd7fefee4e22f9bc404d4a1` — Reject pointer-transparent one-pager covers +17. `973fd5384d0806b6b241ffade406488762593de8` — Harden one-pager scoped paint evidence +18. `23a774ea71c75de284073cc7d245bd9f383c86a9` — Verify outward one-pager paint footprints +19. `1b3101cc817446982a0dc46c5c98d859c333618f` — Recognize painted SVG descendant hits +20. `64631bba43b7217bf0aa50db64e8d9b429c79f63` — Document the Evidence One-Pager boundary +21. `aa6bcbcd405d296c2f4d4dcd5d9fba86858ace2a` — Fix one-pager reference and share truth +22. `129129b493265b227ebcbb6f8670a74931df0ec0` — Stabilize authoring accessibility evidence + +## TDD and verification ledger + +### Task 0 baseline + +- Protected manifest: + `/tmp/stock-evidence-one-pager-preflight.PAHcYX/protected-manifest.tsv`, + 136 rows including the header, SHA-256 + `2cc91d87f0ca148f23570276901f6e3e1148b5a8eddf9ad2d90858cf224d2830`. +- Reference SHA-256: + `d467ce50f7803b3a269b5cfd748a87c1ce4a269345943ca6993d365056c72d59`. +- Existing HTML baseline: `944 passed`, one known third-party `dateutil` + warning. Log: + `/tmp/stock-evidence-one-pager-preflight.PAHcYX/baseline-company-workbench-html.log`, + SHA-256 + `26e577d9f26ad7b84bc4392a7b669f7b3347f5c329e09974d0322999a14fcd9d`. + +### Task 1: frozen Workbench input contract + +- RED: `22` expected failures. +- GREEN: `22` focused passes, `966` complete HTML unit passes, and `101` + browser-gate passes. +- Commit: + `81680cde5226b3a348b3c2a57020c98fc0cdd569`. +- Independent review: Spec Compliance PASS, Task Quality PASS, Critical `0`, + Important `0`. + +### Task 2: evidence-state renderer + +- Renderer RED: `20` expected failures. +- Initial GREEN: `20` focused passes and `986` complete HTML passes. +- First review found two Important state-semantics defects. Fix RED: `6` + expected failures. Fix GREEN: `9` focused passes and `995` complete HTML + passes. +- Commits: + `d78b47438ad1b6a505ade453a9034ae033eef01e` and + `76e89d2c0ef5ee725a9e75a273874b8e46a8ed3c`. +- Re-review closed both findings with Critical `0` and Important `0`. +- Later Task 5 routing produced the independently reviewed print and screen + contrast commits `bb4340ccc9ba9a1ba0a297d621c558c7cccb44b2` and + `a49d709fc01d24ec4877b455a2740286f7c55a6c`. + +### Task 3: additive report composition + +- Composition RED: `12` expected failures. +- Initial GREEN: `12` focused passes and `1007` complete HTML passes. +- First review found one Important test-quality gap for stale but syntactically + valid 64-hex identity. Test-only fix GREEN: `13` focused passes and `1008` + complete HTML passes. +- Commits: + `b61a56b5aa22e55bec6df472f43cb15aef6030ff` and + `6827e61dcbf2d8d4b208e70fdb500410617600d7`. +- Re-review resolved the finding with Critical `0` and Important `0`. + +### Task 4: Workbench integration + +- RED: `1` expected missing-constructor-input failure with `16` existing + focused passes. +- GREEN: `17` focused passes; full affected dashboard/render/HTML suite: + `2260 passed`, one known third-party `dateutil` warning, `482.18s`. +- Commit: + `89a9b5ec607649b1c3d1ee6a69f5c908cc72b137`. +- Independent review: Spec Compliance PASS, Task Quality PASS, Critical `0`, + Important `0`, Minor `0`. +- Later Task 5 routing produced the independently reviewed `44px` Workbench + download-target commit `3187295cd13e5b8b57c8242d974dc032625e5a4d`. + +### Task 5: browser and in-app evidence + +- In-app collector/payload RED: `5 failed, 61 deselected, 1 warning in + 0.90s`; GREEN: `5 passed, 61 deselected, 1 warning in 0.69s`. +- Result-packet RED: `6 failed, 232 deselected in 0.27s`; GREEN: `6 passed, + 232 deselected in 0.37s`. +- Make launcher RED: `1 failed in 0.35s`; GREEN: `1 passed in 0.32s`. +- Focused summary real-Chrome collector GREEN: `22 passed, 210 deselected in + 46.55s`. +- Combined pure/fake-browser GREEN: `89 passed, 215 deselected, 1 warning in + 2.02s`; collection audit selected exactly `89/304` tests and no real-Chrome + matrix case. +- State-label follow-up actual in-app RED: `1 failed, 1 warning in 10.27s`; + focused actual in-app GREEN: `1 passed, 1 warning in 10.11s`; pure + collector/payload GREEN: `5 passed, 62 deselected, 1 warning in 0.62s`. +- Final visibility closure evidence: standalone SVG plus non-SVG control + `2 passed in 9.13s`; in-app SVG `1 passed, 1 warning in 16.72s`; + standalone affected family `11 passed, 306 deselected in 44.03s`; in-app + affected family `8 passed, 90 deselected, 1 warning in 124.49s`. +- Independent closure review reran three exact cases: `3 passed in 24.51s`; + PASS, Critical `0`, Important `0`, Minor `0`. +- The final authoritative standalone gate on Task 5 HEAD passed `317` tests in + `445.79s`. The final Research gate exited `0`. +- Final Task 5 HEAD: + `1b3101cc817446982a0dc46c5c98d859c333618f`. +- Full Task 5 detail, including historical superseded packets, is retained in + `.superpowers/sdd/2026-08-16-evidence-one-pager/task-5-report.md`. + +### Task 6: active documentation contract + +- Focused RED command selected the active-document contract before the docs + change: `1 failed, 3 passed, 90 deselected in 0.37s`. The expected failure + was the absent Evidence One-Pager wording. +- The required bounded sentence and exclusions were added to the existing + README and ROADMAP HTML Research Brief paragraphs. +- The first formatting attempt added two lines and failed the existing line + budgets. The copy was merged into the existing paragraph without changing + meaning; final sizes are README `179` lines and ROADMAP `320` lines. +- Focused GREEN: `4 passed, 90 deselected in 0.32s`. +- Initial fresh full documentation GREEN: `94 passed in 1.38s`; final Step 7 + recheck: `94 passed in 1.64s`. +- Task 6 changed-Python lint: + `python3 -m ruff check tests/test_public_v1_release_docs.py` returned + `All checks passed!` under Ruff `0.15.21`. +- `git diff --check` and `git diff --cached --check` were clean before the + documentation commit. +- Documentation commit: + `64631bba43b7217bf0aa50db64e8d9b429c79f63`. + +### Task 6: post-documentation truth and evidence fixes + +- Commit `aa6bcbcd405d296c2f4d4dcd5d9fba86858ace2a` made malformed HTTPS + references fail closed without raising and rendered supplied shares as a + unitless quantity in the one-pager while preserving currency display for + monetary rows and the unchanged complete report below it. +- The aa6 focused fix was independently READY with Critical `0`, Important + `0`, and Minor `1`. Its authoritative standalone browser run passed `317` + tests in `441.72s`; the resulting packet passed `24/24` cells with zero + failed assertions. +- The first and only pre-fix Research accessibility run on aa6 failed closed: + `/tmp/stock-evidence-one-pager-research-accessibility.aa6bcbcd.uT1Kez`, + SHA-256 + `964f5b6a57e0026490b388e7c248c76842bf3d4ae4e983104acaf249cdef4e19`. + It passed `11/12` routes and all `3/3` one-pager cells; the sole failure was + phone Company Workbench `authoring_field_error_association`. +- Narrow current-byte diagnosis showed the exact linked, visible, + bridge-owned error text `thesis_id is required`, one alert, and exact Thesis + Id focus in every sample after attachment. Diagnostic output: + `/tmp/stock-evidence-one-pager-authoring-phone-diagnostic.aa6bcbcd.json`, + SHA-256 + `d3b9395d9cf058a35cf69e7f1dd31a2d06da2686cc6f0a75535981552904d3eb`. + Product and bridge bytes were unchanged from the prior passing evidence, so + this was a gate timing/observability defect rather than a product defect. +- TDD replaced the cross-call observation with one atomic semantic wait for a + unique invalid field, exact `aria-describedby` target and text, bridge + ownership, visibility, one alert, and exact focus. The same check covers the + Effective At cleanup transition. Any wait or evaluation exception forces + `ready=false`, and both callers explicitly reject recorded errors. +- The deterministic event-driven regression failed on exact base aa6 because + the base never entered the semantic wait; RED log: + `/tmp/stock-evidence-one-pager-event-driven-base-red.log`, SHA-256 + `9dddac78ae98b56f653d0ea38ac21a87a401cbb06ae121b61b68be42cd535a1d`. + Current GREEN: timeout/evaluation fakes `2 passed`; event-driven real browser + `1 passed`; exact phone route `73/73`; bounded non-browser gate tests `65 + passed`; accessibility bridge/authoring UI `84 passed`. +- Two broader source-text tests remain independently verified stale at exact + base and current bytes; they were excluded rather than weakened or repaired + outside scope. Ruff and diff hygiene passed on the focused two-file fix. +- Commit `129129b493265b227ebcbb6f8670a74931df0ec0` contains only + `src/research_accessibility_browser_gate.py` and + `tests/test_research_accessibility_browser_gate.py`. Fresh independent review + returned READY with Critical `0`, Important `0`, and Minor `0` before the + final Research rerun was authorized. + +## Qualified broad Ruff result + +The exact Task 6 broad Ruff command is not green. Under Ruff `0.15.21`, it +exited `1` with `38` findings across baseline files not changed by Task 6, +including `32` in `src/dashboard.py`. The pre-existing findings are reported +under `F401`, `F841`, `F601`, `F541`, and `F811`; none was introduced by +the Task 6 changes. + +- Broad command transcript: + `/tmp/stock-evidence-one-pager-ruff-broad-baseline-64631bba4.log`, SHA-256 + `24642e1c8c689dc88214f54d022ee6b1c915788e88f84e1afacc8cee7c924d6f`. +- Exact-baseline reproduction streamed `src/dashboard.py` from + `1b3101cc817446982a0dc46c5c98d859c333618f` into Ruff with + `--stdin-filename src/dashboard.py`; it exited `1` with the same `32` + dashboard findings. Transcript: + `/tmp/stock-evidence-one-pager-ruff-dashboard-git-show-1b3101cc.log`, + SHA-256 + `6003bfd9af0dde4bb0e7bd56c6329553ba748b705b13ad36d5a516a3f313ca22`. +- The review-pending bounded deviation is to preserve the inherited baseline + bytes and leave the unrelated `38` findings untouched. Task 6 acceptance + therefore uses clean lint on all five Python paths changed after the Task 5 + implementation HEAD — `src/company_workbench_html.py`, + `tests/test_company_workbench_html.py`, + `src/research_accessibility_browser_gate.py`, + `tests/test_research_accessibility_browser_gate.py`, and + `tests/test_public_v1_release_docs.py` — the `94`-test docs suite, exact + browser/source bridging, and clean diff checks. This qualification is part + of the independent review request. + +## Task 6 serial changed-byte gates + +After the focused phone-gate fix was committed and independently reviewed, the +Research accessibility target ran exactly once. Only after it passed did the +Research render-smoke target run exactly once; the targets did not overlap. +The standalone gate was not rerun because its aa6 packet binds unchanged +renderer/gate/test bytes. The performance contract was not rerun because its +source manifest does not bind either corrected Research gate/test path and all +of its bound bytes still match current HEAD. + +### Research accessibility browser gate + +- Fresh artifact: + `/tmp/stock-evidence-one-pager-research-accessibility.129129b4.7fRsMl`, + SHA-256 + `52c62631274d7f28027f648a98144133cf271b9bf6594cba550d6498bcfd5946`. +- Result: commit + `129129b493265b227ebcbb6f8670a74931df0ec0`, verdict `passed`, empty + failures, all `12/12` route cells passed, all `3/3` Workbench one-pager + cells passed, and state-harness evidence passed. +- Fresh sorted source manifest: + `/tmp/stock-evidence-one-pager-research-accessibility.129129b4.7fRsMl.source-hashes.json`, + SHA-256 + `7809180a3eb483ddb8acc493d9f9ec2624dccaf3cfc6942e68921faa7f0e30a8`. +- Bound current hashes: renderer + `e569e28e98f2cd93f980117eaec0c7a8a4fba043c34e76c252abca61f5d24167`; + shared HTML gate + `f50272f2e64f2213b9ed8aed034d4b0e43e5e7070347a002ec82841cfdbe55c5`; + dashboard + `8111e9a93769dc9d3e6616d5dabc9d0b7081d80b3bd726588c7eee5a5414c4f1`; + Research gate + `88ab5a0575052550213df2e6e46a7efe6b29119ee704b6b63cf15cef1413ff80`; + Research test + `ef2f9df68e835a6ae0045275338a999c73a166b9ede0ce961bb0360b53d982e4`. + +### Research dashboard render smoke + +- Transcript: + `/tmp/stock-evidence-one-pager-research-render-smoke.129129b4.plrPDe`, + SHA-256 + `131db486810521fc95018399601d588c0f3c8fa8052219161ea7a4475d96c9cb`. +- Result: all six routes passed — Research Desk, Discover, Company Workbench, + Monitor, Research Data Health, and Research Proof History. +- The only stderr was Streamlit bare-mode `ScriptRunContext` warning output. +- Sorted source manifest: + `/tmp/stock-evidence-one-pager-research-render-smoke.129129b4.plrPDe.source-hashes.tsv`, + SHA-256 + `96d79d6b3d7b7b7fe9a15205538768c097927229bfb9cc786b4485ee59c7b9b5`. +- Bound hashes: `Makefile` + `943a33efe80f03535c0e24bc31937c34a1fdbb145ca9c708d63640bd4d32a2bc`; + `src/company_workbench_html.py` + `e569e28e98f2cd93f980117eaec0c7a8a4fba043c34e76c252abca61f5d24167`; + `src/dashboard.py` + `8111e9a93769dc9d3e6616d5dabc9d0b7081d80b3bd726588c7eee5a5414c4f1`; + `src/dashboard_render_smoke.py` + `d3729eb40f7c3d203df0343823df75c2202603970d0a4f346e2f74792e191559`; + `src/paths.py` + `7cae06e2d9f057e9fc1ead3d44b2bddf9c3af02a317226f157f8dfada6dc8b68`; + `tests/test_dashboard_render_smoke.py` + `d8c611ec8ea8a8f836f399d74322b6daf76c4a22dc4bb52e58f49f3cbea9dff1`; + `tests/test_launchers.py` + `0d933833353e05b9911401873371f50d6433e52e3d9bdbfbd945983241600338`. + +### Commercial beta performance contract + +- Retained transcript (not rerun after independent source-scope review): + `/tmp/stock-evidence-one-pager-performance-contract.log`, SHA-256 + `adc9eeb99c5dd6e571776991e0cdc6112396772e2dfe48d9e2de1194aebb24ac`. +- Result: the target printed the four critical routes and the declared + thresholds: shell `1.0s`, first useful `3.0s`, warm full settle `5.0s`, and + cold full settle `10.0s`. This is a contract-render result, not a measured + runtime-performance claim. +- Sorted source manifest: + `/tmp/stock-evidence-one-pager-performance-contract.log.source-hashes.tsv`, + SHA-256 + `1c69a04b413fab8a7588488caa158248e356a2e2b9296e91a9ee1bedc662abe5`. +- Bound hashes: `Makefile` + `943a33efe80f03535c0e24bc31937c34a1fdbb145ca9c708d63640bd4d32a2bc`; + `src/paths.py` + `7cae06e2d9f057e9fc1ead3d44b2bddf9c3af02a317226f157f8dfada6dc8b68`; + `src/public_performance_gate.py` + `be4752450ca443ffe580643588f73809e02907736a32a2496587b3fadeb7bf64`; + `tests/test_launchers.py` + `0d933833353e05b9911401873371f50d6433e52e3d9bdbfbd945983241600338`; + `tests/test_public_performance_gate.py` + `ef642260914188a506add8cf7d4a42c19f389449a0a81d95fb1a7df42f2c62fc`. + +## Exact current evidence bridge + +The exact ledger revalidates four retained or fresh evidence lanes against the +current file bytes: the aa6 standalone packet, fresh Research accessibility +and render-smoke evidence, and the unchanged performance contract. + +- Bridge ledger: + `/tmp/stock-evidence-one-pager-bridge-ledger.129129b4.gPYnNN`, SHA-256 + `ac52a58a19b13b57d73fc21bd55df75bf5cdc63e4a7db305515a50f260e4b88a`. +- Standalone results from exact execution HEAD + `aa6bcbcd405d296c2f4d4dcd5d9fba86858ace2a`: + `/tmp/stock-company-workbench-html-browser.KPjoTa/results.json`, SHA-256 + `2eba7b2a354ba0bcc2cdedb5119f07e21879023722472c79fa64f0b45fcd80b2`. +- Standalone source manifest: + `/tmp/stock-company-workbench-html-browser.KPjoTa/source-hashes.json`, + SHA-256 + `ad59c51a3c4730c9875e5d2e09a99972128ddb067bd516a8cf98c48bfecd113c`. +- Standalone validation: schema version `1`, verdict `passed`, exactly two + packet files, `24/24` unique state/viewport/zoom cells passed, zero failed + assertions, and four input documents. +- Standalone bound current hashes: renderer + `e569e28e98f2cd93f980117eaec0c7a8a4fba043c34e76c252abca61f5d24167`; + gate + `f50272f2e64f2213b9ed8aed034d4b0e43e5e7070347a002ec82841cfdbe55c5`; + test + `8723bb287779a6fb8b4a5af082d0e9eb57050b15034cf22ee8f690e6557dcfe2`. +- Research accessibility artifact: + `/tmp/stock-evidence-one-pager-research-accessibility.129129b4.7fRsMl`, + SHA-256 + `52c62631274d7f28027f648a98144133cf271b9bf6594cba550d6498bcfd5946`. +- Research source manifest: + `/tmp/stock-evidence-one-pager-research-accessibility.129129b4.7fRsMl.source-hashes.json`, + SHA-256 + `7809180a3eb483ddb8acc493d9f9ec2624dccaf3cfc6942e68921faa7f0e30a8`. +- Research validation: verdict `passed`, exact current commit + `129129b493265b227ebcbb6f8670a74931df0ec0`, empty failures, `12/12` + unique route cells passed, and `3/3` unique Workbench one-pager cells passed, + all with zero failed assertions. +- Research bound current hashes: renderer + `e569e28e98f2cd93f980117eaec0c7a8a4fba043c34e76c252abca61f5d24167`; + shared gate + `f50272f2e64f2213b9ed8aed034d4b0e43e5e7070347a002ec82841cfdbe55c5`; + dashboard + `8111e9a93769dc9d3e6616d5dabc9d0b7081d80b3bd726588c7eee5a5414c4f1`; + Research gate + `88ab5a0575052550213df2e6e46a7efe6b29119ee704b6b63cf15cef1413ff80`; + Research test + `ef2f9df68e835a6ae0045275338a999c73a166b9ede0ce961bb0360b53d982e4`. +- The same ledger verifies the fresh render transcript and seven-source + manifest plus the retained performance transcript and five-source manifest. + Every bound source hash matches current HEAD; performance remains a rendered + contract, not measured runtime evidence. + +## Final visual QA + +- Approved reference exists at the exact required path and SHA-256 + `d467ce50f7803b3a269b5cfd748a87c1ce4a269345943ca6993d365056c72d59`. +- Complete-state fixture: + `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/complete.html`, + 36,173 bytes, SHA-256 + `861a8e7048eb00db3fee216a42b1cd989d7236ea6f3351094524bb1c7d870b9d`. +- Desktop implementation: + `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/implementation-desktop-2310x1504.png`, + SHA-256 + `6395a7b396e0cf014a61d31c1f1ca87ddad03bdfca1dc66d9dd14c5f10091990`; + CSS viewport and PNG IHDR `2310x1504`, zoom `100%`, DPR `1`, screenshot + scale `device`. +- Phone implementation: + `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/implementation-phone-390x844.png`, + SHA-256 + `47c9232768b6030f42c6d5dbfe2c1757f2dcd9097efeaee0ccd9fc1cc297aa47`; + CSS viewport and PNG IHDR `390x844`, zoom `100%`, DPR `1`, screenshot scale + `device`. +- Labeled equal-panel composite: + `/tmp/stock-evidence-one-pager-design-qa-129129b493265b227ebcbb6f8670a74931df0ec0/reference-vs-implementation-2310x1504.png`, + SHA-256 + `e2514378e1201fc74b5649d1943f6bb6b343370cc7f974c20527ccbd046b34c1`; + `4700x1624`, equal raw-pixel `2310x1504` panels, `80px` neutral gutter, + `120px` labels outside content, no crop/resize/content-aware edit. +- Capture metadata SHA-256 + `9c177008c08fc4f4280a64d72463c51eea3011a84b7407b17219f4d7e96cf6c8`; + composite metadata SHA-256 + `9f58c1f02499bc588dbf0494719089028c6720f7a6a1a45cdad57c05e961f171`; + raw-pixel verification SHA-256 + `481d3326573303ebd884cb72a4539512b0f8e5c59cbc248daa67530aabf7d1bd`; + seven-entry visual ledger SHA-256 + `72990560d766305dff6e23e83de3b40f5fb10972deb39da1ca341efbeff18f7e`. +- All reference, desktop, phone, and composite images were inspected. +- Findings: P0 `0`, P1 `0`, P2 `0`, P3 `1`. The P3 is the intentional + narrower existing report shell and greater whitespace at the very wide + desktop viewport; it does not block this approved additive design. +- Desktop and phone both have zero document/one-pager horizontal overflow and + no console/page errors. The phone reflows to one column. +- Intentional omissions: no `Certified` badge, target/upside/current-price + framing, probability claim, capital-allocation instruction, or buy/sell/own/ + invest/action language. +- Detailed report: + `.superpowers/sdd/2026-08-16-evidence-one-pager/design-qa.md` with + `final result: passed`. + +## Protected paths and repository status + +- Regenerated manifest: + `/tmp/stock-evidence-one-pager-protected-129129b4.tsv`, 136 rows including + header, SHA-256 + `2cc91d87f0ca148f23570276901f6e3e1148b5a8eddf9ad2d90858cf224d2830`. +- Baseline manifest: + `/tmp/stock-evidence-one-pager-preflight.PAHcYX/protected-manifest.tsv`, 136 + rows including header, identical SHA-256 + `2cc91d87f0ca148f23570276901f6e3e1148b5a8eddf9ad2d90858cf224d2830`. +- Byte-for-byte comparison: equal; `135` protected entries; zero added, + removed, type-changed, mode-changed, content-changed, or link-target-changed + paths in `data/`, `outputs/`, and `docs/assets/`. +- At the Step 7 review handoff, the Git index is empty and + `git status --porcelain` is empty. The two intentionally created SDD report + files are ignored and unstaged; there are zero unexpected staged or + untracked paths. + +## External gates and limitations + +- The evidence is deterministic local, synthetic/demo, and point-in-time. It + does not establish current market data, source-right portability, or rights + beyond the already-rendered evidence states. +- `source_backed` remains partial until frozen dates, rights, field scope, and + cutoff provenance are portable. Missing or stale evidence remains withheld; + no data was fabricated. +- Automated browser assertions do not establish human usability, screen-reader + operation, a full WCAG audit, or accessibility conformance. +- Local scenario presentation does not establish externally calibrated + probabilities, independent research-session validation, market fit, or user + acceptance. +- The performance target only rendered the declared contract; it did not + measure route timings in this Task 6 run. +- No credentials, broker integration, order routing, auto-trading, or + investment action was used or added. +- No push, pull request, ready-for-review transition, merge, deploy, + publication, or external message was performed. + +## Independent review request and verdict + +Please independently review: + +- the complete `origin/main...129129b493265b227ebcbb6f8670a74931df0ec0` + branch diff; +- the preservation contract and exact protected-manifest equality; +- the no-recommendation and no-fabrication boundary; +- the exact standalone, Research, render, performance result/source hashes and + current-byte bridge; +- the Task 6 serial gate transcripts and Ruff-baseline qualification; +- the reference, desktop, phone, composite, and `design-qa.md`; and +- the empty index/status plus exact intended path set. + +Reviewer verdict: `READY` + +Spec Compliance: `PASS` + +Task Quality: `PASS` + +Critical findings: `0` + +Important findings: `0` + +Minor findings: `1` + +Non-blocking Minor: malformed-authority rejection is covered, and an +independent probe confirmed that valid percent-encoded HTTPS paths remain +accepted, but no committed positive regression test covers that preserved +case. Production behavior is correct. + +The Step 8 authorization condition of zero Critical and zero Important +findings is satisfied. Do not stage the final slice or make the final +implementation commit until the bounded exact-byte follow-up review passes. diff --git a/Makefile b/Makefile index 92737790..ac6cc411 100644 --- a/Makefile +++ b/Makefile @@ -528,7 +528,14 @@ endif --output-dir "$(OUTPUT_DIR)" company-workbench-html-browser-check: - @PYTHONDONTWRITEBYTECODE=1 python3 -m pytest tests/test_company_workbench_html_browser_gate.py -q + @packet_dir="$$(mktemp -d /tmp/stock-company-workbench-html-browser.XXXXXX)" && \ + HTML_BRIEF_BROWSER_OUTPUT_DIR="$$packet_dir" PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q -p no:cacheprovider tests/test_company_workbench_html_browser_gate.py && \ + test -s "$$packet_dir/results.json" && \ + test -s "$$packet_dir/source-hashes.json" && \ + for packet in "$$packet_dir/results.json" "$$packet_dir/source-hashes.json"; do \ + packet_hash="$$(shasum -a 256 "$$packet" | awk '{print $$1}')"; \ + printf '%s %s\n' "$$packet" "$$packet_hash"; \ + done public-ux-review-checklist: @python3 -m src.public_ux_review_checklist diff --git a/README.md b/README.md index 5fab4755..5a31b724 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ flowchart LR Workbench -. advanced evidence .-> Health["Data Health and Proof History"] ``` ### Download HTML Research Brief -Company Workbench can prepare **Download HTML Research Brief** from existing saved evidence and Python scenario math already shown in the selected research session. The download is an immutable, offline, research-only review snapshot: missing, partial, stale, mismatched, or unsupported fields remain independently labelled or withheld instead of being inferred. It does not refresh data or acquire a new source, change readiness, create a recommendation, or add a second valuation engine. No repository HTML or PDF artifact is written; the browser receives deterministic UTF-8 download bytes only. Modal modifiers and active exposure fail closed. Broad-review repairs must be evaluated only through direct current-head local and exact-head CI evidence; their presence alone establishes neither gate. Local engineering evidence does not establish source rights, current-market data, readiness activation, a new or professional line-item model, hosted operation, human or screen-reader conformance, independent validation, market fit, screening alpha, or probability calibration. +The existing HTML Research Brief begins with an Evidence One-Pager that projects only the already-frozen saved evidence, scenario assumptions, blockers, process status, and next research task; the complete evidence report follows in the same offline artifact. The summary adds no source, calculation engine, readiness promotion, recommendation, target price, probability, or second download. No new route is added; withheld evidence remains independently visible, and the summary carries no `Certified` badge, target-price or upside framing, or probability claim. Company Workbench can prepare **Download HTML Research Brief** from existing saved evidence and Python scenario math already shown in the selected research session. The download is an immutable, offline, research-only review snapshot: missing, partial, stale, mismatched, or unsupported fields remain independently labelled or withheld instead of being inferred. It does not refresh data or acquire a new source, change readiness, create a recommendation, or add a second valuation engine. No repository HTML or PDF artifact is written; the browser receives deterministic UTF-8 download bytes only. Modal modifiers and active exposure fail closed. Broad-review repairs must be evaluated only through direct current-head local and exact-head CI evidence; their presence alone establishes neither gate. Local engineering evidence does not establish source rights, current-market data, readiness activation, a new or professional line-item model, hosted operation, human or screen-reader conformance, independent validation, market fit, screening alpha, or probability calibration. ## Now / Next / Not Yet This is the fastest reviewer answer: the product is shareable as a controlled demo now, deeper coverage is source-gated, and hosting/provider automation stays optional until verified. | Stage | Answer | Guardrail | diff --git a/ROADMAP.md b/ROADMAP.md index 04d8e912..81f3b4d0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -61,7 +61,7 @@ Stage A-G labels are continuation maturity lanes only; they do not replace the n - One permitted independently reviewed real point-in-time universe package, one permitted point-in-time consensus source, and one genuinely reviewed peer relationship are not on record. - Independent beta sessions completed: zero. The local protocol is ready, but it is not user-validation or demand evidence. ### Company Workbench HTML Research Brief -Company Workbench HTML Research Brief — Historical pre-fix evidence: Task 4 local matrix completed at `c8c313b9c`. Modal modifiers and active exposure fail closed. Broad-review repairs must be evaluated only through direct current-head local and exact-head CI evidence; their presence alone establishes neither gate. Exact-head repair evidence: commit `b69badfc80424d3a97fae5f77706aa6ed1533167` passed the 5,828-test full suite, the required dashboard, render, HTML, accessibility, public, and hygiene gates, branch/PR synchronization, and exact-head GitHub Actions run `30726301045`. The brief downloads an immutable offline view of existing saved evidence and prepared Python scenario math, preserves independent field gates and research-only wording, writes no repository artifact, and does not activate readiness or create a new calculation engine. Pilot packaging remains blocked on readiness freshness and source proof. Source rights, current data, hosted operation, human and screen-reader accessibility, independent workflow sessions, screening validation, and probability calibration remain open gates. Local engineering evidence does not establish source rights, current-market data, readiness activation, a new or professional line-item model, hosted operation, human or screen-reader conformance, independent validation, market fit, screening alpha, or probability calibration. +The existing HTML Research Brief begins with an Evidence One-Pager that projects only the already-frozen saved evidence, scenario assumptions, blockers, process status, and next research task; the complete evidence report follows in the same offline artifact. The summary adds no source, calculation engine, readiness promotion, recommendation, target price, probability, or second download. No new route is added; withheld evidence remains independently visible, and the summary carries no `Certified` badge, target-price or upside framing, or probability claim. Company Workbench HTML Research Brief — Historical pre-fix evidence: Task 4 local matrix completed at `c8c313b9c`. Modal modifiers and active exposure fail closed. Broad-review repairs must be evaluated only through direct current-head local and exact-head CI evidence; their presence alone establishes neither gate. Exact-head repair evidence: commit `b69badfc80424d3a97fae5f77706aa6ed1533167` passed the 5,828-test full suite, the required dashboard, render, HTML, accessibility, public, and hygiene gates, branch/PR synchronization, and exact-head GitHub Actions run `30726301045`. The brief downloads an immutable offline view of existing saved evidence and prepared Python scenario math, preserves independent field gates and research-only wording, writes no repository artifact, and does not activate readiness or create a new calculation engine. Pilot packaging remains blocked on readiness freshness and source proof. Source rights, current data, hosted operation, human and screen-reader accessibility, independent workflow sessions, screening validation, and probability calibration remain open gates. Local engineering evidence does not establish source rights, current-market data, readiness activation, a new or professional line-item model, hosted operation, human or screen-reader conformance, independent validation, market fit, screening alpha, or probability calibration. ## Next: Ordered Maturity Work The prior engineering closure is incorporated in the default branch, and its automated engineering checks passed. Do not repeat that completed engineering slice without changed product bytes or separate owner authorization. Select the first incomplete safe roadmap priority. If the next gate needs an unavailable source, account, environment, reviewer, or elapsed event history, classify it once under **Externally blocked** and continue to the next executable priority. Passing local tests never completes an external gate. Evidence publication, snapshot, and retrieval timestamps must all be at or before the cutoff. diff --git a/docs/superpowers/plans/2026-08-16-evidence-one-pager.md b/docs/superpowers/plans/2026-08-16-evidence-one-pager.md new file mode 100644 index 00000000..3f21f168 --- /dev/null +++ b/docs/superpowers/plans/2026-08-16-evidence-one-pager.md @@ -0,0 +1,2089 @@ +# Evidence One-Pager Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add an auditable Evidence One-Pager to the front of the existing Company Workbench HTML Research Brief without changing routes, calculations, readiness, persistence, or the complete report. + +**Architecture:** Extend the existing pure `CompanyWorkbenchHtmlSnapshot` only with an explicitly scoped, already-computed What changed answer. Add one pure summary projector inside `src/company_workbench_html.py`, compose it before the existing full-report content in both fragment and document renderers, and pass the existing Workbench change object from `src/dashboard.py`. Extend the current offline browser gate rather than creating another report or verification engine. + +**Tech Stack:** Python 3.12, frozen dataclasses, deterministic HTML/CSS, Streamlit, pytest, Playwright with local Chrome, repository fingerprint guards. + +## Global Constraints + +- Work only in `/Users/yjian070/Documents/New project/.worktrees/evidence-one-pager` on `codex/evidence-one-pager`. +- Preserve `data/`, `outputs/`, and `docs/assets/` byte-for-byte and do not stage them. +- Never use `git add -A`; stage only the named files for each task. +- The existing Workbench, module gate, full HTML report, download label, MIME type, filename convention, CSP, session behavior, and every existing research function remain available. +- The one-pager selects and formats already-frozen values only; it performs no file I/O, provider call, refresh, readiness rebuild, calculation, ledger append, or repository write. +- Missing, unsafe, stale, unverified, mismatched, excluded, or rights-blocked evidence remains independently `partial`, `not_recorded`, or `withheld`. +- Do not add `Certified`, ownership language, recommendations, rankings, target prices, spot comparisons, upside/downside, probabilities, confidence-looking precision, sizing, allocation, entry/exit, or transaction language. +- Do not add Blue Sky, capital-allocation metrics, company-specific KPIs, generated claims, generated thesis prose, or generated falsifiers. +- `source_backed` What changed content remains `partial` in this slice because portable publication/retrieval dates, rights, field scope, and cutoff proof are not frozen. +- Reuse the current `per_share_state` display decision and show the existing `share_basis_state`; do not introduce a new readiness rule. +- “One-Pager” means a bounded summary section, not a guaranteed single printed sheet; never clip, truncate, or shrink evidence to force page count. +- Local automation is engineering evidence only, not source-rights, current-market, hosted, human-accessibility, market-fit, or investment-performance proof. + +**Pre-execution prerequisite:** this reviewed plan must itself be committed by +named path on `codex/evidence-one-pager` before Task 0 starts. Task 0 must not +waive or ignore an untracked plan file. + +--- + +### Task 0: Re-establish exact branch and protected-artifact truth + +**Files:** +- Create only temporary manifests under `/tmp`; modify no repository file. + +- [ ] **Step 1: Verify the isolated execution boundary** + +Run: + +```bash +pwd +git status --short --branch +git rev-parse HEAD +git merge-base --is-ancestor 9147a47c327774e31e5ad76a370561b572d3ccbd HEAD +git rev-list --left-right --count origin/main...HEAD +git diff --check +``` + +Expected: exact worktree +`/Users/yjian070/Documents/New project/.worktrees/evidence-one-pager`, branch +`codex/evidence-one-pager`, approved design and implementation-plan commits on +top of the named `origin/main` base, no staged/untracked files, and clean diff +hygiene. Stop if another byte is present; classify it before continuing. + +- [ ] **Step 2: Capture the before-state for every protected path** + +Write a sorted relative-path/type/SHA-256 manifest for every file, directory, +and symlink under `data/`, `outputs/`, and `docs/assets/` to a fresh named +directory under `/tmp`. Record the manifest path and SHA-256 in the execution +log. Do not follow symlinks and do not create any file under those protected +trees. + +- [ ] **Step 3: Record the reference image identity without copying it** + +```bash +shasum -a 256 /var/folders/cw/xfqgmp_57rn7nn3fq68z_6280000gn/T/codex-clipboard-80b40520-4c8b-493e-89af-a87e159e329b.png +``` + +Expected: +`d467ce50f7803b3a269b5cfd748a87c1ce4a269345943ca6993d365056c72d59`. +If missing or different, continue code work but mark visual closeout blocked +until the owner reattaches the exact image. + +--- + +### Task 1: Freeze the scoped What changed answer + +**Files:** +- Modify: `src/company_workbench_html.py:51-176,567-604` +- Modify: `tests/test_company_workbench_html.py:1-230` + +**Interfaces:** +- Consumes: `CompanyWorkbenchHtmlInputs.change_answer`, `.change_ticker`, and `.change_profile_key` supplied by Task 4. +- Produces: a four-item `CompanyWorkbenchHtmlSnapshot.answers` tuple ordered `Use now`, `Still withheld`, `What changed`, `Next research task`; the What changed `HtmlBriefAnswer` carries safe source references and blockers. + +- [ ] **Step 1: Add failing snapshot tests for scoped change states** + +Add a default change mapping to `_inputs(...)` and tests that prove exact scope, order, state, references, and failure behavior: + +```python +def _change(**changes: object) -> dict[str, object]: + row: dict[str, object] = { + "state": "review_now", + "answer": "1 unresolved source-backed change needs review.", + "next_task": "Review the changed filing evidence.", + "source_refs": ("https://sec.example/change",), + "source_backed_eligible": True, + "change_context_kind": "source_backed", + } + row.update(changes) + return row + + +def test_snapshot_freezes_four_scoped_answers_without_promoting_source_backed_change(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + + assert [answer.label for answer in snapshot.answers] == [ + "Use now", + "Still withheld", + "What changed", + "Next research task", + ] + changed = snapshot.answers[2] + assert changed.state == "partial" + assert changed.title == "1 unresolved source-backed change needs review." + assert changed.body == "Review the changed filing evidence." + assert [reference.href for reference in changed.source_refs] == [ + "https://sec.example/change" + ] + assert any("portable publication" in blocker.lower() for blocker in changed.blockers) + + +@pytest.mark.parametrize( + ("kind", "eligible", "expected"), + ( + ("none", False, "not_recorded"), + ("snapshot_only", False, "partial"), + ("source_backed", True, "partial"), + ("unknown", True, "withheld"), + ), +) +def test_snapshot_maps_change_context_without_inheriting_workflow_state(kind, eligible, expected): + inputs = _inputs(change_answer=_change(change_context_kind=kind, source_backed_eligible=eligible)) + assert build_company_workbench_html_snapshot(inputs).answers[2].state == expected + + +@pytest.mark.parametrize("kind", ("none", "unknown")) +def test_snapshot_clears_claim_and_references_for_none_or_unknown_context(kind): + changed = build_company_workbench_html_snapshot( + _inputs(change_answer=_change(change_context_kind=kind)) + ).answers[2] + assert changed.title == "No portable change answer." + assert changed.body == "No scoped saved change answer is available." + assert changed.source_refs == () + assert "unresolved source-backed" not in repr(changed).lower() + + +@pytest.mark.parametrize( + ("ticker", "profile"), + (("AMD", "demo"), ("NVDA", "other"), ("", "demo")), +) +def test_snapshot_rejects_unscoped_or_mismatched_change_answer(ticker, profile): + snapshot = build_company_workbench_html_snapshot( + _inputs(change_ticker=ticker, change_profile_key=profile) + ) + changed = snapshot.answers[2] + assert changed.state == "not_recorded" + assert changed.source_refs == () + assert "changed filing" not in repr(changed).lower() + + +def test_snapshot_sanitizes_change_copy_state_and_references(): + snapshot = build_company_workbench_html_snapshot( + _inputs( + change_answer=_change( + state="invented-state", + next_task="buy this stock now", + source_refs=( + "javascript:alert(1)", + "https://sec.example/change", + ), + ) + ) + ) + changed = snapshot.answers[2] + assert changed.state == "withheld" + assert changed.badges == () + assert " HtmlBriefAnswer: + return HtmlBriefAnswer( + "What changed", + "No portable change answer.", + "No scoped saved change answer is available.", + state, + (), + (), + (blocker,), + ) + + +def _portable_change_text(value: object) -> str: + if not isinstance(value, str): + return "" + raw = value.strip() + if not raw or "<" in raw or ">" in raw: + return "" + safe = safe_html_brief_text(raw) + return "" if not safe or safe == _WITHHELD_ACTION else safe + + +def _benign_incomplete_change_reference(raw: str) -> bool: + if len(raw) > 512: + return False + if re.fullmatch(r"sec:[A-Za-z0-9][A-Za-z0-9._-]{0,127}", raw): + return True + if re.fullmatch( + r"sec-accession:[A-Za-z0-9][A-Za-z0-9._-]{0,127}", raw + ): + return True + source_tokens = tuple(part.strip() for part in raw.split(";")) + if 1 <= len(source_tokens) <= 8 and all( + re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}", part) + for part in source_tokens + ): + return True + try: + parsed = urlsplit(raw) + path_parts = tuple(part for part in parsed.path.split("/") if part) + return bool( + parsed.scheme == "consensus" + and parsed.hostname + and re.fullmatch( + r"[A-Za-z0-9][A-Za-z0-9.-]{0,127}", parsed.hostname + ) + and not parsed.username + and not parsed.password + and parsed.port is None + and not parsed.query + and not parsed.fragment + and path_parts + and all( + part not in {".", ".."} + and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}", part) + for part in path_parts + ) + ) + except ValueError: + return False + + +def _change_reference_is_unsafe(value: object) -> bool: + if not isinstance(value, str): + return True + raw = value.strip() + if not raw or "<" in raw or ">" in raw or "\\" in raw: + return True + if any(unicodedata.category(char) == "Cc" for char in raw): + return True + if _SECRET_PATTERN.search(raw): + return True + if _benign_incomplete_change_reference(raw): + return False + return not bool( + safe_html_brief_reference( + {"label": "Change source", "href": raw} + ).href + ) + + +def _portable_change_answer( + inputs: CompanyWorkbenchHtmlInputs, + ticker: str, +) -> HtmlBriefAnswer: + scoped = _ticker_matches(inputs.change_ticker, ticker) and _profile_matches( + inputs.change_profile_key, + inputs.profile_context.profile_key, + ) + change = _mapping(inputs.change_answer) + if not scoped or not change: + return _neutral_change_answer( + state="not_recorded", + blocker="Portable change scope is absent or mismatched.", + ) + + raw_context_kind = str(change.get("change_context_kind") or "").strip().lower() + if raw_context_kind == "none": + return _neutral_change_answer( + state="not_recorded", + blocker="No source-backed or snapshot-only change is recorded.", + ) + if raw_context_kind not in {"snapshot_only", "source_backed"}: + return _neutral_change_answer( + state="withheld", + blocker="Portable change context is unsupported.", + ) + + raw_workflow_state = str(change.get("state") or "").strip().lower() + title = _portable_change_text(change.get("answer")) + body = _portable_change_text(change.get("next_task")) + refs: list[HtmlBriefSafeReference] = [] + refs_incomplete = False + raw_refs = change.get("source_refs") + refs_unsafe = raw_refs is not None and not isinstance(raw_refs, (list, tuple)) + for index, raw in enumerate(raw_refs if isinstance(raw_refs, (list, tuple)) else ()): + if _change_reference_is_unsafe(raw): + refs_unsafe = True + continue + safe = safe_html_brief_reference( + {"label": f"Change source {index + 1}", "href": raw} + ) + if safe.href and safe not in refs: + refs.append(safe) + elif not safe.href: + refs_incomplete = True + + content_safe = ( + raw_workflow_state in {"monitor", "review_now", "wait_for_evidence"} + and bool(title) + and bool(body) + and not refs_unsafe + ) + if not content_safe: + return _neutral_change_answer( + state="withheld", + blocker="Portable change content, workflow state, or reference is unsafe.", + ) + + if raw_context_kind == "snapshot_only": + state = "partial" + refs = [] + blockers = ("Change context is snapshot-only.",) + else: + state = "partial" + blockers = ( + "Portable publication and retrieval dates, rights, field scope, and cutoff proof are not frozen." + if ( + change.get("source_backed_eligible") is True + and refs + and not refs_incomplete + ) + else "Portable source-backed change eligibility or reference is incomplete." + ,) + + return HtmlBriefAnswer( + "What changed", + title, + body, + state, + tuple( + safe_html_brief_text(item) + for item in (raw_workflow_state, raw_context_kind) + if safe_html_brief_text(item) + ), + tuple(refs), + blockers, + ) +``` + +Construct `answers` in this exact order: + +```python +answers = ( + HtmlBriefAnswer("Use now", "Use now", selected_use_now, normalize_html_brief_state(selected_state), ()), + HtmlBriefAnswer("Still withheld", "Still withheld", selected_blocked, "withheld", ()), + _portable_change_answer(inputs, ticker), + HtmlBriefAnswer( + "Next research task", + _clean_text(inputs.authoritative_task.get("title"), "Next research task"), + _clean_text(inputs.authoritative_task.get("body"), "No portable task."), + normalize_html_brief_state(inputs.authoritative_task.get("state")), + tuple( + safe_html_brief_text(item) + for item in inputs.authoritative_task.get("badges", ()) + if safe_html_brief_text(item) + ) + if isinstance(inputs.authoritative_task.get("badges"), (list, tuple)) + else (), + ), +) +``` + +- [ ] **Step 4: Run the focused change tests and the complete HTML unit file** + +Run: + +```bash +PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q -p no:cacheprovider \ + tests/test_company_workbench_html.py +``` + +Expected: all tests pass; existing identity assertions are updated only where the intentional fourth answer changes the frozen payload. + +- [ ] **Step 5: Commit the scoped snapshot change** + +```bash +git add src/company_workbench_html.py tests/test_company_workbench_html.py +git diff --cached --check +git commit -m "Freeze Workbench change answer for portable summary" +``` + +--- + +### Task 2: Render the pure Evidence One-Pager + +**Files:** +- Modify: `src/company_workbench_html.py:612-880` +- Modify: `tests/test_company_workbench_html.py:560-1848` + +**Interfaces:** +- Consumes: `CompanyWorkbenchHtmlSnapshot` from Task 1 and existing `_html_brief_*` safety/formatting helpers. +- Produces: `_html_evidence_one_pager(snapshot, heading_level) -> str` and `_html_evidence_one_pager_or_unavailable(snapshot, heading_level) -> str`. + +- [ ] **Step 1: Add failing renderer tests for order, truth, and prohibited content** + +Add `import html` beside the existing standard-library imports in +`tests/test_company_workbench_html.py`. Add tests that parse the actual rendered +markup rather than matching implementation source: + +```python +def test_evidence_one_pager_renders_fixed_order_from_frozen_snapshot_only(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + rendered = html_brief._html_evidence_one_pager(snapshot, heading_level=2) + + markers = ( + "Saved evidence snapshot", + "Company Brief", + "Scenarios under assumptions", + "Research case", + "Operating and valuation evidence", + "What could break the research case", + "Questions still requiring evidence", + "Provenance and boundaries", + "Continue to the full evidence report below.", + ) + assert all(marker in rendered for marker in markers) + assert [rendered.index(marker) for marker in markers] == sorted( + rendered.index(marker) for marker in markers + ) + assert rendered.count('data-section="evidence-one-pager"') == 1 + assert rendered.count("Use now") >= 1 + assert rendered.count("What changed") >= 1 + + +@pytest.mark.parametrize( + "forbidden", + ( + "Certified", + "Why own it", + "Blue Sky", + "upside", + "target price", + "expected return", + "buy", + "sell", + "position size", + ), +) +def test_evidence_one_pager_never_adds_prohibited_claims(forbidden): + rendered = html.unescape( + html_brief._html_evidence_one_pager( + build_company_workbench_html_snapshot(_inputs()), + heading_level=2, + ) + ) + assert forbidden.lower() not in rendered.lower() + + +def test_evidence_one_pager_keeps_withheld_values_non_numeric(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + sentinels = tuple(101001.1 + index for index in range(12)) + base = next(scenario for scenario in snapshot.scenarios if scenario.name == "Base") + withheld_bridge = replace( + base.bridge, + state="withheld", + enterprise_state="withheld", + equity_state="withheld", + per_share_state="withheld", + explicit_total_state="withheld", + projected_fcfs=(sentinels[0],), + discounted_fcfs=(sentinels[1],), + discounted_explicit_total=sentinels[2], + terminal_value=sentinels[3], + discounted_terminal_value=sentinels[4], + enterprise_value=sentinels[5], + cash=sentinels[6], + debt=sentinels[7], + net_debt=sentinels[8], + equity_value=sentinels[9], + shares_outstanding=sentinels[10], + scenario_value_per_share=sentinels[11], + blockers=("The supplied Base bridge is withheld.",), + ) + withheld = replace( + snapshot, + scenarios=tuple( + replace(scenario, bridge=withheld_bridge) + if scenario.name == "Base" + else scenario + for scenario in snapshot.scenarios + ), + ) + rendered = html_brief._html_evidence_one_pager(withheld, heading_level=2) + assert "Scenario value withheld" in rendered + assert "The supplied Base bridge is withheld." in html.unescape(rendered) + assert not [ + value + for value in sentinels + if html_brief.format_html_brief_number(value) in html.unescape(rendered) + ] + + +def test_evidence_one_pager_preserves_share_basis_state_without_using_it_as_gate(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + scenarios = tuple( + replace( + scenario, + bridge=replace( + scenario.bridge, + per_share_state="available", + share_basis_state="unverified", + scenario_value_per_share=31415.92, + ), + ) + for scenario in snapshot.scenarios + ) + rendered = html.unescape( + html_brief._html_evidence_one_pager( + replace(snapshot, scenarios=scenarios), heading_level=2 + ) + ) + assert html_brief.format_html_brief_number(31415.92) in rendered + assert rendered.count("Share basis state: unverified") >= 3 + + +def test_share_basis_state_cannot_override_withheld_per_share_gate(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + base = next(item for item in snapshot.scenarios if item.name == "Base") + changed = replace( + snapshot, + scenarios=tuple( + replace( + scenario, + bridge=replace( + scenario.bridge, + per_share_state="withheld", + share_basis_state="available", + scenario_value_per_share=27182.81, + ), + ) + if scenario.name == "Base" + else scenario + for scenario in snapshot.scenarios + ), + ) + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + assert html_brief.format_html_brief_number(27182.81) not in rendered + assert "Share basis state: available" in rendered + + +@pytest.mark.parametrize( + ("state", "label"), + ( + ("available", "complete"), + ("partial", "partial"), + ("stale", "stale"), + ("withheld", "withheld"), + ), +) +def test_evidence_one_pager_keeps_independent_state_text_visible(state, label): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = replace( + snapshot, + freshness_state=state, + answers=tuple(replace(answer, state=state) for answer in snapshot.answers), + ) + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + assert f'data-state="{state}"' in rendered + assert f"State: {label}" in rendered + + +def test_evidence_one_pager_formats_only_supplied_scenario_values(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + scenarios = tuple( + replace( + scenario, + revenue_growth=0.123, + fcf_margin=0.234, + wacc=0.087, + terminal_growth=0.031, + forecast_years=7, + ) + for scenario in snapshot.scenarios + ) + rendered = html.unescape( + html_brief._html_evidence_one_pager( + replace(snapshot, scenarios=scenarios), + heading_level=2, + ) + ) + for supplied in ("12.3%", "23.4%", "8.7%", "3.1%", "7"): + assert supplied in rendered + + +def test_evidence_one_pager_has_summary_scoped_semantics_and_dom_order(): + rendered = html_brief._html_evidence_one_pager( + build_company_workbench_html_snapshot(_inputs()), + heading_level=2, + ) + parser = _OnePagerHtmlParser() + parser.feed(rendered) + assert parser.tags[0] == "section" + assert {"header", "section", "ol", "table", "caption", "aside"} <= set( + parser.tags + ) + assert not {"main", "footer", "script", "form", "iframe"} & set(parser.tags) + assert parser.headings[0] == ("h2", "NVDA Evidence One-Pager") + assert "Portable evidence provenance" in parser.captions + assert parser.answer_item_count == 4 + assert parser.scenario_item_count == 3 + assert len(parser.state_nodes) == len(parser.state_roles) + assert len(parser.state_roles) == len(set(parser.state_roles)) + assert all(role and state for role, state in parser.state_role_pairs) + assert len(parser.share_basis_pairs) == 4 + markers = ( + 'data-section="one-pager-header"', + 'data-section="one-pager-answers"', + 'data-section="one-pager-scenarios"', + 'data-section="one-pager-research-case"', + 'data-section="one-pager-operating-valuation"', + 'data-section="one-pager-break-case"', + 'data-section="one-pager-questions"', + 'data-section="one-pager-provenance"', + 'data-section="one-pager-handoff"', + ) + assert [rendered.index(marker) for marker in markers] == sorted( + rendered.index(marker) for marker in markers + ) +``` + +Add a small `_OnePagerHtmlParser(HTMLParser)` beside the existing +`_BriefHtmlParser`. It must begin recording only at the outer element with +`data-section="evidence-one-pager"`, track nested depth, and collect tags, +headings, caption text, answer/scenario list-item counts, every +`data-state`/`data-state-role` pair, and every share-basis role/state pair from +that subtree. This prevents the existing full +report landmarks/tables from creating a false green. + +Add an escaping test using a manually replaced answer containing `", + next_task="buy this stock now", + source_refs=( + "javascript:alert(1)", + "https://sec.example/change", + ), + ) + ) + ) + changed = snapshot.answers[2] + assert changed.state == "withheld" + assert changed.badges == () + assert "= 1 + assert rendered.count("What changed") >= 1 + + +@pytest.mark.parametrize( + "forbidden", + ( + "Certified", + "Why own it", + "Blue Sky", + "upside", + "target price", + "expected return", + "buy", + "sell", + "position size", + ), +) +def test_evidence_one_pager_never_adds_prohibited_claims(forbidden): + rendered = html.unescape( + html_brief._html_evidence_one_pager( + build_company_workbench_html_snapshot(_inputs()), + heading_level=2, + ) + ) + assert forbidden.lower() not in rendered.lower() + + +def _snapshot_with_one_pager_surface_text(snapshot, surface, text): + if surface.startswith("answer_"): + field = surface.removeprefix("answer_") + answer = snapshot.answers[0] + value = (text,) if field == "badges" else text + changed = replace(answer, state="available", **{field: value}) + return replace(snapshot, answers=(changed,) + snapshot.answers[1:]) + + if surface.startswith("section_"): + field = surface.removeprefix("section_") + section = next( + item for item in snapshot.research_sections if item.key == "key-drivers" + ) + changes = {"state": "available"} + if field == "fact_label": + changes["facts"] = ((text, "Recorded evidence"),) + elif field == "fact_value": + changes["facts"] = (("Metric", text),) + elif field == "blockers": + changes["blockers"] = (text,) + else: + changes[field] = text + changed = replace(section, **changes) + return replace( + snapshot, + research_sections=tuple( + changed if item.key == "key-drivers" else item + for item in snapshot.research_sections + ), + ) + + if surface == "scenario_method": + return replace( + snapshot, + scenarios=tuple( + replace(item, state="available", method_name=text) + if item.name == "Base" + else item + for item in snapshot.scenarios + ), + ) + + if surface.startswith("provenance_"): + field = surface.removeprefix("provenance_") + row = snapshot.evidence_rows[0] + if field == "reference_label": + changed = replace( + row, + state="available", + source_ref=replace(row.source_ref, label=text), + ) + elif field == "blockers": + changed = replace(row, state="available", blockers=(text,)) + else: + changed = replace(row, state="available", **{field: text}) + return replace(snapshot, evidence_rows=(changed,) + snapshot.evidence_rows[1:]) + + if surface == "snapshot_blockers": + return replace(snapshot, blockers=(text,)) + if surface.startswith("snapshot_"): + return replace(snapshot, **{surface.removeprefix("snapshot_"): text}) + raise AssertionError(f"unhandled one-pager surface: {surface}") + + +_ONE_PAGER_DYNAMIC_TEXT_SURFACES = ( + "answer_title", + "answer_body", + "answer_badges", + "section_title", + "section_answer", + "section_fact_label", + "section_fact_value", + "section_blockers", + "scenario_method", + "provenance_section", + "provenance_source_id", + "provenance_reference_label", + "provenance_as_of", + "provenance_retrieved_at", + "provenance_rights_state", + "provenance_field_scope_state", + "provenance_model_identity", + "provenance_input_identity", + "provenance_blockers", + "snapshot_source_as_of", + "snapshot_model_version", + "snapshot_boundary", + "snapshot_blockers", +) + + +@pytest.mark.parametrize("surface", _ONE_PAGER_DYNAMIC_TEXT_SURFACES) +@pytest.mark.parametrize( + "prohibited", + ( + "Certified one-pager.", + "Why own it: durable moat.", + "Blue Sky case.", + "Current price is USD 100.", + "Target price is USD 150.", + "Upside is 20%.", + "Expected return is 20%.", + "Probability is 80%.", + "Confidence is 90%.", + "Position size is 5%.", + ), +) +def test_evidence_one_pager_withholds_prohibited_claims_from_every_dynamic_text_surface( + surface, prohibited +): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = _snapshot_with_one_pager_surface_text(snapshot, surface, prohibited) + + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + + assert prohibited.lower() not in rendered.lower() + assert ( + "One-Pager content was withheld because it violates the summary evidence policy." + in rendered + ) + + +@pytest.mark.parametrize( + "surface, role", + ( + ("answer_body", "answers-use-now"), + ("answer_badges", "answers-use-now"), + ("section_fact_value", "research-case-research-key-drivers"), + ("scenario_method", "scenarios-base"), + ("provenance_model_identity", "provenance-row-1-catalyst-sec"), + ), +) +def test_evidence_one_pager_downgrades_policy_rejected_cards_to_withheld( + surface, role +): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = _snapshot_with_one_pager_surface_text( + snapshot, + surface, + "Current price is USD 100.", + ) + + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + node = _one_pager_state_nodes(rendered)[role] + + assert node["state"] == "withheld" + assert "State: withheld" in node["text"] + assert ( + "One-Pager content was withheld because it violates the summary evidence policy." + in node["text"] + ) + + +def test_evidence_one_pager_keeps_the_approved_research_only_boundary_visible(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + + rendered = html.unescape( + html_brief._html_evidence_one_pager(snapshot, heading_level=2) + ) + + assert snapshot.boundary in rendered + + +def test_evidence_one_pager_policy_does_not_rewrite_the_preserved_full_report(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = _snapshot_with_one_pager_surface_text( + snapshot, + "answer_body", + "Current price is USD 100.", + ) + + summary = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + full_report = html.unescape( + html_brief._html_brief_content(changed, heading_level=2) + ) + + assert "Current price is USD 100." not in summary + assert "Current price is USD 100." in full_report + + +@pytest.mark.parametrize( + "prohibited", + ( + "Сurrеnt рriсe is USD 100.", + "C\u200durrent price is USD 100.", + "Cúrrent price is USD 100.", + "Own it for the durable moat.", + "This is one to own.", + "The ownership case is compelling.", + "A stock worth owning.", + "A compelling company to own.", + "We own NVDA.", + "We own nvda.", + "We own NVDА.", + "We own NТDA.", + "Wе own msft.", + "We оwn msft.", + "We o\u200dwn msft.", + "Wе own NVDА.", + "We own msft.", + "I own amzn.", + "We own aapl.", + "We own msft today.", + "We own aapl as a hedge.", + "I own amzn for diversification.", + "I own msft shares.", + "Ownership is attractive.", + "Suggested sizing is 5%.", + "Portfolio sizing is 5%.", + "Size it at 5%.", + "Suggested weight is 5%.", + "Portfolio weight is 5%.", + "Position weight is 5%.", + "Suggested weight: 5%.", + "Suggested weight is 5 percent.", + "Suggested weight of 5 percent.", + "Suggested weight is five percent.", + "Suggested weight is 5%.", + "Portfolio weight is 0.5%.", + "Position weight at 2.5%.", + "Suggested weight: 0.5%.", + "Expected returns are 20%.", + "Return expectation is 20%.", + "Target valuation is USD 150.", + "Target value is USD 150.", + "Spot is USD 100.", + "20% above spot.", + "Below spot.", + "Likelihood is 80%.", + "The model assigns 80% odds.", + "We are 90% confident.", + "We are highly confident in the thesis.", + "High confidence in the valuation.", + "The model is highly confident in the thesis.", + "The research team remains confident in the case.", + "Analysts are confident in the scenario.", + "The result is confidently estimated.", + "This is a certifying one-pager.", + "Certification complete.", + "Invest in NVDA.", + "Confidence score: 90%.", + "No recommendation; target valuation is USD 150.", + "Not investment advice; own it.", + "No target price is provided, but target price is USD 100.", + "No current price is provided. Buy the stock.", + ), +) +def test_evidence_one_pager_withholds_confusable_and_morphological_claim_variants( + prohibited, +): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = _snapshot_with_one_pager_surface_text( + snapshot, + "answer_body", + prohibited, + ) + + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + node = _one_pager_state_nodes(rendered)["answers-use-now"] + + assert prohibited.casefold() not in rendered.casefold() + assert node["state"] == "withheld" + assert "State: withheld" in node["text"] + assert html_brief._ONE_PAGER_POLICY_BLOCKER in node["text"] + + +@pytest.mark.parametrize( + "allowed", + ( + "No current price, probability, or recommendation is provided.", + "No current price or probability is provided.", + "No current market price is provided.", + "No target value is provided.", + "The brief does not provide a target price.", + "The brief does not provide current price.", + "Probability is not available.", + "Management will invest USD 1 billion in research and development.", + "Management expressed confidence in demand.", + "業績は改善した。", + "SEC 来源记录。", + "We own the review process.", + "I own the source-validation task.", + "We own data validation.", + "We own risk review.", + "I own model governance.", + "We own café review.", + "We own data.", + "We own risk.", + "We own code.", + "We own tasks.", + "We own data from the α cohort.", + "We own risk for the β release.", + "We own code: С++ implementation notes.", + "We own data validation for β testing.", + "We own risk review for μ sensitivity.", + "I own model governance for Роснефть coverage.", + "Source ownership is attractive because accountability is clear.", + "Suggested weight is 5 kilograms.", + "The recommended weight is 5 kg.", + "The recommended weight is 5 kg according to the filing.", + "Owner review required before publication.", + "Source ownership is unverified.", + "Sample sizing method documented.", + "Target date is 2026-09-01.", + "The target company filed its 10-K.", + "Run a spot check on source coverage.", + "Return to the full evidence report.", + ), +) +def test_evidence_one_pager_preserves_negated_boundaries_and_operating_language( + allowed, +): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = _snapshot_with_one_pager_surface_text( + snapshot, + "section_answer", + allowed, + ) + + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + node = _one_pager_state_nodes(rendered)[ + "research-case-research-key-drivers" + ] + + assert allowed in rendered + assert node["state"] == "available" + assert html_brief._ONE_PAGER_POLICY_BLOCKER not in node["text"] + + +@pytest.mark.parametrize( + "href", + ( + "https://sec.example/current-price", + "https://sec.example/%63urrent%2Dprice", + "https://sec.example/%2563urrent%252Dprice", + "https://current-price.example/filing", + "https://sec.example/buy-stock", + "https://sec.example/order-shares", + "https://sec.example/invest-in-nvda", + "https://sec.example/%62uy%2Dstock", + "https://sec.example/%6Frder%2Dshares", + "https://sec.example/%69nvest%2Din%2Dnvda", + "https://sec.example/bυy-stock", + "https://sec.example/οrder-shares", + "https://sec.example/invest-in-nvdа", + ), +) +def test_evidence_one_pager_withholds_prohibited_reference_href_and_clears_link( + href, +): + snapshot = build_company_workbench_html_snapshot(_inputs()) + answer = replace( + snapshot.answers[0], + state="available", + source_refs=( + html_brief.HtmlBriefSafeReference( + "SEC filing", + href, + ), + ), + ) + changed = replace(snapshot, answers=(answer,) + snapshot.answers[1:]) + + projected = html_brief._html_one_pager_snapshot_value(changed) + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + node = _one_pager_state_nodes(rendered)["answers-use-now"] + + assert projected.answers[0].state == "withheld" + assert projected.answers[0].source_refs[0].href == "" + assert href not in rendered + assert node["state"] == "withheld" + assert html_brief._ONE_PAGER_POLICY_BLOCKER in node["text"] + + +@pytest.mark.parametrize( + "href", + ( + "https://sec.example/filing", + "https://company.example/management-invest-in-rd", + ), +) +def test_evidence_one_pager_keeps_normal_permitted_reference_href_and_state(href): + snapshot = build_company_workbench_html_snapshot(_inputs()) + answer = replace( + snapshot.answers[0], + state="available", + source_refs=( + html_brief.HtmlBriefSafeReference( + "SEC filing", + href, + ), + ), + ) + changed = replace(snapshot, answers=(answer,) + snapshot.answers[1:]) + + projected = html_brief._html_one_pager_snapshot_value(changed) + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + + assert projected.answers[0].state == "available" + assert projected.answers[0].source_refs[0].href == href + assert f'href="{href}"' in rendered + + +def test_evidence_one_pager_downgrades_rights_rollup_when_projected_row_is_withheld(): + report = _inputs().report_payload + report["provenance"]["source_records"] = [ + _source_record(model_identity="model-v1") + ] + snapshot = build_company_workbench_html_snapshot( + _inputs( + report, + catalyst_timeline=replace(_catalysts(), upcoming=()), + ) + ) + assert snapshot.rights_state == "available" + changed = replace( + snapshot, + evidence_rows=( + replace( + snapshot.evidence_rows[0], + state="available", + model_identity="Current price is USD 100.", + ), + ), + ) + frozen_identity = changed.identity + + projected = html_brief._html_one_pager_snapshot_value(changed) + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + nodes = _one_pager_state_nodes(rendered) + + assert projected.evidence_rows[0].state == "withheld" + assert projected.rights_state == "withheld" + assert nodes["header-rights-state"]["state"] == "withheld" + assert nodes["provenance-rights-state"]["state"] == "withheld" + assert html_brief._ONE_PAGER_POLICY_BLOCKER in rendered + assert changed.rights_state == "available" + assert changed.evidence_rows[0].state == "available" + assert changed.identity == frozen_identity + assert "Current price is USD 100." in html.unescape( + html_brief._html_brief_content(changed, heading_level=2) + ) + + +def test_evidence_one_pager_method_rejection_does_not_suppress_independent_value_gate(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = replace( + snapshot, + scenarios=tuple( + replace(scenario, method_name="Target valuation is USD 150.") + if scenario.name == "Base" + else scenario + for scenario in snapshot.scenarios + ), + ) + + projected = html_brief._html_one_pager_snapshot_value(changed) + projected_base = next( + scenario for scenario in projected.scenarios if scenario.name == "Base" + ) + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + nodes = _one_pager_state_nodes(rendered) + + assert projected_base.state == "withheld" + assert projected_base.bridge.state == "withheld" + assert projected_base.bridge.per_share_state == "available" + assert projected_base.bridge.scenario_value_per_share == next( + scenario for scenario in snapshot.scenarios if scenario.name == "Base" + ).bridge.scenario_value_per_share + assert nodes["scenarios-base"]["state"] == "withheld" + assert nodes["scenarios-base-value-per-share"]["state"] == "available" + assert "Scenario value: USD 212.58" in nodes[ + "scenarios-base-value-per-share" + ]["text"] + + +def test_evidence_one_pager_keeps_withheld_values_non_numeric(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + sentinels = tuple(101001.1 + index for index in range(12)) + base = next(scenario for scenario in snapshot.scenarios if scenario.name == "Base") + withheld_bridge = replace( + base.bridge, + state="withheld", + enterprise_state="withheld", + equity_state="withheld", + per_share_state="withheld", + explicit_total_state="withheld", + projected_fcfs=(sentinels[0],), + discounted_fcfs=(sentinels[1],), + discounted_explicit_total=sentinels[2], + terminal_value=sentinels[3], + discounted_terminal_value=sentinels[4], + enterprise_value=sentinels[5], + cash=sentinels[6], + debt=sentinels[7], + net_debt=sentinels[8], + equity_value=sentinels[9], + shares_outstanding=sentinels[10], + scenario_value_per_share=sentinels[11], + blockers=("The supplied Base bridge is withheld.",), + ) + withheld = replace( + snapshot, + scenarios=tuple( + replace(scenario, bridge=withheld_bridge) + if scenario.name == "Base" + else scenario + for scenario in snapshot.scenarios + ), + ) + rendered = html_brief._html_evidence_one_pager(withheld, heading_level=2) + assert "Scenario value withheld" in rendered + assert "The supplied Base bridge is withheld." in html.unescape(rendered) + assert not [ + value + for value in sentinels + if html_brief.format_html_brief_number(value) in html.unescape(rendered) + ] + + +@pytest.mark.parametrize( + "invalid", + (None, True, float("nan"), float("inf"), float("-inf")), + ids=("missing", "boolean", "nan", "positive-infinity", "negative-infinity"), +) +def test_evidence_one_pager_downgrades_invalid_available_numeric_states(invalid): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = replace( + snapshot, + scenarios=tuple( + replace( + scenario, + bridge=replace( + scenario.bridge, + enterprise_state="available", + per_share_state="available", + enterprise_value=invalid, + scenario_value_per_share=invalid, + ), + ) + if scenario.name == "Base" + else scenario + for scenario in snapshot.scenarios + ), + ) + + rendered = html_brief._html_evidence_one_pager(changed, heading_level=2) + nodes = _one_pager_state_nodes(rendered) + for role in ( + "scenarios-base-value-per-share", + "operating-valuation-base-bridge-enterprise-value", + ): + assert nodes[role]["state"] == "withheld" + assert "State: withheld" in nodes[role]["text"] + assert "State: complete" not in nodes[role]["text"] + assert "withheld" in nodes[role]["text"].lower() + + +@pytest.mark.parametrize( + ("supplied_state", "expected_state", "expected_label"), + ( + ("partial", "partial", "partial"), + ("not_recorded", "not_recorded", "not recorded"), + ("withheld", "withheld", "withheld"), + ), +) +def test_evidence_one_pager_never_promotes_or_leaks_nonavailable_finite_values( + supplied_state, + expected_state, + expected_label, +): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = replace( + snapshot, + scenarios=tuple( + replace( + scenario, + bridge=replace( + scenario.bridge, + enterprise_state=supplied_state, + per_share_state=supplied_state, + enterprise_value=876543.21, + scenario_value_per_share=987654.32, + ), + ) + if scenario.name == "Base" + else scenario + for scenario in snapshot.scenarios + ), + ) + + rendered = html_brief._html_evidence_one_pager(changed, heading_level=2) + nodes = _one_pager_state_nodes(rendered) + for role in ( + "scenarios-base-value-per-share", + "operating-valuation-base-bridge-enterprise-value", + ): + assert nodes[role]["state"] == expected_state + assert f"State: {expected_label}" in nodes[role]["text"] + assert "State: complete" not in nodes[role]["text"] + assert "876,543.21" not in html.unescape(rendered) + assert "987,654.32" not in html.unescape(rendered) + + +def test_evidence_one_pager_preserves_share_basis_state_without_using_it_as_gate(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + scenarios = tuple( + replace( + scenario, + bridge=replace( + scenario.bridge, + per_share_state="available", + share_basis_state="unverified", + scenario_value_per_share=31415.92, + ), + ) + for scenario in snapshot.scenarios + ) + rendered = html.unescape( + html_brief._html_evidence_one_pager( + replace(snapshot, scenarios=scenarios), heading_level=2 + ) + ) + assert html_brief.format_html_brief_number(31415.92) in rendered + assert rendered.count("Share basis state: unverified") >= 3 + + +def test_evidence_one_pager_formats_shares_as_unitless_without_changing_currency_rows(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = replace( + snapshot, + scenarios=tuple( + replace( + scenario, + bridge=replace( + scenario.bridge, + explicit_total_state="available", + equity_state="available", + per_share_state="available", + discounted_explicit_total=112233.0, + cash=223344.0, + debt=334455.0, + shares_outstanding=445566.0, + currency="USD", + ), + ) + if scenario.name == "Base" + else scenario + for scenario in snapshot.scenarios + ), + ) + + nodes = _one_pager_state_nodes( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + shares = nodes["operating-valuation-base-bridge-supplied-shares"]["text"] + assert "445,566.00" in shares + assert "USD" not in shares + assert "USD 112,233.00" in nodes[ + "operating-valuation-base-bridge-discounted-explicit-total" + ]["text"] + assert "USD 223,344.00" in nodes[ + "operating-valuation-base-bridge-cash" + ]["text"] + assert "USD 334,455.00" in nodes[ + "operating-valuation-base-bridge-debt" + ]["text"] + assert ( + 'Shares outstanding used by existing model' + "USD 445,566.00" + in html_brief.render_company_workbench_html_fragment(changed) + ) + + +def test_share_basis_state_cannot_override_withheld_per_share_gate(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = replace( + snapshot, + scenarios=tuple( + replace( + scenario, + bridge=replace( + scenario.bridge, + per_share_state="withheld", + share_basis_state="available", + scenario_value_per_share=27182.81, + ), + ) + if scenario.name == "Base" + else scenario + for scenario in snapshot.scenarios + ), + ) + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + assert html_brief.format_html_brief_number(27182.81) not in rendered + assert "Share basis state: available" in rendered + + +@pytest.mark.parametrize( + ("state", "label"), + ( + ("available", "complete"), + ("partial", "partial"), + ("stale", "stale"), + ("withheld", "withheld"), + ), +) +def test_evidence_one_pager_keeps_independent_state_text_visible(state, label): + snapshot = build_company_workbench_html_snapshot(_inputs()) + changed = replace( + snapshot, + freshness_state=state, + answers=tuple(replace(answer, state=state) for answer in snapshot.answers), + ) + rendered = html.unescape( + html_brief._html_evidence_one_pager(changed, heading_level=2) + ) + assert f'data-state="{state}"' in rendered + assert f"State: {label}" in rendered + + +def test_evidence_one_pager_formats_only_supplied_scenario_values(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + scenarios = tuple( + replace( + scenario, + revenue_growth=0.123, + fcf_margin=0.234, + wacc=0.087, + terminal_growth=0.031, + forecast_years=7, + ) + for scenario in snapshot.scenarios + ) + rendered = html.unescape( + html_brief._html_evidence_one_pager( + replace(snapshot, scenarios=scenarios), + heading_level=2, + ) + ) + for supplied in ("12.3%", "23.4%", "8.7%", "3.1%", "7"): + assert supplied in rendered + + +def test_evidence_one_pager_has_summary_scoped_semantics_and_dom_order(): + rendered = html_brief._html_evidence_one_pager( + build_company_workbench_html_snapshot(_inputs()), + heading_level=2, + ) + parser = _OnePagerHtmlParser() + parser.feed(rendered) + assert parser.tags[0] == "section" + assert {"header", "section", "ol", "table", "caption", "aside"} <= set( + parser.tags + ) + assert not {"main", "footer", "script", "form", "iframe"} & set(parser.tags) + assert parser.headings[0] == ("h2", "NVDA Evidence One-Pager") + assert "Portable evidence provenance" in parser.captions + assert parser.labelled_asides == ["evidence-one-pager-provenance-title"] + assert parser.answer_item_count == 4 + assert parser.scenario_item_count == 3 + assert len(parser.state_nodes) == len(parser.state_roles) + assert len(parser.state_roles) == len(set(parser.state_roles)) + assert all(role and state for role, state in parser.state_role_pairs) + assert len(parser.share_basis_pairs) == 4 + markers = ( + 'data-section="one-pager-header"', + 'data-section="one-pager-answers"', + 'data-section="one-pager-scenarios"', + 'data-section="one-pager-research-case"', + 'data-section="one-pager-operating-valuation"', + 'data-section="one-pager-break-case"', + 'data-section="one-pager-questions"', + 'data-section="one-pager-provenance"', + 'data-section="one-pager-handoff"', + ) + assert [rendered.index(marker) for marker in markers] == sorted( + rendered.index(marker) for marker in markers + ) + + +def test_evidence_one_pager_unavailable_fallback_has_labelled_summary_header(): + rendered = html_brief._html_evidence_one_pager_or_unavailable( + None, + heading_level=2, + ) + assert 'data-section="evidence-one-pager-unavailable"' in rendered + assert '

Evidence One-Pager unavailable

' in rendered + assert "Continue to the full evidence report below." in rendered + + +def test_evidence_one_pager_escapes_and_withholds_unsafe_answer_content(): + snapshot = build_company_workbench_html_snapshot(_inputs()) + unsafe = replace( + snapshot.answers[0], + title="", + body="/private/repository buy shares", + ) + rendered = html_brief._html_evidence_one_pager( + replace(snapshot, answers=(unsafe,) + snapshot.answers[1:]), + heading_level=2, + ) + parser = _OnePagerHtmlParser() + parser.feed(rendered) + + assert "