diff --git a/CHANGELOG.md b/CHANGELOG.md index 03ea2227..9d52da8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,12 @@ Development version: `0.2.4.dev0`. Public install remains the immutable v0.2.3 r ### Post-release engineering +- Consolidated the owner workflow around visible public-PR and constructed-demo entry points, + explicit criteria confirmation, unresolved-decision progress, and direct criterion navigation. + Optional alpha-feedback controls now live under research options, constructed demo records remain + explicitly segregated, and evidence types remain separate. Added a documentation index that + distinguishes current operating guidance from historical evidence snapshots, and refreshed the + official-source market comparison without claiming customer or market validation. - Limited analysis concurrency to redundant same-PR, same-head runs while leaving every revocation writer runnable. The publisher revalidates the live pull immediately before mutation and neutralizes its exact Check if identity or applicability changes after the write. Default-base diff --git a/README.md b/README.md index 4f136e06..d26ab69a 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,8 @@ they do not establish correctness, customer validation, or Stage 1 progress. See the [v0.2.3 status and next-stage audit](docs/releases/v0.2.3-status-and-next-stages.md) for the implemented feature ledger, current gaps, and owner-led Stage 2 roadmap. +The [documentation map](docs/README.md) separates current operating documents from historical +evidence snapshots. ```bash python3 -m venv .venv diff --git a/ROADMAP.md b/ROADMAP.md index adbf3271..adc004b0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -20,6 +20,7 @@ which provides the wheel, source archive, and checksum manifest. | Published install | v0.2.3 GitHub Release with wheel, source archive, and checksum manifest | | Active source line | Unreleased `0.2.4.dev0`; no v0.2.4 release, tag, or package publication exists | | Post-PR #193 resulting-main snapshot (2026-08-13) | PR #193 product-source baseline at `432371c4faec0b790f70fec32b4d3fc4d5132cfa` (PR head `8bb407079a0ff7098d2fc18af3d75b216725df2e`, base `9426e8714ffd2c3742bb074ae26fc788f1049c63`) | +| Post-PR #196 resulting-main snapshot (2026-08-17) | Exact PR #196 head `5a6a25a6dff23cdfa8dcb4023b83144078620610` landed in merge `8387156fd6f6e90eef7caf58881b0cc5bb62b111`; hosted resulting-main CI run [`32093041685`](https://github.com/YuzeJ21/Scope-Proof/actions/runs/32093041685) succeeded as the exact-head engineering record | | Verified product baseline | PR #184 release integration landed on `main` at `448c42758ea139bf9203cbf1bb04b02b02ae412c` | | Product verification | Full product-code verification is bound to `fb74d4bbb402f4de3e2fabb56ce28c948214f8c2`; package, install, installed-benchmark, and health artifacts are bound to `81598899fcd85df58ab22f9212f2e8382f4a5e5f`. | | Release integration evidence | PR #184 release integration at `448c42758ea139bf9203cbf1bb04b02b02ae412c`; exact-main CI, CodeQL, and Pages all succeeded, and `origin/main` matched at the 2026-08-08 branch-start snapshot | @@ -49,6 +50,13 @@ storage. The dated post-PR #193 resulting-main snapshot at declaration of perpetually current `main`. These are engineering changes on `0.2.4.dev0`, not a new published release; v0.2.3 remains the published release and none earns Stage 1 credit. +PR #194 aligned the dated status record without turning it into a moving-head claim; PR #195 closed +Stage 1 as not pursued and activated owner-led Stage 2 without customer-validation claims; and PR +#196 added the exact-head informational Check lifecycle with bounded default-base repair. The next +owner workflow consolidation slice may simplify first use, criteria confirmation, decision progress, +and current documentation. It remains Stage 2 engineering work and does not claim customer +validation. + ### Verification and evidence boundaries - Engineering checks do not prove acceptance-criteria correctness. @@ -281,6 +289,11 @@ validation. Work may improve product and workflow clarity, deterministic evidenc fail-closed lifecycle integrity, packaging, installation, compatibility, accessibility engineering, documentation, public official-source research, and release readiness. +The current owner workflow consolidation priority is to keep public PR and constructed-demo entry +clear, make explicit criteria confirmation unmistakable, put unresolved decisions before secondary +evidence detail, and provide direct navigation to the next criterion. It must preserve every +evidence boundary and keep optional external research secondary. + The [Stage 2 productization packet](docs/commercialization/stage2-readiness-packet.md) is the operating boundary. External commercial discovery is optional and separate from owner-led productization. It is not required to continue Stage 2 and needs separate owner authorization diff --git a/apps/web/app.py b/apps/web/app.py index fcf4a691..5415e616 100644 --- a/apps/web/app.py +++ b/apps/web/app.py @@ -1149,11 +1149,18 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - fetch_action_placeholder = st.empty() alpha_feedback_mode = bool(st.session_state.get("alpha_feedback_mode", False)) -with st.expander("Try ScopeProof", expanded=False): +with st.container(border=True): + st.markdown("**Deliberately constructed demonstration**") + st.caption( + "A visible practice-data path. Any saved record remains constructed-demo-tagged and " + "segregated from genuine review claims. It is not a public PR, customer case, production " + "result, or validation claim." + ) if st.button( "Load deliberately constructed demo", key="load_demo", disabled=replacement_blocked or alpha_feedback_mode, + use_container_width=True, ): labels = load_demo_labels() snapshot = load_demo_snapshot() @@ -1179,13 +1186,42 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - _reset_analysis() st.rerun() -with st.expander("Alpha feedback session (optional)", expanded=False): +with st.expander("Advanced source options", expanded=False): + github_token = st.text_input( + "Optional GitHub token", + type="password", + help=( + "Used only in this session to increase free GitHub rate limits. " + "Never exported or saved." + ), + key="github_token", + ) + candidate_paths_text = st.text_area( + "Bounded unchanged candidate paths (optional)", + key="candidate_paths", + help=( + "One explicit repository-relative file path per line. ScopeProof does not " + "infer paths or scan the repository." + ), + ) + candidate_paths = list( + dict.fromkeys( + line.strip() for line in candidate_paths_text.splitlines() if line.strip() + ) + ) + st.caption("At most eight explicit UTF-8 text files are fetched at the PR head SHA.") + +requirements_source_url = "" +with st.expander("Research and historical options", expanded=False): + st.caption( + "Stage 1 is closed and external feedback is not required for owner-led Stage 2. " + "This optional research path is separate from the standard product workflow." + ) alpha_feedback_mode = st.checkbox( "Collect local alpha feedback for this review", value=False, key="alpha_feedback_mode", ) - if alpha_feedback_mode: st.caption( "Qualification is session-only. Confirm a genuine public case before fetching; " @@ -1209,6 +1245,7 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - "This review contains no confidential information, secrets, or private links", key="no_confidential_information", ) + alpha_qualification_ready = True alpha_qualification_input: AlphaQualificationInput | None = None alpha_qualification: AlphaQualification | None = None @@ -1252,30 +1289,6 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - else: st.caption("Standard review mode does not create participant research records.") -with st.expander("Advanced source options", expanded=False): - github_token = st.text_input( - "Optional GitHub token", - type="password", - help=( - "Used only in this session to increase free GitHub rate limits. " - "Never exported or saved." - ), - key="github_token", - ) - candidate_paths_text = st.text_area( - "Bounded unchanged candidate paths (optional)", - key="candidate_paths", - help=( - "One explicit repository-relative file path per line. ScopeProof does not " - "infer paths or scan the repository." - ), - ) - candidate_paths = list( - dict.fromkeys( - line.strip() for line in candidate_paths_text.splitlines() if line.strip() - ) - ) - st.caption("At most eight explicit UTF-8 text files are fetched at the PR head SHA.") reopened_review = st.session_state["review_state"] fetch_action_label = ( "Check current head" @@ -1696,8 +1709,12 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - for message in warnings_by_criterion[criterion_id]: st.warning(message) + st.caption( + "Typing or pressing Enter only stages draft changes. Use the explicit action below " + "to apply edits and bind the confirmed criteria snapshot." + ) confirm_clicked = confirm_action_placeholder.button( - "Confirm criteria", + "Apply edits and confirm criteria", key="confirm_criteria", disabled=( bool(blank_criterion_ids) @@ -2070,11 +2087,6 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - if comparison.ruleset_version_changed else "Ruleset unchanged between reviews." ) - st.caption( - "Evidence status describes deterministic candidates, not correctness. Evidence types " - "keep implementation, test, and externally recorded runtime observations separate." - ) - _render_ci_observation_summary(bundle) finding_by_id = {finding.criterion_id: finding for finding in bundle.findings} diagnostic_by_id = { diagnostic.criterion_id: diagnostic @@ -2086,6 +2098,54 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - coverage_by_id = { row.criterion_id: row for row in criterion_coverage_rows(bundle) } + blocking_criteria = set(bundle.gate.blocking_criteria) + unresolved_ids = [ + criterion.criterion_id + for criterion in bundle.criteria + if criterion.criterion_id not in resolution_by_id + ] + recorded_decisions = len(bundle.criteria) - len(unresolved_ids) + st.markdown("### Decision progress") + st.caption( + f"Decisions recorded: {recorded_decisions} of {len(bundle.criteria)}." + ) + if unresolved_ids: + st.markdown("### Unresolved criteria queue") + st.caption( + "Review candidate evidence and record an explicit human decision for each item." + ) + for criterion_id in unresolved_ids: + st.markdown(f"[Review {criterion_id}](#review-{criterion_id.lower()})") + for criterion_id in unresolved_ids: + criterion = next( + item for item in bundle.criteria if item.criterion_id == criterion_id + ) + with st.container(border=True): + st.markdown(f"#### Review {criterion_id}") + st.text(criterion.text) + evidence_status = evidence_status_text( + coverage_by_id[criterion_id].evidence_status + ) + st.caption( + f"Candidate evidence: {evidence_status}" + ) + st.caption( + "Observed runtime evidence and the human acceptance decision remain " + "separate from static implementation or test candidates." + ) + st.text(finding_by_id[criterion_id].recommended_action) + if st.button( + f"Open {criterion_id} decision controls", + key=f"inspect_queue_{criterion_id}", + ): + st.session_state["selected_criterion"] = criterion_id + else: + st.success("A current human decision is recorded for every active criterion.") + st.caption( + "Evidence status describes deterministic candidates, not correctness. Evidence types " + "keep implementation, test, and externally recorded runtime observations separate." + ) + _render_ci_observation_summary(bundle) evidence_strength_counts = { EvidenceStatus.STRONG_CANDIDATE: 0, EvidenceStatus.WEAK_CANDIDATE: 0, @@ -2125,7 +2185,6 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - format_func=lambda item: item.value, key="evidence_level_filter", ) - blocking_criteria = set(bundle.gate.blocking_criteria) matrix = [] for criterion in bundle.criteria: finding = finding_by_id[criterion.criterion_id] @@ -2161,6 +2220,7 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - ) for row in matrix: with st.container(border=True): + st.markdown(f"#### Evidence for {row['Criterion']}") st.markdown(f"**Criterion:** {row['Criterion']}") st.caption("Requirement") st.text(row["Requirement"]) @@ -2184,22 +2244,6 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - st.session_state["selected_criterion"] = row["Criterion"] st.rerun() - unresolved_ids = [ - criterion.criterion_id - for criterion in bundle.criteria - if criterion.criterion_id not in resolution_by_id - ] - if unresolved_ids: - st.markdown("### Unresolved criteria queue") - st.caption( - "Review candidate evidence and record an explicit human decision for each item." - ) - for criterion_id in unresolved_ids: - st.markdown( - f"- **{criterion_id}** — " - f"{finding_by_id[criterion_id].recommended_action}" - ) - st.header("4 · Criterion Detail") criterion_ids = [criterion.criterion_id for criterion in bundle.criteria] selected_criterion = st.session_state.get("selected_criterion") @@ -2819,6 +2863,18 @@ def _render_ingestion_limitations(source: PullRequestSnapshot | Review | None) - st.markdown("### What to do next") for message in guidance: st.text(message) + if unresolved_ids: + st.markdown( + "[Review next unresolved criterion]" + f"(#review-{unresolved_ids[0].lower()})" + ) + elif not final_acceptance_recorded: + st.markdown( + "[Record final acceptance after reviewing every criterion]" + "(#final-review-acceptance)" + ) + else: + st.caption("Save the validated review locally or download an export below.") st.caption( f"Head SHA {bundle.review.head_sha} · Ruleset {bundle.review.ruleset_version} · " "results are reproducible from the exported review" diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..0f79d882 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,31 @@ +# ScopeProof documentation map + +Use current operating documents for product decisions. Historical records are immutable evidence +snapshots: they preserve what was observed at a named commit or release and must not be read as the +current product state. + +## Current operating documents + +- [Product roadmap](../ROADMAP.md) — current stage decisions, boundaries, and next owner gates. +- [v0.2.3 status and next stages](releases/v0.2.3-status-and-next-stages.md) — published-release + boundary, current development line, feature ledger, gaps, and stage status. +- [Stage 2 productization packet](commercialization/stage2-readiness-packet.md) — authorized + owner-led engineering scope; it does not claim customer validation. +- [Current official-source market comparison](commercialization/market-comparison-2026-07-26.md) + — dated competitive research and positioning hypotheses. +- [Development environment](development-environment.md) — supported and unsupported engineering + environments. +- [Privacy readiness](privacy-readiness.md) — current local-only data and trust boundaries. + +## Historical evidence records + +- `docs/audits/` contains exact-head implementation, verification, and review snapshots. +- `docs/releases/` contains published-release and post-merge evidence records. The current status + document linked above is the exception and explicitly distinguishes live operating status from + dated evidence. +- `docs/research/` contains constructed or historical research evidence. It does not establish + customer validation or advance a product stage. + +When documents differ, prefer the GitHub Release for publication availability, the current roadmap +for stage authority, and exact-head hosted checks for engineering results. Missing external evidence +remains missing. diff --git a/docs/commercialization/market-comparison-2026-07-26.md b/docs/commercialization/market-comparison-2026-07-26.md index d941fbde..d20bc06e 100644 --- a/docs/commercialization/market-comparison-2026-07-26.md +++ b/docs/commercialization/market-comparison-2026-07-26.md @@ -1,6 +1,7 @@ # ScopeProof market comparison and product adjustment -Date: 2026-07-28 +Date: 2026-07-26 +Last refreshed: 2026-08-17 Status: current product and competitor-documentation audit Boundary: competitor capabilities below are vendor-advertised unless explicitly labelled as a ScopeProof implementation fact. This document is not customer or @@ -33,14 +34,15 @@ criterion is supported at this exact head.” | Category and products | Vendor-advertised job | Overlap with ScopeProof | ScopeProof distinction today | | --- | --- | --- | --- | -| AI PR review — CodeRabbit, Qodo, GitHub Copilot code review | Find bugs or rule violations, summarize changes, suggest fixes, and automate or repeat reviews in the PR workflow. | PR context, changed-code inspection, review guidance, re-review. | ScopeProof does not generate general review comments or fixes. It starts from reviewer-confirmed criteria, exposes deterministic candidate lines, and leaves acceptance unresolved until a human decides. | +| AI PR review — GitHub Copilot, CodeRabbit, Qodo, Graphite | Find bugs or rule violations, summarize changes, suggest fixes, and automate or repeat reviews in the PR workflow. | PR context, changed-code inspection, review guidance, re-review. | ScopeProof does not generate general review comments or fixes. It starts from reviewer-confirmed criteria, exposes deterministic candidate lines at an exact public PR head, and leaves acceptance unresolved until a human decides. | | Static analysis — SonarQube | Analyze issues introduced on new code and report a pass/fail quality gate to the repository platform. | Changed-code analysis, status/gate vocabulary, merge-time use. | ScopeProof does not scan for generic quality or security issues. Its gate is tied to criterion evidence, observed CI, runtime records, and current human decisions. | | Test management and requirements traceability — Qase, TestRail | Link requirements to test cases and results; show covered/uncovered requirements and stale test results. | Requirement-to-test coverage, missing-coverage visibility, snapshots or reports. | ScopeProof works at a single public PR head, includes implementation candidates, and keeps linked/static candidates distinct from executed runtime verification. It is not a test repository or execution manager. | | Work tracking — Azure Boards and GitHub controls | Link work items, PRs, commits, builds, reviews, and required status checks to provide traceability and merge controls. | Requirement/source linkage, PR identity, CI state, policy enforcement. | ScopeProof inspects criterion-level evidence inside the PR rather than treating an item link, template, approval, or passing check as proof that each criterion is covered. | ## Official evidence used -- [CodeRabbit documentation](https://docs.coderabbit.ai/) advertises +- [CodeRabbit pull-request review documentation](https://docs.coderabbit.ai/overview/pull-request-review) + advertises context-aware PR reviews, bug detection, standards enforcement, suggested fixes, and IDE/CLI/PR surfaces. - [CodeRabbit linked-issue validation](https://docs.coderabbit.ai/issues/pr-validation) @@ -61,6 +63,10 @@ criterion is supported at this exact head.” - [GitHub Copilot code review documentation](https://docs.github.com/en/copilot/concepts/agents/code-review) describes AI-generated PR feedback and suggested fixes; it also states the feature is available on paid Copilot plans and consumes AI credits. +- [Graphite AI review documentation](https://graphite.com/docs/ai-reviews) describes automated PR + findings and suggested fixes in Graphite's review workflow. +- [Graphite code review documentation](https://graphite.com/docs/code-review) describes its pull + request review, inbox, and merge workflow. - [GitHub status-check documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks) describes pending, passing, and failing checks attached to commits. - [SonarQube pull-request analysis documentation](https://docs.sonarsource.com/sonarqube-server/2026.1/analyzing-source-code/pull-request-analysis/introduction) @@ -156,5 +162,6 @@ criterion is supported at this exact head.” The best near-term positioning is therefore: **“Inspect acceptance coverage at an exact public PR head without confusing candidate code, CI, runtime proof, or -human acceptance.”** It should remain a positioning hypothesis until genuine -users demonstrate that this job is important and repeated. +human acceptance.”** This is a positioning hypothesis, not customer or market validation. It +should remain a hypothesis unless genuine users later demonstrate that this job is important and +repeated. diff --git a/docs/releases/v0.2.3-status-and-next-stages.md b/docs/releases/v0.2.3-status-and-next-stages.md index ac8a5737..f3359340 100644 --- a/docs/releases/v0.2.3-status-and-next-stages.md +++ b/docs/releases/v0.2.3-status-and-next-stages.md @@ -1,12 +1,18 @@ # ScopeProof v0.2.3 status, gaps, and next stages -Release-baseline date: 2026-08-03; current development alignment: 2026-08-13 +Release-baseline date: 2026-08-03; current development alignment: 2026-08-17 Current release baseline: PR #184 release integration at `448c42758ea139bf9203cbf1bb04b02b02ae412c` Post-PR #193 resulting-main snapshot (2026-08-13): PR #193 product-source baseline at `432371c4faec0b790f70fec32b4d3fc4d5132cfa` (PR head `8bb407079a0ff7098d2fc18af3d75b216725df2e`, base `9426e8714ffd2c3742bb074ae26fc788f1049c63`) +Post-PR #196 resulting-main snapshot (2026-08-17): exact PR #196 head +`5a6a25a6dff23cdfa8dcb4023b83144078620610` landed in merge +`8387156fd6f6e90eef7caf58881b0cc5bb62b111`. Hosted resulting-main CI run +[`32093041685`](https://github.com/YuzeJ21/Scope-Proof/actions/runs/32093041685) is the exact-head +engineering record and succeeded across its full verification, installed-wheel, packaged-browser, +Python 3.11, Python 3.13, Windows, and locked-environment jobs. Development version in this snapshot: unreleased `0.2.4.dev0` after merged PRs #185 and #187–#193 PR #184 release-integration checks: CI run `30854382641`, CodeQL run `30854382413`, and Pages run `30854382659`; exact-main CI, CodeQL, and Pages all succeeded for that PR #184 @@ -54,6 +60,13 @@ storage. The dated post-PR #193 resulting-main snapshot at declaration of perpetually current `main`. v0.2.3 remains the published release; none of this engineering work creates a v0.2.4 release or earns Stage 1 credit. +PR #194 aligned the authoritative dated status record; PR #195 closed Stage 1 as not pursued and +activated owner-led Stage 2 without customer-validation claims; and PR #196 added the exact-head +informational Check lifecycle with bounded default-base repair. The current owner workflow +consolidation priority may simplify first use, explicit criteria confirmation, unresolved-decision +progress, direct criterion navigation, and current documentation. It remains engineering evidence +and does not claim customer validation. + ## Current evidence and storage-maintenance boundaries - Engineering checks do not prove acceptance-criteria correctness. @@ -301,6 +314,11 @@ allows product and workflow clarity, deterministic evidence quality, fail-closed packaging, compatibility, accessibility engineering, documentation, public official-source research, and release-readiness work. +The current owner workflow consolidation priority keeps public PR and constructed-demo entry clear, +makes explicit criteria confirmation unmistakable, puts unresolved decisions before secondary +evidence detail, and provides direct navigation to the next criterion. It must preserve every +evidence boundary and keep optional external research secondary. + External commercial discovery is optional and separate from owner-led productization. It is not required for Stage 2 and requires separate owner authorization before outreach or participant contact. Stage 2 does not authorize a merge, release, tag, package publication, R-002 retuning, diff --git a/tests/apps/test_streamlit_app.py b/tests/apps/test_streamlit_app.py index 7420aeff..f2e5f1f3 100644 --- a/tests/apps/test_streamlit_app.py +++ b/tests/apps/test_streamlit_app.py @@ -1059,17 +1059,37 @@ def test_public_pr_entry_precedes_optional_start_review_controls( def test_start_review_secondary_paths_are_collapsed_after_public_pr_entry() -> None: app = new_app() - assert [item.label for item in app.expander[:4]] == [ - "Try ScopeProof", - "Alpha feedback session (optional)", + assert [item.label for item in app.expander[:3]] == [ "Advanced source options", + "Research and historical options", "Resume a saved review", ] - assert all(item.proto.expanded is False for item in app.expander[:4]) + assert all(item.proto.expanded is False for item in app.expander[:3]) assert app.button(key="load_demo").label == "Load deliberately constructed demo" assert app.button(key="reopen_review").disabled is True +def test_owner_led_first_use_keeps_demo_visible_and_research_feedback_secondary() -> None: + app = new_app() + keys = _main_widget_keys(app) + visible = "\n".join(item.value for item in [*app.markdown, *app.caption]) + + assert "Deliberately constructed demonstration" in visible + assert "constructed-demo-tagged" in visible + assert "segregated from genuine review claims" in visible + assert keys.index("candidate_paths") < keys.index("alpha_feedback_mode") + assert "Stage 1 is closed" in visible + assert "not required for owner-led Stage 2" in visible + + +def test_criteria_confirmation_explains_draft_submission_boundary() -> None: + app = load_demo(new_app()) + caption_text = "\n".join(item.value for item in app.caption) + + assert "Typing or pressing Enter only stages draft changes" in caption_text + assert app.button(key="confirm_criteria").label == "Apply edits and confirm criteria" + + def test_saved_review_is_discoverable_and_selectable_in_a_fresh_session( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: @@ -2250,10 +2270,34 @@ def test_evidence_matrix_has_compact_strength_summary_and_unresolved_queue() -> assert "Weak" in visible_text assert "None" in visible_text assert "Unresolved criteria queue" in visible_text + assert "Decisions recorded: 0 of 4" in visible_text assert "Review candidate evidence and record an explicit human decision" in visible_text assert "ScopeProof does not decide them" in visible_text assert "Gate reasons: Blocking Criteria" in visible_text + captions = [item.value for item in app.caption] + assert captions.index("Decisions recorded: 0 of 4.") < next( + index for index, value in enumerate(captions) if value.startswith("Observed CI:") + ) + markdown = [item.value for item in app.markdown] + assert "[Review AC-01](#review-ac-01)" in markdown + assert "#### Review AC-01" in markdown + assert app.button(key="inspect_queue_AC-02").label == ( + "Open AC-02 decision controls" + ) + + app = app.button(key="inspect_queue_AC-02").click().run() + + assert app.selectbox(key="selected_criterion").value == "AC-02" + + +def test_summary_offers_direct_next_unresolved_action() -> None: + app = analyzed_demo(new_app()) + + assert "[Review next unresolved criterion](#review-ac-01)" in [ + item.value for item in app.markdown + ] + def test_demo_summary_humanizes_gate_reasons_without_mutating_codes() -> None: app = analyzed_demo(new_app()) @@ -3897,6 +3941,9 @@ def test_evidence_matrix_reports_empty_filter_results() -> None: assert evidence_matrix_criterion_ids(app) == [] assert "No criteria match the current filters." in [item.value for item in app.info] + markdown = [item.value for item in app.markdown] + assert "[Review AC-01](#review-ac-01)" in markdown + assert "#### Review AC-01" in markdown def test_evidence_matrix_renders_as_reachable_cards_without_grid_tools() -> None: diff --git a/tests/browser/test_packaged_workbench.py b/tests/browser/test_packaged_workbench.py index 3aa0b3c2..edb2959c 100644 --- a/tests/browser/test_packaged_workbench.py +++ b/tests/browser/test_packaged_workbench.py @@ -223,17 +223,12 @@ def _activate_with_keyboard( page.keyboard.press(key) -def _exercise_primary_path(page: Page, base_url: str) -> None: +def _exercise_primary_path( + page: Page, base_url: str, *, verify_persistence_and_downloads: bool +) -> None: page.goto(base_url, wait_until="domcontentloaded") expect(page.get_by_role("heading", name="ScopeProof", exact=True)).to_be_visible() - demo_disclosure = page.locator("summary").filter(has_text="Try ScopeProof") - _activate_with_keyboard( - page, - demo_disclosure, - label="Try ScopeProof", - key="Enter", - ) load_demo = page.get_by_role( "button", name="Load deliberately constructed demo", exact=True ) @@ -248,11 +243,13 @@ def _exercise_primary_path(page: Page, base_url: str) -> None: page.keyboard.type("Packaged browser reviewer") expect(confirmer).to_have_value("Packaged browser reviewer") - confirm = page.get_by_role("button", name="Confirm criteria", exact=True) + confirm = page.get_by_role( + "button", name="Apply edits and confirm criteria", exact=True + ) _activate_with_keyboard( page, confirm, - label="Confirm criteria", + label="Apply edits and confirm criteria", key="Space", ) expect(page.get_by_text("Criteria confirmed by the reviewer.", exact=True)).to_be_visible() @@ -270,12 +267,51 @@ def _exercise_primary_path(page: Page, base_url: str) -> None: expect(page.get_by_text("Missing evidence", exact=True).first).to_be_visible() expect(page.get_by_text("Review status: Action required", exact=True)).to_be_visible() expect(page.get_by_text("Evidence status:", exact=False).first).to_be_visible() - - for label in ("Download Markdown", "Download JSON", "Download CSV"): + review_ac_02 = page.get_by_role("link", name="Review AC-02", exact=True).first + expect(review_ac_02).to_be_visible() + review_ac_02.click() + assert page.url.endswith("#review-ac-02") + expect(page.get_by_role("heading", name="Review AC-02", exact=True)).to_be_visible() + + export_controls = ( + ("Download Markdown", ".md"), + ("Download JSON", ".json"), + ("Download CSV", ".csv"), + ) + for label, _suffix in export_controls: export = page.get_by_role("button", name=label, exact=True) expect(export).to_be_visible() expect(export).to_be_enabled() + if verify_persistence_and_downloads: + save_notice = page.get_by_text("Review saved automatically. ID:", exact=False) + expect(save_notice).to_be_visible() + + markdown_export = page.get_by_role("button", name="Download Markdown", exact=True) + with page.expect_download() as download_info: + markdown_export.click() + download = download_info.value + assert download.suggested_filename.endswith(".md") + assert b"head-demo-002" in download.path().read_bytes() + + page.get_by_text("Resume a saved review", exact=True).click() + expect(page.get_by_text("saved local review found", exact=False)).to_be_visible() + saved_review = page.get_by_role("combobox", name="Saved review ID", exact=True) + saved_review.locator("..").get_by_role("button", name="Open", exact=True).click() + page.keyboard.press("ArrowDown") + page.keyboard.press("Enter") + reopen = page.get_by_role("button", name="Reopen local review", exact=True) + expect(reopen).to_be_enabled() + reopen.click() + expect( + page.get_by_text( + "Review reopened from local storage after validation.", exact=True + ) + ).to_be_visible() + expect( + page.get_by_role("button", name="Check current head", exact=True) + ).to_be_visible() + assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth") assert page.evaluate("document.body.scrollWidth <= window.innerWidth") @@ -359,7 +395,7 @@ def test_installed_wheel_primary_path_in_chromium( browser = playwright.chromium.launch(headless=True) try: external_requests: list[str] = [] - for viewport in VIEWPORTS: + for viewport_index, viewport in enumerate(VIEWPORTS): context = browser.new_context(viewport=viewport) context.route( "**/*", @@ -383,7 +419,11 @@ def test_installed_wheel_primary_path_in_chromium( else None ), ) - _exercise_primary_path(page, base_url) + _exercise_primary_path( + page, + base_url, + verify_persistence_and_downloads=viewport_index == 0, + ) context.close() finally: browser.close() diff --git a/tests/test_repository_contracts.py b/tests/test_repository_contracts.py index 82aa7c82..3d5a94af 100644 --- a/tests/test_repository_contracts.py +++ b/tests/test_repository_contracts.py @@ -43,6 +43,9 @@ PR193_RESULTING_MAIN_CI_RUN_ID = "31704668247" PR193_RESULTING_MAIN_CODEQL_RUN_ID = "31704666031" PR193_RESULTING_MAIN_PAGES_RUN_ID = "31704668164" +PR196_EXACT_HEAD_SHA = "5a6a25a6dff23cdfa8dcb4023b83144078620610" +PR196_MERGE_SHA = "8387156fd6f6e90eef7caf58881b0cc5bb62b111" +PR196_RESULTING_MAIN_CI_RUN_ID = "32093041685" GITHUB_ACTIONS_RUN_ROOT = "https://github.com/YuzeJ21/Scope-Proof/actions/runs" @@ -1707,6 +1710,67 @@ def test_active_docs_distinguish_post_v023_engineering_from_release_and_stage_pr assert unsupported in platform +def test_current_docs_index_and_market_refresh_preserve_owner_led_boundary() -> None: + readme = Path("README.md").read_text(encoding="utf-8") + docs_index = Path("docs/README.md").read_text(encoding="utf-8") + market = Path("docs/commercialization/market-comparison-2026-07-26.md").read_text( + encoding="utf-8" + ) + changelog = Path("CHANGELOG.md").read_text(encoding="utf-8") + + assert "docs/README.md" in readme + assert "## Current operating documents" in docs_index + assert "## Historical evidence records" in docs_index + normalized_docs_index = " ".join(docs_index.split()) + for current_document in ( + "../ROADMAP.md", + "releases/v0.2.3-status-and-next-stages.md", + "commercialization/stage2-readiness-packet.md", + "commercialization/market-comparison-2026-07-26.md", + ): + assert current_document in docs_index + assert "Historical records are immutable evidence snapshots" in normalized_docs_index + + normalized_market = " ".join(market.split()) + assert "Date: 2026-07-26" in market + assert "Last refreshed: 2026-08-17" in market + for product in ("GitHub Copilot", "CodeRabbit", "Qodo", "Graphite"): + assert product in market + for official_source in ( + "https://docs.github.com/en/copilot/concepts/agents/code-review", + "https://docs.coderabbit.ai/overview/pull-request-review", + "https://docs.qodo.ai/code-review", + "https://graphite.com/docs/ai-reviews", + ): + assert official_source in market + assert "exact public PR head" in normalized_market + assert "positioning hypothesis, not customer or market validation" in normalized_market + + unreleased = changelog.split("## 0.2.3", maxsplit=1)[0] + assert "owner workflow" in unreleased.lower() + assert "documentation index" in unreleased.lower() + + +def test_authoritative_status_records_dated_post_pr196_main_evidence() -> None: + roadmap = Path("ROADMAP.md").read_text(encoding="utf-8") + status = Path("docs/releases/v0.2.3-status-and-next-stages.md").read_text( + encoding="utf-8" + ) + + for document in (roadmap, status): + normalized = " ".join(document.split()) + assert "Post-PR #196 resulting-main snapshot (2026-08-17)" in normalized + assert f"PR #196 head `{PR196_EXACT_HEAD_SHA}`" in normalized + assert f"merge `{PR196_MERGE_SHA}`" in normalized + assert ( + f"[`{PR196_RESULTING_MAIN_CI_RUN_ID}`]" + f"({GITHUB_ACTIONS_RUN_ROOT}/{PR196_RESULTING_MAIN_CI_RUN_ID})" + ) in normalized + assert "succeeded" in normalized + assert "owner workflow consolidation" in normalized.lower() + assert "does not claim customer validation" in normalized + + def test_authoritative_stage_one_docs_record_post_pr193_truth_and_owner_gate() -> None: roadmap = Path("ROADMAP.md").read_text(encoding="utf-8") status = Path("docs/releases/v0.2.3-status-and-next-stages.md").read_text(