test(safe-outputs): verify work item rendering fidelity end to end - #1974
Conversation
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
There was a problem hiding this comment.
Security review
This PR is primarily test coverage for Markdown→work-item rendering fidelity, but it also lands the larger sanitize.rs → allowlist-based sanitize/markdown.rs rewrite that's central to the XSS/injection trust boundary for create-work-item descriptions.
Reviewed the allowlist design (ALLOWED_TAGS, ALLOWED_URL_SCHEMES, scheme_allowed, escape_non_tag_markup, locate_destination/protectable_destination, unescape_ampersands) end to end:
- The switch from a blocklist (
is_dangerous_html_tag) to a closed HTML tag allowlist (ammonia::Builder) is a genuine hardening improvement — unrecognized tags are dropped by construction instead of enumerated. scheme_allowedcorrectly denies-by-default (onlyhttp/https/mailtopass), including disguised schemes with internal whitespace (java\tscript:), since only the outer string is trimmed before comparison.- The label/destination-confusion bug already flagged in this thread (
locate_destinationmatching the label text instead of the actual destination for payloads like[(redacted) is fixed in the current diff — the search now starts after the](/]:terminator, and the new tests (redacts_destination_when_the_label_repeats_it,redacts_reference_definition_when_the_label_repeats_it`) cover exactly that regression. unescape_ampersandsonly reverses a single&→&pass on text outside tags and leaves</tag boundaries alone, so it doesn't reopen a path to re-parse dropped markup as a tag.
No new injection or token-scope regression found in this diff. The two open threads from the earlier review pass (destination-matching and workItemTypeExists test coverage) both appear addressed by the referenced follow-up commit. Nothing else here looks merge-blocking from a security standpoint.
🔒 Security review by PR Security Reviewer · auto · 43.9 AIC · ⌖ 14.8 AIC · ⊞ 11.7K
Comment /review to run again
There was a problem hiding this comment.
TypeScript review (scripts/ado-script/)
Reviewed the executor-e2e additions: AdoRest.workItemTypeExists, the new rendering-fidelity scenarios in work-item.ts, and the shared JSON corpus.
- Error propagation is correct:
workItemTypeExistsonly swallows 404 viaallow404, other failures still throw with the HTTP status/body, and this is unit-tested (200/404/403) in the newado-rest.test.ts. - The prior review thread flagged the missing direct unit test for
workItemTypeExists— that's now resolved byado-rest.test.ts. renderingScenario'sassertrecordsstate.createdIdbefore any fallible check, so cleanup still runs on a golden mismatch — good defensive ordering.- Types are sound throughout (no
any, no unchecked casts);storedis narrowed with atypeofguard before use, and the optionalmultilineFieldsFormatis handled without a non-null assertion.
No merge-blocking issues found in this workspace. (A background TypeScript sub-agent pass returned no additional findings.)
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · auto · 56.2 AIC · ⌖ 14.2 AIC · ⊞ 11.4K
Comment /review to run again
There was a problem hiding this comment.
Solid rendering-fidelity coverage overall — the shared JSON corpus keeps the Rust golden and the executor E2E scenario from drifting, and the ordering of security-check-before-golden-check is well tested (leaked construct, mangled fenced code, non-Markdown format, mismatch all have dedicated cases).
Two prior findings on this PR (the locate_destination label-repeat bug and the missing workItemTypeExists unit test) are already confirmed fixed in later commits — nice.
Three smaller gaps in the new test coverage, none blocking:
- The Bug scenario (
createBugWorkItemRendering) is only exercised on its failure path (missing-field error) — there's no test proving a successful Bug pass withMicrosoft.VSTS.TCM.ReproStepscorrectly populated. - The
(redacted) URL leak branch inassert(separate from theDENIED_CONSTRUCTS` loop) has no dedicated test. - The new
cleanupfallback (state.createdId ?? findWorkItemByTitle(...)) — the exact scenario these tests are designed around (assert throws before recording the id) — is itself untested.
None of these are regressions or weakened assertions; they're coverage gaps in otherwise well-structured new tests.
🧪 Test quality analysis by Test Quality Sentinel · auto · 133.4 AIC · ⌖ 2.04 AIC · ⊞ 11.3K
Comment /review to run again
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c8e5f36-958b-48ea-99cd-d7c14b5eb8da
|
/review |
|
✅ Test Quality Sentinel completed the test quality analysis.
|
There was a problem hiding this comment.
Test-only PR (executor-E2E + Rust golden coverage for sanitize_markdown rendering fidelity). Checked for the usual drift classes:
- AGENTS.md: updated to add the new
src/sanitize/markdown.rsmodule — in sync. - docs/safe-outputs.md: new Rendering fidelity section documents the two new work-item scenarios and the shared corpus/goldens file — in sync.
- Codegen: no changes to
filter_ir.rs/Factenum, sotypes.gen.ts/fact-catalog.gen.jsoncorrectly untouched. - Lock files: no
.github/workflows/*.mdchanged, so no.lock.ymldrift. scripts/ado-script/*.jsbundles: absent from the file list as expected (generated, gitignored) — not a finding.- Newtypes /
src/secure.rs: no newParamsidentifier fields introduced;create_work_item.rschange is test-only (wiremock assertions), no new rawStringpath/ref fields. - Shared corpus single-source-of-truth:
markdown-rendering-corpus.jsonisinclude_str!d by Rust and imported by the TS harness, so a golden edit cannot drift between the two layers — good design.
No contract violations found. No blocking issues.
🏗️ Compiler contract review by Compiler Contract Reviewer · auto · 25.1 AIC · ⌖ 3.09 AIC · ⊞ 12.8K
Comment /review to run again
There was a problem hiding this comment.
TypeScript review
Reviewed the executor-e2e additions (ado-rest.ts, work-item.ts, and their tests) for the markdown rendering-fidelity coverage.
Both prior review findings on this diff — the locate_destination search-span bug in src/sanitize/markdown.rs and the missing unit test for workItemTypeExists — are already fixed in follow-up commits (8d0f6f0 per the review thread). No new async/error-handling defects, type-safety holes, or secret-leakage issues found in the new TypeScript: workItemTypeExists/getWorkItem correctly treat multilineFieldsFormat as optional, external JSON payloads are guarded with typeof checks before use, and the rendering scenario propagates errors with clear context. No blocking issues.
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · auto · 42 AIC · ⌖ 2.46 AIC · ⊞ 11.4K
Comment /review to run again
There was a problem hiding this comment.
This is a security-focused review of a Markdown sanitizer rewrite plus test coverage additions.
Summary
src/sanitize/markdown.rs replaces the previous regex-based HTML denylist (neutralize_dangerous_html, sanitize_url_protocols, event-handler stripping) with a structural, allowlist-based design: pulldown_cmark identifies code spans/fences/autolinks and link/image destinations, and ammonia cleans everything else against a closed tag/attribute allowlist plus a URL-scheme allowlist (http, https, mailto). This is a net security improvement over the prior enumerate-the-bad-patterns approach, since allowlisting is closed by construction rather than requiring every new evasion to be special-cased.
Prior findings already resolved
Both issues raised in earlier review passes on this PR are confirmed fixed in the current diff:
locate_destinationmatching the label instead of the actual destination for repeated-text payloads (e.g.[(redacted) — fixed by searching only after the label terminator (](/]:), verified by the newredacts_destination_when_the_label_repeats_itandredacts_reference_definition_when_the_label_repeats_it` tests.workItemTypeExistslacking direct unit coverage — addressed withado-rest.test.tsstubbingfetchfor 200/404/403.
Review of the new sanitizer
I checked the allowlist config (CLEANER in markdown.rs) for privilege leaks:
- No scripting tags (
script,iframe,object,embed,svg, event-handler attributes) are inALLOWED_TAGS/tag_attributes()—clean_content_tagsalso drops the content of strippedscript/styleelements rather than leaving it as escaped text. aonly carrieshref; notarget/rel, solink_rel(None)does not reintroduce a tabnabbing vector.imgonly carriessrc/alt/width/height— noonerror/onloadreachable regardless of whatplausible_attributes()(a heuristic only used to decide tag-vs-prose escaping, not a security gate) considers plausible.scheme_allowed()denies by default and correctly treats(host/redacted)as scheme-less (relative), which is consistent with existing behavior and not a new open redirect since no scheme-based execution is possible there.- The redaction path only strips the destination, keeping nested elements independently checked, and the whole element is redacted when the destination cannot be located deterministically (fail-closed).
Verdict
This diff does not weaken the security posture — it is a like-for-like (and more robust) reimplementation of sanitize_markdown, is exercised by a large new golden-corpus test suite (markdown-rendering-corpus.json + Rust/TS tests), and both previously flagged issues are fixed. No new merge-blocking findings.
🔒 Security review by PR Security Reviewer · auto · 59.2 AIC · ⌖ 2 AIC · ⊞ 11.7K
Comment /review to run again
There was a problem hiding this comment.
Rust review — no new merge-blocking issues
Reviewed the new src/sanitize/markdown.rs module (allowlist-based Markdown/HTML sanitizer replacing the old regex blocklist) and the sanitize.rs diff. Checked char-boundary safety of bounded(), starts_html_tag, plausible_attributes, mark_blockquotes, unescape_ampersands, and the placeholder restore() round trip against adversarial multi-byte/UTF-8 input, and the deny-by-default scheme_allowed scheme check — no panics or bypasses found. The locate_destination label-repetition bug flagged in an earlier review round is already fixed (commit 8d0f6f0) and the corresponding test (redacts_destination_when_the_label_repeats_it) covers it. The rust-critic sub-agent returned no findings after a full pass on this diff.
No additional Rust-quality concerns to raise; deferring to the Compiler Contract and Security reviewers for anything ado-aw-specific.
🦀 Rust code quality review by Rust Code Quality Reviewer · auto · 52.8 AIC · ⌖ 8.85 AIC · ⊞ 11.5K
Comment /review to run again
There was a problem hiding this comment.
Solid test additions overall — the shared corpus mechanism (Rust include_str! + TS import) is a good way to prevent the two boundaries drifting, and the golden/security/format assertion ordering is well thought out with dedicated tests for each failure mode. Two coverage gaps worth closing before merge:
- The security check in
renderingScenario.asserthas two independent leak-detection branches (DENIED_CONSTRUCTSarray vs. the separate(redacted) check), but only one construct (<script`) is ever exercised by a test — a regression in the other three paths would ship silently. - The happy-path Markdown-format test asserts against a lowercased
"markdown"fixture rather than the"Markdown"value the executor actually writes, so the realistic value is never exercised.
Both are inline. Nothing merge-blocking.
🧪 Test quality analysis by Test Quality Sentinel · auto · 73 AIC · ⌖ 2.28 AIC · ⊞ 11.3K
Comment /review to run again
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c8e5f36-958b-48ea-99cd-d7c14b5eb8da
|
/review |
|
✅ Rust Code Quality Reviewer completed the Rust code quality review.
|
There was a problem hiding this comment.
TypeScript review (scripts/ado-script)
Reviewed the new AdoRest.workItemTypeExists helper, the JSON-corpus-driven rendering scenarios in work-item.ts, and their tests. No unhandled rejections, swallowed errors, unsafe any/casts on external data, or secret-leak risks found — all async paths are awaited/propagated correctly, and the new REST call uses the existing allow404 pattern consistently with the rest of the file. The one pre-existing gap (workItemTypeExists lacking a direct unit test outside the mocked scenario context) is already covered by an earlier review comment on this PR, so not duplicating it. No merge-blocking issues.
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · auto · 45.5 AIC · ⌖ 3.85 AIC · ⊞ 11.4K
Comment /review to run again
There was a problem hiding this comment.
Test Quality Review
Strong test suite overall — this is exactly the kind of coverage a rendering-fidelity regression needs.
Strengths:
- The shared corpus (
markdown-rendering-corpus.json) as single source of truth between the Rust golden test and the TS executor E2E is a solid design — a rendering change can't drift between the two layers. - Good failure-path coverage in
work-item-scenarios.test.ts: format mismatch, denied-construct leakage (script/onerror/iframe/javascript-URL, each parametrized), Bug repro-steps field name mismatch, and the ADO-golden-mismatch-after-createdId-recorded ordering (important for cleanup correctness). src/sanitize/markdown.rstests are behavioral, not implementation-mirroring: they assert on rendered output for real attack patterns (folded tags, entity-encoded schemes, nested destinations, forged sentinels) rather than internal parser state. The idempotency and bounded-scan (linear time on adversarial input) tests are good defenses against regressions in a security-sensitive rewrite.ado-rest.test.tscovers the newworkItemTypeExistshappy/404/403 paths, addressing the gap flagged in an earlier review round.- The
sanitize.rstest diff removing old blocklist-specific tests is expected churn from the allowlist rewrite, not a weakening — assertions were adapted to the new tag-allowlist semantics, not silently dropped.
One gap found: see inline comment on work-item.ts — the format === undefined fallback branch (org doesn't surface multilineFieldsFormat) has no test distinguishing it from the "format is defined but wrong" failure case.
Non-blocking overall.
🧪 Test quality analysis by Test Quality Sentinel · auto · 80.2 AIC · ⌖ 2.23 AIC · ⊞ 11.3K
Comment /review to run again
There was a problem hiding this comment.
Security review - no diff-level regressions found
This diff is test-only in production impact: it adds a Markdown-aware sanitizer rewrite (src/sanitize/markdown.rs) with an allowlist-based HTML/URL scheme policy, plus a shared rendering corpus and executor-e2e/Rust golden tests for create-work-item's description field. I traced the two threat-model areas most relevant to this PR:
- Injection into generated pipelines: not touched by this diff - no new bash:/##vso[/template-expression sinks.
- Token scope: ado-rest.ts's new workItemTypeExists reuses the existing authenticated request() helper; no new token/secret paths.
- URL scheme / HTML allowlist correctness: the two prior review findings on this PR (destination-lookup anchoring bypass in locate_destination, and missing unit coverage for workItemTypeExists) are both already fixed and confirmed in the current HEAD (commits 8d0f6f0 and later) - locate_destination now searches after the label terminator, and ado-rest.test.ts covers 200/404/403 cases.
- Reviewed scheme_allowed, starts_html_tag, plausible_attributes, and the placeholder/sentinel scheme for smuggling/redaction bypass: sentinels are stripped from input before use, redaction happens before the HTML cleaner runs, and destinations carrying angle brackets fall through to the allowlist rather than being copied verbatim - no bypass found.
No new merge-blocking findings. Good test-coverage work tying the Rust golden and executor-e2e ADO round-trip goldens to one shared corpus file.
🔒 Security review by PR Security Reviewer · auto · 57.5 AIC · ⌖ 3.08 AIC · ⊞ 11.7K
Comment /review to run again
There was a problem hiding this comment.
Rust review — no merge-blocking issues
Scope: src/sanitize/markdown.rs (new), src/sanitize.rs, src/safe_outputs/create_work_item.rs (test-only addition).
The rust-critic sub-agent produced no findings after two turns (idle both times, no JSONL output) — noting per the contract rather than treating silence as a pass.
My own pass over the new markdown sanitizer found no unaddressed correctness or error-handling defects. locate_destination mislocating a destination for nested-link source spans — the one real bug in this area — was already flagged in a prior review round and is confirmed fixed in 8d0f6f0. The create_work_item.rs change is test-only. Error handling in the new module is sound (unwrap/expect are confined to the #[cfg(test)] corpus loader); Option-returning helpers (locate_destination, protectable_destination) degrade safely on miss per their doc comments, and the module is exercised by an extensive adversarial/idempotency test suite plus a shared golden corpus.
No inline comments to post.
🦀 Rust code quality review by Rust Code Quality Reviewer · auto · 76.7 AIC · ⌖ 2.62 AIC · ⊞ 11.5K
Comment /review to run again
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c8e5f36-958b-48ea-99cd-d7c14b5eb8da
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c8e5f36-958b-48ea-99cd-d7c14b5eb8da
|
/review |
|
✅ Rust Code Quality Reviewer completed the Rust code quality review.
|
There was a problem hiding this comment.
Reviewed the TypeScript changes in scripts/ado-script/executor-e2e/ (ado-rest.ts, work-item.ts, and the new unit tests). Async/error handling is sound: workItemTypeExists correctly distinguishes 404→false from other failures (still throws), all scenario callbacks are properly awaited, and the security/format/golden assertions in renderingScenario fail loudly with actionable messages rather than swallowing errors. No unhandled rejections, no any leakage, and no secrets touch these code paths.
The only pattern worth noting (not new to this PR) is that AdoRest.request<T>() casts the parsed JSON body to T without runtime shape validation — workItemTypeExists inherits that from the shared helper along with every other method in the class, so it isn't a regression introduced here.
Prior review feedback on this file set (the locate_destination match-span bug and the missing workItemTypeExists unit test) has already been addressed in this PR's history. No new blocking issues found.
🟦 TypeScript code quality review by TypeScript Code Quality Reviewer · auto · 50 AIC · ⌖ 2.19 AIC · ⊞ 11.4K
Comment /review to run again
There was a problem hiding this comment.
Security review: no regressions found
This PR (test(safe-outputs): verify work item rendering fidelity end to end) is test-only. It adds:
- a shared rendering-fidelity corpus consumed by both Rust unit tests and the executor E2E
create-work-item-rendering(-bug)scenarios, - a
workItemTypeExistshelper andmultilineFieldsFormatfield ongetWorkItemfor assertions, - a wiremock test pinning the
create-work-itemPATCH body (title + sanitized description +multilineFieldsFormat=Markdown).
The src/sanitize.rs diff shown is the prior allowlist-based rewrite of sanitize_markdown (already reviewed/fixed in earlier commits per pr-review-comments.json), not new production logic in this PR. The new ALLOWED_TAGS/ALLOWED_URL_SCHEMES/KNOWN_TAGS policy in src/sanitize/markdown.rs is deny-by-default (fixed tag allowlist via ammonia, http/https/mailto only for URLs, script/style content stripped), and locate_destination searches only after the label terminator, consistent with the fix already applied for the prior finding on this line.
No injection into generated pipeline YAML, no token-scope changes, no path-traversal-relevant code, and no weakened validation — this diff is security-neutral.
🔒 Security review by PR Security Reviewer · auto · 39.7 AIC · ⌖ 4.41 AIC · ⊞ 11.7K
Comment /review to run again
There was a problem hiding this comment.
Rust review — no merge-blocking issues found
Reviewed the Rust portion of this PR: src/sanitize.rs, the new src/sanitize/markdown.rs (allowlist-based Markdown/HTML sanitizer), and the wiremock test added to src/safe_outputs/create_work_item.rs.
Note on process: the rust-critic sub-agent was launched in the background and given a blocking wait (150s) plus one follow-up nudge, but returned no parseable JSONL findings either time. Its output is discarded per the sub-agent contract; the assessment below is my own pass only.
Findings: none rise to merge-blocking. The two prior review threads on locate_destination (destination-search starting point) and workItemTypeExists (missing direct unit test) are already resolved per the fix commits referenced in the existing PR comments, and the fixes are reflected in this diff (search now starts after ](/]:, and a dedicated ado-rest.test.ts was added).
Spot-checked for common defect classes and found the new module handles them correctly:
- Range arithmetic in
protectable_destination(range.start - 1) is guarded by short-circuit evaluation on an empty-stringends_withcheck, so no underflow panic is reachable. bounded()'s char-boundary walk-back cannot underflow past 0 since byte offset 0 is always a valid boundary.- The allowlist-first HTML design (closed by construction) is a solid improvement over the previous denylist, and the idempotency test (
is_idempotent) plus the shared rendering-corpus golden test give good regression coverage for a security-sensitive sanitizer.
No inline comments to add.
🦀 Rust code quality review by Rust Code Quality Reviewer · auto · 57.7 AIC · ⌖ 2.79 AIC · ⊞ 11.5K
Comment /review to run again
There was a problem hiding this comment.
Overall solid test coverage for the new rendering-fidelity path (shared corpus golden test in Rust, wiremock pin on the executor patch, and layered security/format/byte-equality assertions in the E2E scenario). One gap: the Bug-type test only exercises the error path for Microsoft.VSTS.TCM.ReproSteps, never the success path — see inline comment.
🧪 Test quality analysis by Test Quality Sentinel · auto · 58.8 AIC · ⌖ 2.33 AIC · ⊞ 11.3K
Comment /review to run again
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c8e5f36-958b-48ea-99cd-d7c14b5eb8da
Summary
The Markdown sanitizer rewrite changes what ends up in a work item description, but nothing asserted the rendered result —
scenarios/work-item.tsonly checked title and assignee, so a rendering regression would ship silently.sanitize_markdownreaches ADO through exactly one path:create-work-item's description, written toSystem.Description(orMicrosoft.VSTS.TCM.ReproStepsfor Bugs) with a/multilineFieldsFormat/<field>=Markdownpatch. This adds coverage for that path at both the sub-second and against-real-ADO levels.Shared corpus (single source of truth)
scripts/ado-script/src/executor-e2e/scenarios/markdown-rendering-corpus.jsonholdsinput(headings, nested lists, GFM + HTML tables withcolspan,<details>/<summary>,<kbd>/<sub>/<sup>, autolink, image, and denied constructs interleaved with their fenced-code twins) andexpected(the sanitized golden), as line arrays.include_str!s it; the harness imports it. A deliberate rendering change means editingexpectedin one file — the two layers cannot drift.Rust
src/sanitize/markdown.rs:rendering_corpustest module + golden test assertingsanitize_markdown(input) == expected.src/safe_outputs/create_work_item.rs: wiremock test pinning the default path — the patch carries the sanitized golden and/multilineFieldsFormat/System.Description = Markdown.Executor E2E
create-work-item-rendering(Task) andcreate-work-item-rendering-bug(Bug) propose the raw corpus, then assert on the stored field:<script/onerror=/<iframe/javascript:outside fenced code, while the fenced twins survive verbatim;multilineFieldsFormat: Markdown;AdoRest.getWorkItemnow surfacesmultilineFieldsFormat; newworkItemTypeExistslets the Bug scenarioSkipErrorwhere the type is undefined.Deviation worth reviewing: ADO does not reliably surface
multilineFieldsFormaton read and I could not probe AgentPlayground from the sandbox, so assertion (2) is conditional — it fails when the value is present and notMarkdown, and logs a note when absent, with the patch itself pinned by the new Rust test. This is the fallback the plan allowed; if the field does come back on AgentPlayground, the conditional should be tightened to a hard requirement. The optional comment-based renderer probe was skipped, since comments go throughsanitize_text, not the changed code.Test plan
cargo test --all-targets(new golden + executor patch tests included)npx vitest runinscripts/ado-script— 1149 tests, including new coverage for golden mismatch, non-Markdown format, leaked construct, mangled fenced code, and the Bug-type skipnpm run typecheck;cargo clippy --all-targetsshows only a pre-existing warningallScenarios); no pipeline change needed and they have not been exercised against ADO from here