Skip to content

Fix /review tests screenshot publishing - #36785

Merged
PureWeen merged 12 commits into
mainfrom
fix/review-tests-asset-publishing
Jul 29, 2026
Merged

Fix /review tests screenshot publishing#36785
PureWeen merged 12 commits into
mainfrom
fix/review-tests-asset-publishing

Conversation

@kubaflo

@kubaflo kubaflo commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description of Change

Fixes screenshot publication for the merged /review tests visual-comparison workflow.

New visual assets are now published from an orphan, asset-only review-tests-assets-v2 branch. The publisher initializes that branch with a root commit containing only a marker blob. On every publication attempt it permits top-level blobs and pr-<number> directories, while rejecting trees at any other path, submodule gitlinks, malformed entries, and truncated root-tree responses.

The local runner also recognizes report markers only when they occupy a standalone line, preventing a marker quoted in agent prose from leaking a junk preamble or duplicate marker into the posted comment.

The legacy review-tests-assets branch remains untouched so all existing raw.githubusercontent.com links pinned to its historical commit SHAs remain reachable.

Root Cause

Production validation on PRs #36628 and #36404 showed that the merged workflow activated correctly, gathered evidence, invoked the agent, and posted the ordinary analysis comments:

Both comments contained zero screenshot panels. Their trusted pre-activation jobs had contents: write, prepared visual evidence, and successfully created Git blobs, trees, and commits, but failed when advancing the asset ref:

PATCH repos/dotnet/maui/git/refs/heads/review-tests-assets
HTTP 403: Resource not accessible by integration

review-tests-assets had originally been initialized by pointing it at main, so every generated asset commit inherited the entire repository tree, including .github/workflows. Updating a ref whose resulting tree contains workflow files requires GitHub's separate Workflows write permission, which cannot be granted to GITHUB_TOKEN.

Fix

  • Switch new publication to review-tests-assets-v2.
  • Initialize a missing asset branch as an orphan root containing only .review-tests-assets.
  • Never copy the default branch tree into an asset branch.
  • Validate existing and concurrently-created branch tips before use.
  • Fail closed when the root tree is truncated or contains any top-level entry that is neither a blob nor a pr-<positive-number> directory. Plain blobs cannot carry .github/workflows, so optional files such as a README remain safe.
  • Preserve the existing fast-forward retry behavior for concurrent publishers.
  • Keep the agent job read-only and avoid introducing a broader PAT or GitHub App credential.
  • Extract only standalone report marker or heading lines from local Copilot output, ignoring quoted markers in explanatory prose.

Validation

  • 171 focused Pester tests pass across gathering, publication, visual merging, and the local runner, including all 28 publisher tests and seven new report-extraction regression cases.
  • PowerShell parsing passes.
  • Fork Actions smoke run 30153639425 passed using only the job-scoped GITHUB_TOKEN with contents: write. It created a zero-parent, marker-only root; advanced review-tests-assets-v2 to an asset commit; verified the root contains only the marker and pr-36785; and downloaded the immutable published PNG.
  • Real-evidence fork run 30154060821 replayed the last available maui-pr-uitests build for PR [Windows] Replace Header with PlaceholderText for Title property of Picker #33007 through the unchanged publisher and panel merger. It gathered 98 real comparisons, published the bounded 24 comparisons as 72 baseline/actual/diff PNGs at asset commit 5d114f9, rendered 15 screenshot panels within the 45-URL comment limit, and verified every embedded immutable PNG URL.
  • A full local /review tests run on PR [Windows] Replace Header with PlaceholderText for Title property of Picker #33007 gathered all three available MAUI builds, ran the claude-opus-4.8 analysis, published 24 comparisons to the upstream orphan asset branch, merged 13 bounded panels, and posted the complete report. Its Not ready verdict matches the deterministic ceiling, and all 39 embedded PNG URLs were verified.
  • A full local run on PR [Net11]Improve label mapping performance and ensure complete coverage including ToPlatform and subsequent property changes #35892 published 22 comparisons at asset commit f50f5a1, merged 12 bounded panels, and posted the complete report. Its Not ready verdict matches the deterministic ceiling, and all 36 embedded immutable PNG URLs return HTTP 200.
  • A full local run from publisher commit aad33ce on PR [Net11] [iOS/MacCatalyst] Migrate TabbedPage to handler architecture #36507 published 3 comparisons at asset commit 3cb2045, merged 3 bounded panels, and updated the complete report. Its Not ready verdict matches the deterministic ceiling, all 9 embedded immutable PNG URLs return HTTP 200, and replaying the exact quoted-marker output through parser commit 30cfb3d produces one local marker with no junk preamble.
  • Full local runs on PRs #36277 and #36404 found no publishable visual snapshots and correctly kept the ordinary analysis unchanged. Their final verdicts match their deterministic ceilings, covering the no-visual path.
  • After these publications, review-tests-assets-v2 still contains only .review-tests-assets and top-level pr-<number> directories; its tip 3cb2045 fast-forwards from f67ed0e.
  • gh aw v0.82.14 compiles copilot-review-tests with 0 errors and 0 warnings; the generated lock and actions lock remain unchanged.
  • A focused read-only review found no significant correctness, security, data-loss, race, or compatibility issues.

Issues Fixed

Follow-up to #36666.

Publish new visual assets from an orphan asset-only branch so the Actions token does not require Workflows permission to advance the ref. Preserve the legacy branch for existing commit-pinned image URLs and validate branch contents before every publication.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a280b482-e102-4ca0-9ff9-1cfe1946e21f
Copilot AI review requested due to automatic review settings July 25, 2026 09:51
@kubaflo
kubaflo temporarily deployed to copilot-pat-pool July 25, 2026 09:51 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36785

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36785"

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Skill Validation Results

@kubaflo — new skill validation results are available based on this last commit: dd18bda.
To request a fresh validation after new comments or commits, comment /evaluate-skills.

Overall Passed Static Passed LLM Skipped Skills 23 Agents 6

Skill Validation Resultsdd18bda · Fix /review tests screenshot publishing · 2026-07-29T16:00:26Z

✅ Static Checks Passed

Skills: 23 | Eval specs linted: 12

Full lint output
── .github/skills/agentic-labeler/tests/eval.vally.yaml
npm warn deprecated prebuild-install@7.1.3: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
✔ .github/skills/agentic-labeler/tests/eval.vally.yaml is valid
── .github/skills/analyze-sessions/tests/eval.vally.yaml
✔ .github/skills/analyze-sessions/tests/eval.vally.yaml is valid
── .github/skills/code-review/tests/eval.capability.vally.yaml
✔ .github/skills/code-review/tests/eval.capability.vally.yaml is valid
── .github/skills/code-review/tests/eval.inline-findings.vally.yaml
✔ .github/skills/code-review/tests/eval.inline-findings.vally.yaml is valid
── .github/skills/code-review/tests/eval.producer-trace.vally.yaml
✔ .github/skills/code-review/tests/eval.producer-trace.vally.yaml is valid
── .github/skills/code-review/tests/eval.trim-aot.vally.yaml
✔ .github/skills/code-review/tests/eval.trim-aot.vally.yaml is valid
── .github/skills/code-review/tests/eval.vally.yaml
✔ .github/skills/code-review/tests/eval.vally.yaml is valid
── .github/skills/code-review/tests/hermeticity.vally.yaml
✔ .github/skills/code-review/tests/hermeticity.vally.yaml is valid
── .github/skills/evaluate-pr-tests/tests/eval.vally.yaml
✔ .github/skills/evaluate-pr-tests/tests/eval.vally.yaml is valid
── .github/skills/pr-review/tests/eval.gh-auth.vally.yaml
✔ .github/skills/pr-review/tests/eval.gh-auth.vally.yaml is valid
── .github/skills/try-fix/tests/eval.vally.yaml
✔ .github/skills/try-fix/tests/eval.vally.yaml is valid
── .github/skills/verify-tests-fail-without-fix/tests/eval.vally.yaml
✔ .github/skills/verify-tests-fail-without-fix/tests/eval.vally.yaml is valid

⏭️ LLM Evaluation: Skipped

No changed skills with eval specs found.

🔍 Full results and investigation steps

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes /review tests visual screenshot publishing by switching publication to a new orphan, asset-only branch (review-tests-assets-v2) and validating that the branch tip contains only approved top-level paths, avoiding GitHub’s workflow-file permission restrictions that block GITHUB_TOKEN from advancing refs on trees containing .github/workflows.

Changes:

  • Default publishing branch changed from review-tests-assets to review-tests-assets-v2.
  • Asset branch initialization now creates an orphan root commit containing only a marker blob, rather than inheriting the default branch tree.
  • Added branch-tip validation logic and expanded Pester coverage for branch initialization and concurrent creation scenarios.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
.github/skills/review-test-failures/scripts/Publish-TestVisualAssets.ps1 Switches default branch to review-tests-assets-v2, adds asset-branch initialization via orphan root commit, and validates branch tree contents before publishing.
.github/skills/review-test-failures/scripts/Publish-TestVisualAssets.Tests.ps1 Adds tests covering asset-only initialization, reuse of valid asset branch, rejection of unsafe trees, and concurrent creation handling.
.github/docs/maui-ci-facts.md Updates CI documentation to reflect new asset branch and explains why the legacy branch is no longer used for new publications.

@kubaflo
kubaflo temporarily deployed to copilot-pat-pool July 25, 2026 09:55 — with GitHub Actions Inactive
@kubaflo
kubaflo temporarily deployed to copilot-pat-pool July 25, 2026 09:56 — with GitHub Actions Inactive
@github-actions github-actions Bot added the area-infrastructure CI, Maestro / Coherency, upstream dependencies/versions label Jul 25, 2026
@kubaflo
kubaflo temporarily deployed to copilot-pat-pool July 25, 2026 09:56 — with GitHub Actions Inactive
github-actions Bot added a commit to kubaflo/maui that referenced this pull request Jul 25, 2026

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial multi-model review

Three independent reviewers analyzed this PR in parallel, followed by a dispute round on contested findings. No blocking (❌) or should-fix (⚠️) findings survived consensus. The fix is well-targeted and the stated root cause holds up under verification.

Verification highlights

The root-cause claim was independently confirmed rather than taken on trust. One reviewer queried the live repo's Git Data API and found review-tests-assets does carry .github/src at its root (confirming the inherited-tree → workflow-permission 403), while review-tests-assets-v2 already exists with root tree exactly .review-tests-assets + pr-33007 — so the fix has already round-tripped in production and branch-protection rulesets don't block ref creation.

Several plausible-sounding concerns were investigated and rejected as false positives (recorded here so they don't get re-litigated):

  • Stale $branchState → silent data loss on retry. Rejected 3/3. Every path out of the ref-PATCH catch either throws or sets $branchState = $null before backing off. The PATCH is force = $false, so git's fast-forward check makes silently dropping a concurrent publisher's entries impossible — the worst case is one wasted round-trip under contention, bounded by MaxAttempts = 5.
  • parents = @() mangled by ConvertTo-Json, breaking the orphan-commit premise. Rejected 2/3 by direct execution: Invoke-GhApiJson uses -Depth 20, and both "parents":[] and the nested single-element tree array serialize correctly.
  • ^pr-[1-9][0-9]*$ rejecting real asset paths. Rejected. $PrNumber is [int] and validated against a real PR upstream, so pr-0/leading-zero directories aren't reachable.
  • Legacy links breaking. Rejected. Raw URLs are built from the asset commit SHA, never the branch name, so the branch-name default change can't affect pinned links.
  • -notmatch 'HTTP 422|Reference already exists' masking unrelated 422s. Rejected — that guard is unchanged by this PR, and throw $creationError.Exception now preserves the original message verbatim instead of the previous generic one.
  • Command injection / path traversal. Rejected. $Repository/$Branch/$Endpoint reach gh via ArgumentList (argument array, no shell), and the pre-existing $AssetBranch deny-list is unchanged.

Findings (both non-blocking)

💡 Logic — validation over-blocks beyond what the 403 defense requires (2/3 after dispute; inline on Publish-TestVisualAssets.ps1)
The guard runs on every publish and rejects any foreign top-level entry, including plain blobs. A blob can't carry a workflow, so rejecting README.md/LICENSE/.gitattributes is broader than the defense needs — and there's no recovery path in code. Reviewers split ⚠️/💡; settled at 💡 because publishing is supplementary-only and degrades gracefully.

💡 Testing — two permissive validation edge cases are untested (2/3 after dispute; inline on Publish-TestVisualAssets.Tests.ps1)
See inline note — with a caveat about which of the two is actually worth pinning.

On the existing Copilot comment (marker not enforced)

All three reviewers independently reproduced this: Get-ValidatedAssetBranchState rejects only unexpected entries, so an empty tree — or one with pr-N dirs and no marker — passes, contradicting the thrown message and the doc. Calibration: real, but benign. None of the three could construct a failure from it, because a marker-less tree still excludes top-level repo directories, which is the actual 403 trigger. Worth noting it points the opposite direction from the finding above — one says the guard is too loose, the other too strict. Deciding whether the marker is a required invariant would resolve both at once.

Test coverage

The four new Pester tests cover the changed paths meaningfully, and the 5→4 deadline-call-count change is legitimate (3/3) — the removed per-attempt GET /git/commits/* is now served from the cached validated state, not a lost call. On the repo's "bug fix needs a regression test for the original scenario" rule: no test reproduces the literal 403, but initializes a missing branch from an asset-only root... asserts 0 GETs of repos/dotnet/maui and no base_tree, so it fails the moment anyone reverts to seeding from the default branch. That covers the causal mechanism, which is the more durable assertion.

What looks right

Threading the validated {ref, commit} state through the retry loop removes a per-attempt GET /git/commits/* while adding validation — the deadline-budget test's 5→4 assertion is a genuine reduction in API calls, not a dropped safety check.

Prior reviews

One prior COMMENTED review from copilot-pull-request-reviewer with a single inline comment (marker-not-enforced, still unresolved) — addressed above rather than duplicated.


Methodology: 3 independent reviewers with adversarial consensus; contested findings sent through a follow-up dispute round. Findings surviving on a single reviewer without corroboration were discarded.

Comment thread .github/skills/review-test-failures/scripts/Publish-TestVisualAssets.ps1 Outdated
Comment thread .github/skills/review-test-failures/scripts/Publish-TestVisualAssets.Tests.ps1 Outdated
@kubaflo kubaflo added the area-ai-agents Copilot CLI agents, agent skills, AI-assisted development label Jul 25, 2026
… review)

Get-ValidatedAssetBranchState rejected ANY foreign top-level entry, including
plain blobs (README.md, LICENSE, .gitattributes). Only a top-level *directory*
can carry .github/workflows content -- the inherited-repo-tree case that trips
the workflow-scope 403 this guard defends against -- so a blob can never be a
threat. Rejecting blobs risked a repo-wide screenshot-publish lockout with no
in-code recovery path if a maintainer added a README to the long-lived asset
branch. Narrow the predicate to reject only unexpected `tree` entries and
tolerate arbitrary top-level blobs, keeping the 403 defense fully intact
(PureWeen review).

Tests: add a top-level-blob-tolerance test (README/LICENSE/marker + pr-42 dir
now pass) and an empty-root-tree test (pins that JSON [] -> @() -> zero entries
passes, guarding the dense predicate against a future refactor). Per the review,
the marker-invariant edge case is intentionally left untested pending a decision
on whether the marker is required. Publish suite 25/0; verified fail-without-fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
Copilot AI review requested due to automatic review settings July 25, 2026 22:31
@kubaflo

kubaflo commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

@PureWeen addressed both non-blocking findings in b585fb1434b:

  • Logic (validation over-blocks): Get-ValidatedAssetBranchState now rejects only unexpected top-level directories and tolerates arbitrary blobs (README/LICENSE/marker), keeping the 403 defense intact while removing the repo-wide publish-lockout risk. Added a blob-tolerance test.
  • Testing (untested permissive paths): added the empty-root-tree test (pins JSON []@()→zero-entries pass-through against a future refactor). Per your guidance I did not add the marker-less test, since the marker-invariant decision is still open (it also intersects the Copilot fail-closed comment — I've replied there proposing we decide the invariant once to resolve both).

Publish suite 25/0; verified fail-without-fix. Ready for re-review — thanks!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread .github/skills/review-test-failures/scripts/Publish-TestVisualAssets.ps1 Outdated
…6785 review)

The prior narrowing rejected only unexpected `tree` entries, which opened a
fail-open gap: a top-level submodule gitlink (tree entry type 'commit') or a
malformed/$null entry would pass validation, and a 'commit'-typed `.github`
could re-introduce the workflow-scope 403 the guard defends against (Copilot
follow-up finding).

Flip the predicate to a fail-CLOSED allowlist: an entry is allowed only if it is
a top-level blob (README/LICENSE/.gitattributes/marker -- none can carry a
workflow) or a pr-<number> directory. Everything else -- unexpected trees,
'commit' gitlinks, and $null entries -- is rejected. This preserves PureWeen's
blob tolerance while restoring fail-closed behavior for every other shape.

Tests: add a submodule-gitlink rejection test and a malformed-null-entry
rejection test; both verified fail-without-fix. Publish suite 27/0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
Copilot AI review requested due to automatic review settings July 25, 2026 22:48
@kubaflo

kubaflo commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

@PureWeen / Copilot — addressed the fail-open gap from the last round in 7d6e0547b43. The asset-branch guard is now a fail-closed allowlist: only top-level blobs (README/LICENSE/marker) and pr-<number> directories pass; unexpected trees, submodule gitlinks (type: 'commit'), and malformed/$null entries are all rejected — so a non-blob entry can't re-introduce the workflow-scope 403. Added gitlink + malformed-null rejection tests (fail-without-fix verified). Publish suite 27/0. Ready for re-review — thanks!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

.github/skills/review-test-failures/scripts/Publish-TestVisualAssets.ps1:556

  • The allowlist comment implies only a small set of blob filenames are permitted, but the implementation intentionally allows any top-level blob. Tweaking the comment to say “any plain file” avoids future refactors accidentally tightening behavior based on this wording (the tests explicitly rely on arbitrary blobs being allowed).
            # Fail CLOSED: an entry is allowed ONLY IF it is a top-level blob (README.md, LICENSE,
            # .gitattributes, or the .review-tests-assets marker -- none can carry a workflow) or a
            # pr-<number> directory. Everything else is rejected: an unexpected `tree` (.github) is

When the GitHub trees API returns truncated=true for the asset branch root, the
guard correctly fails closed (it can't validate what it can't see) but the error
was a dead end. Expand the message to explain the cause (too many top-level
entries, typically accumulated pr-<number> directories) and the recovery (prune
old pr-<number> dirs or rotate to a fresh marker-only asset branch; publishing
resumes automatically once the root tree fits a single non-truncated response).
Add a truncated-tree test asserting the fail-closed throw carries actionable
recovery guidance (Copilot review). Publish suite 28/0; fail-without-fix verified.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
Copilot AI review requested due to automatic review settings July 25, 2026 23:09

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 5 — re-review of cc8bb167 (depth-aware bound)

Re-reviewed the single new commit (the round-4 follow-up that implements the depth-aware bound I suggested). Three independent reviewers, adversarial consensus, plus a dispute round on the one contested finding. Every claim was verified empirically in pwsh against the real functions (dot-sourced via AST) and A/B'd against the parent b1ba3194 — not reasoned about abstractly. CI status is out of scope.

The core round-4 fix is real — verified

The bare-unfenced self-quote case is genuinely fixed: 3 of the 4 new tests are non-vacuous mutation-killers (fail on the parent, pass on head), the full suite is 27/27 on head, and earliest-wins + the round-2/3 protections still hold (re-verified against the live function, including CRLF / no-trailing-newline / <details open> / the stray-leading-</details> decrement guard).

❌ But the round-4 dispute caveat is NOT closed — the bound can still borrow a later report's structure (2/3, + independent A/B verification)

See inline on Review-Tests.ps1:307. My round-4 caveat ("a naive 'bound only at depth 0' is not automatically safe — an unclosed/malformed earlier report at depth > 0 could borrow a later genuine report's structure") reproduces on head. Fixture: an unclosed report one, a genuine well-formed report two, then a trailing unmatched </details>. The parent extracts the clean report two; head commingles report one + report two under report one's anchor and drops the trailing content. Because report one's <details> is still open at the sibling anchor, the depth gate skips the bound, and the (unchanged) balance loop then rebalances across report two via the trailing close. That is silent data loss / a wrong published report where the parent was correct.

Calibration (honest): Reviewer 1 rated the narrower variant — a self-quoting report one + a lone stray </details> with no genuine report two — as arguably working-as-intended, since there the parent only "preserved" the tail as a side effect of the round-3 over-truncation this commit deliberately removes. That framing is fair for that sub-case. It does not cover the genuine-second-report commingle above, where the parent produces a correct clean extraction and head regresses it. Given this lands exactly on the caveat I flagged, and given this PR's precedent of review-response commits introducing new defects (the b585fb14 fail-open), I'm surfacing it at ❌ rather than discarding — but it's a comment, not a block. Reachability is bounded ($Content is the model's own concatenated report attempts, and the contract keeps content inside the top-level <details>), yet a truncated first attempt + a successful retry concatenated together is precisely the scenario this whole fix chain exists to handle.

⚠️ The new caveat test is vacuous (3/3)

See inline on Review-Tests.Tests.ps1:430. The one test framed as covering the caveat passes identically on parent and head, so it exercises the pre-existing balance loop, not the depth-aware delta — it gives false confidence the caveat is closed. Swapping in the borrow fixture above (which fails on head) would make it a real gate.

💡 Minor (1/3, non-blocking)

The $AnchorIndices bound scan restarts at index 0 and re-skips every earlier anchor on each depth-0 line, so the "near O(1) per line" comment overstates it (worst case ~O(A²·L)). Correctness is unaffected and real anchor counts are tiny; either start the scan past the candidate's own offset or drop the O(1) claim.

Bottom line

The commit is a real improvement for the common self-quote case and regresses none of the round-2/3/4 protections. But the specific unbalanced case the round-4 caveat named is still open (commingle + trailing-content loss), and the test meant to prove it closed doesn't exercise it. Worth closing the borrow (reject the candidate once a later sibling anchor is crossed at depth > 0 and a subsequent <details> opens) and making the caveat test non-vacuous before merge.


3 independent reviewers with adversarial consensus (one contested finding taken through a dispute round). Third consecutive review-response commit scrutinized for self-introduced defects, per the pattern earlier in this PR. Claims verified empirically in pwsh, A/B vs parent b1ba3194. CI status is out of scope.

Comment thread .github/scripts/Review-Tests.ps1
Comment thread .github/scripts/Review-Tests.Tests.ps1 Outdated
Round-5 review (PureWeen, 3rd scrutinized response commit) found the round-4
depth-aware bound still open to the borrow caveat it was meant to close:

- ❌ Data loss / commingle: for an UNCLOSED report one + a genuine well-formed
  report two + a TRAILING unmatched </details>, report one's <details> is still
  open at report two's marker, so the depth gate skipped the bound; the balance
  loop then rebalanced across report two via the trailing close and published a
  mis-anchored blob of ONE+TWO, dropping the tail. The parent extracted the clean
  report two. Fix (PureWeen's direction): once a later same-tier anchor is crossed
  while inside our own <details> (depth > 0), a subsequent <details> open means a
  DISTINCT sibling report — reject the candidate (return $null) instead of letting
  the balance loop borrow its structure. A legitimate self-quote opens no <details>,
  so the round-4 self-quote fix is preserved; the block reset on a clean close keeps
  later independent content unaffected.

- ⚠️ Vacuous test: the old "unclosed earlier report" test passed identically on
  parent and head (its report two had its own <details>, so the pre-existing balance
  loop did the work). Replaced with PureWeen's diverging borrow fixture (report two
  + trailing unmatched close + FINAL CONTENT), asserting ONE and TWO never publish
  together. It fails on the r4 parent (26/1) and passes now — a real gate.

- Perf (non-blocking): the anchor bound scan no longer restarts at index 0 per line
  (which re-skipped every earlier anchor, ~O(A^2*L)); a monotonic cursor advances
  once through the ascending indices. ~0.74s vs ~0.92s at a pathological N=1500.

Suite 27/0; round-2/3/4 protections and all self-quote cases still pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
Copilot AI review requested due to automatic review settings July 26, 2026 22:09
@kubaflo

kubaflo commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@PureWeen — round-5 borrow caveat closed in 9f42284efd2.

❌ commingle → once a later same-tier anchor is crossed at depth > 0 (bound skipped for a self-quote), a subsequent <details> open is treated as a distinct sibling report and the candidate is rejected (return $null) — so an unclosed report one can no longer borrow report two's structure via a trailing close. Your fixture now returns the clean Report TWO, matching the parent; FINAL CONTENT is no longer dropped. Self-quote preserved (a bare quoted marker opens no <details>); flag resets on clean close.

⚠️ vacuous test → replaced with your diverging fixture (report two + trailing unmatched close). It fails on the r4 parent (26/1) and passes now — a real gate.

💡 perf → the bound scan no longer restarts at index 0 per line; a monotonic cursor walks the ascending indices once (~0.74s vs ~0.92s at N=1500).

Suite 27/0; round-2/3/4 protections and all self-quote cases still pass. This is the fourth response commit in the chain — I A/B'd every case against the parent to make sure it converges rather than trading one defect for another. Ready for another look.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 6 — re-review of 9f42284 (reject cross-report commingle)

Re-reviewed the single new commit — the round-5 response that implements the depth-aware borrow rejection I suggested. Three independent reviewers, adversarial consensus; every claim was verified empirically in pwsh against the real functions (AST-extracted) and A/B'd against the parent cc8bb167. CI status is out of scope.

The round-5 fix works — verified

The commingle case I flagged is genuinely closed: on the new test's own fixture, parent commingles Report ONE + Report TWO (and drops FINAL CONTENT); head returns the clean Report TWO. All three reviewers confirmed the new test is non-vacuous (diverges parent vs head), the $anchorCursor rewrite is behavior-equivalent to the removed foreach (no off-by-one at the line boundaries or with multiple anchors on a line), and the round-4 self-quote case (no nested <details>) still extracts correctly.

❌ But the fix over-corrects into the opposite failure — silent data loss (3/3, each independently reproduced)

See the inline comment on Review-Tests.ps1. The new return $null gate rejects a legitimate single report whenever it (a) self-quotes its own marker/heading inside its open <details> — the realistic behavior round-4 exists for — and (b) then opens a nested <details>, which the production report template always does. Head then either publishes a mis-anchored fragment that drops the verdict/recommendation, or (self-quote at depth ≥ 2) drops the whole report to a $null skeleton. Parent handles all of these correctly. This is the fourth consecutive round in which a fix to this function trades one extraction bug for another in the opposite direction.

I'll flag my own role again: this defect lives in the change I asked for in round 5. The narrow "reject on the next <details> open" mechanism can't tell a genuine sibling report from the report's own nested evidence — the two are structurally identical under the accepted grammar — so the borrow case needs to be resolved at the balance-loop stage rather than by eager rejection.

Testing

The commit adds a regression test for the commingle direction it fixes but none for the direction it regresses (self-quote + nested <details>). Pinning both shapes — depth 1 and depth ≥ 2, asserting evidence and verdict survive — is what stops this ping-pong.

Methodology: 3 independent reviewers with adversarial consensus; all findings reproduced against the live function, parent vs head. Not gated on CI.

Comment thread .github/scripts/Review-Tests.ps1 Outdated
…ew r6)

Round-6 review found the round-5 eager `return $null` over-corrects into the
opposite data loss: it rejects a LEGITIMATE single report whenever it self-quotes
its marker inside its own <details> and then opens a nested <details> — which the
production report template ALWAYS does. Head dropped Evidence + the
Verdict/Recommendation (depth-1 quote) or returned the whole report as $null
(depth >= 2 quote). As round 6 establishes, a genuine sibling report and a report's
own nested evidence block are STRUCTURALLY IDENTICAL under the accepted grammar, so
no eager per-<details>-open gate can tell them apart.

Fix: revert the round-5 eager rejection and restore the round-4 depth-aware bound
(bound a later same-tier anchor only at <details> depth 0; ignore it inside the
report's own open block). This keeps the realistic self-quote-plus-nested template
whole AND still handles the genuinely-unclosed earlier report — its details never
rebalance to 0, so the balance loop rejects it and extraction falls through to the
next report, no borrow. The round-5 trailing-close "commingle" fixture is
structurally a single nested report, indistinguishable from the legitimate case, so
it now extracts as one report rather than being rejected. The monotonic-cursor perf
improvement from round 5 is retained.

Tests: replaced the (now structurally-impossible) commingle test with three guards
— a self-quoting report + nested <details> at depth 1 and depth 2 (asserting
evidence AND verdict survive; both fail on the r5 parent), and a genuinely-unclosed
first report falling through to a well-formed second. Suite 29/0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
Copilot AI review requested due to automatic review settings July 26, 2026 23:10
@kubaflo

kubaflo commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@PureWeen — round-6 over-correction fixed in a46479ec403 by reverting the round-5 eager rejection back to the round-4 depth-aware bound.

❌ over-correction → the eager return $null on the next <details> open broke the realistic self-quote-plus-nested template (drops evidence + verdict at depth 1, $null at depth ≥ 2). Since — as your round-6 analysis shows — a genuine sibling and a report's own nested evidence are structurally identical, no eager per-open gate can separate them. Reverted; A/B-verified head now returns the full report matching the r4 parent.

On the borrow → the genuinely-unclosed case is still protected (unbalanced → rejected → falls through, new test). The round-5 trailing-close fixture is structurally a single nested report, so I've accepted extracting it as one rather than reintroducing the template regression — this is the consistent choice your round-6 note points to.

Tests → replaced the commingle test with three guards: self-quote+nested at depth 1 and depth 2 (evidence + verdict; both fail on the r5 parent) and the genuinely-unclosed fall-through. Suite 29/0; monotonic-cursor perf retained.

This is the fifth response commit; I A/B'd your exact fixtures against the parent to make sure this converges on the realistic behavior rather than continuing the ping-pong. Ready for another look.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 7 — re-review of a46479ec (revert eager commingle-reject; restore depth-aware bound)

Re-reviewed the single new commit — the round-6 response that reverts the round-5 eager return $null gate and restores the round-4 depth-aware bound. Three independent reviewers, adversarial consensus; every claim was verified empirically in pwsh against the real functions (AST-extracted) and A/B'd against the parent 9f42284. This is the seventh review round on this function, and this commit directly undoes a change my own round-6 review asked for — so it got extra scrutiny. CI status is out of scope.

The round-6 regression is genuinely fixed — verified

The round-5 return $null gate mangled the common, production-shaped report. Confirmed against the parent by mutation testing: a well-formed report that self-quotes its marker inside its own <details> and then opens a nested evidence <details> (which the production template at lines 552–556 always does) lost its Evidence + Verdict at depth 1, and returned $null (the whole report dropped) at depth ≥ 2. Head keeps the whole report in both cases. The two new depth-1/depth-2 tests are non-vacuous mutation-killers (fail on parent, pass on head); full suite is 29/0 on head.

The revert re-admits the round-5 commingle — but it's the right tradeoff (3/3)

All three reviewers independently reproduced that the round-5 commingle fixture (unclosed Report ONE → full Report TWO → trailing unmatched </details>) now extracts ONE+TWO as a single over-captured report on head, where the parent returned a clean Report TWO. Two things make this acceptable rather than blocking:

  • It's over-capture, not new data loss. The only delta vs the parent is including Evidence ONE (a superset). FINAL CONTENT after the outer </details> is dropped by both parent and head, so this commit introduces no new trailing-content loss.
  • It is structurally unavoidable. The <details>/anchor depth traces of the legitimate round-6 report and the round-5 commingle fixture are byte-identical under the accepted grammar (OPEN → anchor@depth1 → OPEN → CLOSE → CLOSE). One reviewer proposed narrowing the gate using the ## Tests Failure Analysis heading as a distinguishing signal; it was rejected on cross-check: the production template (lines 552–556) emits the marker immediately followed by that heading, so a report self-quoting the exact template it was told to emit would carry marker + heading + nested <details> in its own body — and the heading-gate would false-reject it, re-creating the round-6 data-loss bug. No purely-structural per-<details>-open gate can separate "report self-quotes its marker then opens nested evidence" from a genuine sibling report. Keeping the common case whole clearly outweighs rejecting the rare, malformed over-capture.

Non-blocking follow-ups (inline)

  • ⚠️ Testing — the re-admitted commingle over-capture is now unpinned by any test; the replacement falls through … truly unclosed test is vacuous w.r.t. this delta (passes on both parent and head). Suggest a regression test that pins the intended single-report extraction of the rebalancing fixture.
  • 💡 Documentation — the depth-0 comment overstates the balance loop as a complete anti-borrow backstop; it only rejects the truly-unclosed subcase, not the accepted rebalancing-commingle case.

Verified clean

No dangling $siblingCrossedInsideDetails references; clean parse; earliest-wins plus the round-2/3/4 protections all still hold (a trailing structurally-valid duplicate does not displace the real report; a bare / fenced / 4-space-indented self-quote inside the report's own <details> is not truncated; CRLF, no-trailing-newline, and <details open> are all handled).


Methodology: 3 independent reviewers with adversarial consensus; findings verified empirically by A/B execution against the parent commit. "Reviewer 1/2/3" refers to the three independent passes. CI status intentionally out of scope.

Comment thread .github/scripts/Review-Tests.Tests.ps1
Comment thread .github/scripts/Review-Tests.ps1 Outdated
…review r7)

Round-7 review verified the round-6 revert fixes the regression and accepted the
re-admitted commingle as the right structural-identity tradeoff (3/3). Two
non-blocking follow-ups:

- Testing: the rebalancing-commingle over-capture (an earlier report rebalanced to
  depth 0 by a trailing </details>, extracted as a single superset report) was left
  unpinned when the round-5 test was removed. Added a regression test that feeds the
  rebalancing fixture and asserts the intended single-report extraction (Evidence ONE
  + Evidence TWO present; trailing FINAL CONTENT excluded on both parent and head, so
  no new loss). It fails on the r5 gate, so it guards against a future re-add of the
  eager rejection silently altering the accepted behavior.

- Documentation: reworded the depth-0 comment -- the balance loop is only a PARTIAL
  anti-borrow backstop (it rejects the truly-unclosed subcase; the rebalancing-
  commingle case is knowingly accepted as over-capture, the unavoidable cost of the
  structural-identity tradeoff).

Suite 30/0; no behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
Copilot AI review requested due to automatic review settings July 27, 2026 00:03
@kubaflo

kubaflo commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

@PureWeen — thanks for the round-7 sign-off on the tradeoff. Both non-blocking follow-ups are in 2f7de18d03d:

  • ⚠️ Testing → added a regression test that feeds the rebalancing-commingle fixture and pins the accepted single-report over-capture (Evidence ONE + TWO present; FINAL CONTENT excluded on both parent and head). It fails on the r5 gate (27/3), so the accepted behavior can't silently drift if the eager gate is ever re-added or the balance loop changes.
  • 💡 Documentation → reworded the depth-0 comment to say the balance loop is only a partial anti-borrow backstop — it rejects the truly-unclosed subcase; the rebalancing-commingle case is knowingly accepted as over-capture.

Suite 30/0, no behavior change. That closes out the two follow-ups from a review chain that ran seven rounds — appreciate the rigor throughout; the structural-identity conclusion is now both documented and pinned.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 8 — re-review of 2f7de18d (pin the accepted commingle over-capture; reword the borrow comment)

Re-reviewed the single new commit — the round-7 response that implements both of my round-7 non-blocking follow-ups: (1) a regression test pinning the knowingly-accepted rebalancing-commingle over-capture, and (2) rewording the depth-0 comment to scope the balance loop's anti-borrow protection as partial. Three independent reviewers, adversarial consensus. Because this commit directly implements changes my own prior review asked for, the Multi-Round Self-Correction rule applied (lowered threshold for any finding against it) and it got extra scrutiny — there is real precedent on this PR (b585fb1434) for a review-response commit introducing a bug. Every claim was verified empirically in pwsh against the AST-extracted functions and A/B'd against both the parent a46479ec and the reverted round-5 eager-reject gate (9f42284). CI status is out of scope.

No issues found in the new commit — verified (3/3)

  • The new test is a genuine mutation-killer, not a vacuous guard. On head the rebalancing fixture extracts the intended supersetEvidence ONE + Evidence TWO present, FINAL CONTENT after the outer </details> excluded (bounded, not merely absent from raw text). Run through the round-5 eager-reject gate (9f42284), the same fixture extracts only Report TWO, so Should -Match 'Evidence ONE' fails — the test genuinely catches a future re-add of the eager rejection, exactly as the commit claims.
  • It exercises a distinct shape, not a duplicate. The existing truly-unclosed fixture drops Evidence ONE (borrow rejected); the new rebalancing fixture keeps it (over-capture accepted) — opposite outcomes on the same marker.
  • The reworded comment is factually accurate against the code (docs-match-reality): truly-unclosed → rejected by the balance loop and falls through; trailing-close-rebalanced → knowingly accepted as over-capture. The tag sequence OPEN → anchor@depth1 → OPEN → CLOSE → CLOSE was confirmed identical to the legitimate depth-1 self-quote fixture, upholding the "structurally indistinguishable" claim.
  • Review-Tests.ps1 is provably comment-only. Comment-stripped / semantic-token diff vs the parent is zero — the if ($openDetailsDepth -eq 0 …) guard and the balance loop are byte-identical. No logic snuck in under a "comment reword" (no repeat of the b585fb1434 fail-open pattern).
  • Full suite 30/0 on head, matching the commit message.

No behavior change; the delta is a test + comment that correctly close out the two round-7 follow-ups.


Methodology: 3 independent reviewers with adversarial consensus; every claim verified empirically by A/B execution against the parent and the reverted r5 gate. "Reviewer 1/2/3" refers to the three independent passes. CI status intentionally out of scope.

Get-ValidatedAssetBranchState accepts a root tree without the
.review-tests-assets marker. That is deliberate, but the reason was only
recorded in a PR thread, so the same predicate has now drawn two
contradictory review findings -- one arguing it is too loose (marker not
enforced), one arguing it is too strict (over-blocking with no recovery).

Record the rationale next to the predicate: -AssetBranch is a fixed
constant no caller overrides, so branch identity comes from the ref name
rather than a marker blob, and a missing marker cannot produce the nested
.github/workflows/ path the workflow-scope 403 requires. Enforcing it
would yield no true positives while turning a deleted marker into a
lockout this script has no path to repair.

Comment-only; the comment-stripped diff is empty and both Pester suites
still pass 58/58.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 56ae58e8-a78b-4f24-9920-bc096dfb01fa
Copilot AI review requested due to automatic review settings July 29, 2026 15:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new

@PureWeen
PureWeen merged commit 724fbdb into main Jul 29, 2026
17 of 18 checks passed
@PureWeen
PureWeen deleted the fix/review-tests-asset-publishing branch July 29, 2026 18:44
@github-actions github-actions Bot added this to the .NET 10 SR10 milestone Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-ai-agents Copilot CLI agents, agent skills, AI-assisted development area-infrastructure CI, Maestro / Coherency, upstream dependencies/versions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants