feat(compile): replace Python gate evaluator with bundled TypeScript gate.js#389
Conversation
🔍 Rust PR ReviewSummary: Looks good overall — solid migration with good test coverage and a clean codegen pipeline. Two actionable issues worth addressing before merge, plus one stale doc string. Findings🐛 Bugs / Logic Issues
|
🔍 Rust PR ReviewSummary: Looks good overall — well-structured migration with solid test coverage and drift prevention. Three issues worth addressing before merge. Findings🐛 Bugs / Logic Issues
|
…ed unknown predicates Closes review findings on #389: 1. FACT_DEPS drift gap: the fact dependency graph in policy.ts was a manually-maintained mirror of Fact::dependencies() in Rust, outside the schema drift check. Added `dependencies: Vec<String>` to FactSpec populated from Fact::dependencies(). Regenerated types.gen.ts. PolicyTracker now reads the dep graph from the spec at construction; the hardcoded FACT_DEPS constant is removed. The CI `git diff --exit-code` on types.gen.ts now covers the dep graph too. 2. Unknown predicate silent pass: the default arm of evaluatePredicate returned true (auto-pass), so a stale gate.js paired with a newer compiler that emits a new predicate would silently pass filters. Now: emits a task.logissue type=warning naming the unknown type and returns false (fail-closed). Added a regression test. 3. Stale "Python" doc on GateSpec: updated to "Node gate evaluator (scripts/gate.js)". Propagates through codegen. 4. scripts.zip ships TS source: added -x patterns for ado-script/src/, test/, package*.json, tsconfig.json, vitest.config*.ts, README.md, and .gitignore. Only gate.js (already at scripts/gate.js) and any future bundle artefacts are shipped. 5. globMatch bracket divergence: documented inline that bracket expressions [seq] are escaped to literal characters (Python fnmatch supported them). The IR currently never emits bracket patterns; if a future predicate needs them, the builder must be extended. 6. release.yml missing npm cache: added cache: "npm" and cache-dependency-path to match the CI workflow. Avoids re-downloading node_modules on every release run. 7. Fact::is_pipeline_var() #[cfg(test)] opacity: added a doc comment noting the runtime mirror is isPipelineVarFact in env-facts.ts, so future readers don't wonder why the helper exists only for tests. Validation: 172/172 vitest tests, full cargo test suite, clippy clean, e2e gate test passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Looks good — clean migration with solid test coverage. A few stale docstrings and one ignored test writing to a now-deleted file are worth cleaning up. Findings
|
🔍 Rust PR ReviewSummary: Solid migration overall — the architecture is clean and the 45-case parity inventory gives good confidence. Two issues worth addressing before merge. Findings🐛 Bugs / Logic Issues
|
7269ccf to
5959433
Compare
🔍 Rust PR ReviewSummary: Solid migration — architecture is well-designed and the security-sensitive paths are handled carefully. Two items worth addressing before merge. Findings🐛 Bugs / Logic Issues
|
🔍 Rust PR ReviewSummary: Looks good overall — clean migration with solid test coverage, good security posture, and a clever drift-prevention mechanism. A couple of minor items below. Findings
|
|
@copilot rebadge scripts.zip as ado-script. Just directly upload the build ado-script folder |
Done in cee11de. Release now uploads Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
@copilot can we just upload the dist folder directly as ado-script? Say no if it is a bad idea. Zip is fine. |
No—keep the zip. GitHub release assets are files, not directories, so we can’t upload a raw folder as an asset. |
…ed unknown predicates Closes review findings on #389: 1. FACT_DEPS drift gap: the fact dependency graph in policy.ts was a manually-maintained mirror of Fact::dependencies() in Rust, outside the schema drift check. Added `dependencies: Vec<String>` to FactSpec populated from Fact::dependencies(). Regenerated types.gen.ts. PolicyTracker now reads the dep graph from the spec at construction; the hardcoded FACT_DEPS constant is removed. The CI `git diff --exit-code` on types.gen.ts now covers the dep graph too. 2. Unknown predicate silent pass: the default arm of evaluatePredicate returned true (auto-pass), so a stale gate.js paired with a newer compiler that emits a new predicate would silently pass filters. Now: emits a task.logissue type=warning naming the unknown type and returns false (fail-closed). Added a regression test. 3. Stale "Python" doc on GateSpec: updated to "Node gate evaluator (scripts/gate.js)". Propagates through codegen. 4. scripts.zip ships TS source: added -x patterns for ado-script/src/, test/, package*.json, tsconfig.json, vitest.config*.ts, README.md, and .gitignore. Only gate.js (already at scripts/gate.js) and any future bundle artefacts are shipped. 5. globMatch bracket divergence: documented inline that bracket expressions [seq] are escaped to literal characters (Python fnmatch supported them). The IR currently never emits bracket patterns; if a future predicate needs them, the builder must be extended. 6. release.yml missing npm cache: added cache: "npm" and cache-dependency-path to match the CI workflow. Avoids re-downloading node_modules on every release run. 7. Fact::is_pipeline_var() #[cfg(test)] opacity: added a doc comment noting the runtime mirror is isPipelineVarFact in env-facts.ts, so future readers don't wonder why the helper exists only for tests. Validation: 172/172 vitest tests, full cargo test suite, clippy clean, e2e gate test passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cee11de to
1534284
Compare
🔍 Rust PR ReviewSummary: Looks good overall — clean architecture with strong drift prevention, but a few items worth addressing before merge. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
…gate.js Migrates the trigger-filter gate evaluator from `scripts/gate-eval.py` to a bundled TypeScript artifact `scripts/gate.js` produced by a new `scripts/ado-script/` workspace. The compiler now emits a `NodeTool@0` install step and `node /tmp/ado-aw-scripts/gate.js` instead of `python3`. Architecture (variant A2 in the design walkthrough): - TypeScript workspace at scripts/ado-script/ built with @vercel/ncc. - shared/ modules (auth, ado-client, env-facts, policy state machine, vso-logger) reusable across future bundles. - gate/ entry implementing bypass, fact acquisition, 11 predicate evaluators, and self-cancel — full parity with the deleted Python evaluator (45/45 tests ported, +6 parity guards). Drift-proof codegen: - New hidden CLI subcommand `ado-aw export-gate-schema` emits a schemars-derived JSON Schema from the Rust IR types. - `npm run codegen` chains it through json-schema-to-typescript to produce src/shared/types.gen.ts. - New CI workflow .github/workflows/ado-script.yml runs codegen + `git diff --exit-code` to fail on IR/TS schema drift. Pipeline integration: - TriggerFiltersExtension prepends a NodeTool@0 step pinned to 20.x. - compile_gate_step_external invokes `node` instead of `python3`. - release.yml builds the bundle (npm ci && npm run build) before zipping scripts/, and excludes node_modules/dist/schema from the zip. - New tests/gate_e2e.rs (#[ignore]'d) compiles a real agent, extracts GATE_SPEC, runs gate.js end-to-end, and asserts SHOULD_RUN. - compiler_tests.rs assertions tightened: now check for `node '/tmp/ado-aw-scripts/gate.js'` instead of the loose `python3` match (which falsely passed via base.yml's mcpg-config validation). Cleanup: - Deleted scripts/gate-eval.py, scripts/gate-spec.schema.json, tests/gate_eval_tests.py. Documentation: - New docs/ado-script.md records the A2 decision, codegen pipeline, bundle-size budget (5 MB; gate.js is ~1.1 MB), and how to add new internal bundles (e.g. poll.js). - docs/filter-ir.md rewritten: Node evaluator, NodeTool@0 step, scripts.zip distribution. - AGENTS.md tree + tech stack updated; new entry in docs index. - ado-script-design.md added at repo root as the design walkthrough that produced the A2 decision. Validation: - 173/173 vitest tests pass (45 ports + 6 parity guards + smoke + shared-module units). - Full cargo test suite green. - cargo clippy --all-targets --all-features clean. - E2E: `cd scripts/ado-script && npm run build && cd ../.. && cargo test --test gate_e2e -- --ignored` passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ate shared tests - Add vitest.config.ts with `include: ["src/**/*.test.ts"]` so the default `npm test` no longer picks up test/smoke.test.ts (which requires the ncc bundle to exist and was failing in CI before the build step ran). - Add vitest.config.smoke.ts targeting test/ for the smoke run. - Wire `npm run test:smoke` and the CI Smoke-test step to use the new smoke config (`vitest run -c vitest.config.smoke.ts`). - Move shared module tests under src/shared/__tests__/ to mirror the layout used by src/gate/__tests__/. Update relative imports (./foo.js → ../foo.js) and the vi.mock path in ado-client.test.ts. Validation: - `npm test` → 171/171 ✅ (smoke excluded) - `npx vitest run -c vitest.config.smoke.ts` after `npm run build` → 2/2 ✅ - `npm run typecheck` ✅ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ed unknown predicates Closes review findings on #389: 1. FACT_DEPS drift gap: the fact dependency graph in policy.ts was a manually-maintained mirror of Fact::dependencies() in Rust, outside the schema drift check. Added `dependencies: Vec<String>` to FactSpec populated from Fact::dependencies(). Regenerated types.gen.ts. PolicyTracker now reads the dep graph from the spec at construction; the hardcoded FACT_DEPS constant is removed. The CI `git diff --exit-code` on types.gen.ts now covers the dep graph too. 2. Unknown predicate silent pass: the default arm of evaluatePredicate returned true (auto-pass), so a stale gate.js paired with a newer compiler that emits a new predicate would silently pass filters. Now: emits a task.logissue type=warning naming the unknown type and returns false (fail-closed). Added a regression test. 3. Stale "Python" doc on GateSpec: updated to "Node gate evaluator (scripts/gate.js)". Propagates through codegen. 4. scripts.zip ships TS source: added -x patterns for ado-script/src/, test/, package*.json, tsconfig.json, vitest.config*.ts, README.md, and .gitignore. Only gate.js (already at scripts/gate.js) and any future bundle artefacts are shipped. 5. globMatch bracket divergence: documented inline that bracket expressions [seq] are escaped to literal characters (Python fnmatch supported them). The IR currently never emits bracket patterns; if a future predicate needs them, the builder must be extended. 6. release.yml missing npm cache: added cache: "npm" and cache-dependency-path to match the CI workflow. Avoids re-downloading node_modules on every release run. 7. Fact::is_pipeline_var() #[cfg(test)] opacity: added a doc comment noting the runtime mirror is isPipelineVarFact in env-facts.ts, so future readers don't wonder why the helper exists only for tests. Validation: 172/172 vitest tests, full cargo test suite, clippy clean, e2e gate test passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update stale doc comments referencing Python evaluator to TypeScript. Fix fnmatch reference in filter-ir.md to describe custom glob semantics. Hoist toLowerCase() map outside includes() for value_in_set/value_not_in_set predicates to avoid per-call array allocation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use escapeMessage instead of escapeProperty for setOutput value — the value is in the message body (after closing ]) where ] and ; are not escaped. Also add trigger_filters.rs to ado-script workflow paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update test_write_schema_to_scripts to write to the canonical scripts/ado-script/schema/ path instead of the deleted scripts/gate-spec.schema.json location. - Convert NodeTool@0 step from escaped single-line string to a raw string literal, consistent with the adjacent download step. - Add logWarning guard in globMatch when pattern contains "[" to catch compiler/evaluator parity drift early (bracket expressions are treated as literals, not character classes). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/eb18237a-9266-4098-bb63-db9816eb03fd Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
…agination, timeouts) Addresses every blocker and should-fix item from the deep code review of the new scripts/ado-script/ TypeScript workspace. Blocking - vitest 2.1.9 -> 4.1.6: clears all 5 moderate-severity npm audit findings (vitest / vite / esbuild / @vitest/mocker / vite-node). No test API changes needed -- all 172 existing tests pass unmodified on Vitest 4. - GATE_SPEC DoS guard (src/gate/index.ts): cap base64-decoded spec at 256 KiB before JSON.parse. ADO env vars are ~32 KiB max so this is two orders of magnitude above any realistic spec. Hardening - Glob ReDoS in-place hardening (src/gate/predicates.ts): - cap pattern length at 1024 chars - cap '*' wildcards at 64 per pattern - pre-compile RegExp cache (Map) with 1024-entry FIFO eviction - rejected patterns log a warning + return false (fail-closed) - + 3 new vitest cases - Pre-flight predicate-tree validator (validatePredicateTree, called from index.ts main right after JSON.parse): walks the full tree and throws on any unknown 'type' discriminant *before* fact acquisition. Closes the gap where an unknown predicate was only surfaced when evaluatePredicate reached it -- if the required fact was unavailable, the unknown type was silently skipped. + 7 new vitest cases covering each known type, nested unknowns under and/or/not, missing operands/operand. ADO REST client (src/shared/ado-client.ts) - 30-second per-attempt timeout via Promise + setTimeout (configurable via ADO_API_TIMEOUT_MS env var); timeouts are treated as transient so the existing one-shot retry path catches them. + 2 new vitest cases. - getIterationChanges now paginates via 'top'/'skip' (page size 100, 100- page cap, 'short page' termination); + 2 new vitest cases. The SDK does not paginate getPullRequestIterations (ADO REST API returns all in one call) so that one is documented but left as-is. Operational - Lazy import of azure-devops-node-api in src/shared/auth.ts (dynamic import inside getWebApi). Entry-point bundle dropped from ~1.1 MB to 78 KB; the SDK is now in a separate 2.7 MB ncc chunk loaded only when an ADO API call is made. Bypass-only / pipeline-var-only runs save the cold-start tax. 'zip -r ado-script/dist' in release.yml continues to package both chunks. - ado-script CI workflow now also runs on push to main, not only PR, so any drift in types.gen.ts that slips through (e.g. from a force-push or bypassed CI) is caught loudly the moment it lands on main. - New ADO_API_TIMEOUT_MS env var documented in docs/ado-script.md. Test counts: 186 vitest (was 172), 1519 cargo unit. cargo clippy clean. npm audit clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4a3f999 to
91d17af
Compare
🔍 Rust PR ReviewSummary: Well-engineered migration — the TypeScript port is clean and the Rust side is surgical. Two actionable items below, both low-to-medium severity. Findings
|
Consolidates the two latest Rust PR Reviewer rounds plus follow-up
architecture-review findings. All changes scoped to scripts/ado-script/
TS workspace and the Rust trigger-filters extension; no behavioural
regressions; 191 vitest (was 186) and 1585 cargo unit tests pass.
TypeScript hardening
- BRANCH_FACTS now lives in src/shared/env-facts.ts and is imported by
src/gate/predicates.ts. The previous duplicate const was a drift
hazard: a new branch-shaped fact added to env-facts.ts without a
mirror in predicates.ts would silently leave glob patterns un-stripped.
- src/shared/vso-logger.ts: escapeProperty now also encodes '=' and ' '
so adversarial property values cannot smuggle a new key into the
command header. Added logInfo() for non-vso lines that escapes
embedded newlines AND a leading '#' (ADO interprets '##vso[' only at
line-start, so neutralising a leading '#' is sufficient).
- src/gate/bypass.ts: replaced plain process.stdout.write of the
bypass log line with logInfo() so adversarial bypass_label cannot
inject ADO commands.
- src/gate/vso-logger.ts: complete() is now idempotent. ADO's
behaviour on double-complete is undefined; first call wins. Exposes
_resetCompletedForTesting() for test teardown.
- src/gate/predicates.ts numeric_range: removed the silent 'undefined
-> 0' coercion (Number(... ?? 0)) which would have caused a missing
fact to pass any range that includes 0 if the PolicyTracker gate
were ever loosened. Fail-closed on missing/non-finite values now.
- src/gate/predicates.ts globRegexCache: documented that the cache key
is the pattern alone and assumes fixed flags ('s'). Any future
flag-varying caller must extend the key.
- src/gate/index.ts: documented why pre-flight validation runs BEFORE
runBypass (fail-fast on bad specs even for Manual builds).
Rust side
- src/compile/extensions/trigger_filters.rs: added timeoutInMinutes: 5
to both injected steps (NodeTool@0 install and curl+unzip download)
so a hung cold-image install can't stall the pipeline indefinitely.
- Same file: unzip now passes -d /tmp/ado-aw-scripts/ explicitly as a
belt-and-suspenders zip-slip defense-in-depth on top of the existing
sha256 verification. (Test asserts the explicit target dir.)
- src/compile/filter_ir.rs: Fact::ado_exports() now documents that the
TS-side ENV_BY_FACT + FactKind in scripts/ado-script/src/shared/
env-facts.ts must be updated in lockstep. The types.gen.ts codegen
drift-check only covers GateSpec shape, not the env-var mapping —
so this is the manual contract.
- src/compile/filter_ir.rs Severity::Info: replaced the bare
#[allow(dead_code)] with a comment that matches the existing
Predicate::And/Or/Not "reserved for future" pattern.
Tests
- src/gate/predicates.test.ts: new 'KNOWN_PREDICATE_TYPES stays in
sync with evaluatePredicate switch' drift guard — constructs every
known predicate type, runs it through the evaluator, and asserts
the 'Unknown predicate type' warning never fires. A new variant
added to the union must be appended here too.
- src/gate/bypass.test.ts: new 'adversarial bypass_label cannot
smuggle vso commands' case feeds a label containing
'##vso[task.complete result=Failed;]X\nY' and asserts no Failed
complete is emitted.
- src/shared/__tests__/vso-logger.test.ts: cases for the new = and
space escapes, complete() idempotency, and logInfo's leading-#
neutralisation.
Pre-existing tests/cli_tests::test_run_subcommand_not_present failure
on Windows debug builds (clap stack overflow generating --help for the
now-15-subcommand enum) is unrelated to this PR and only reproduces
on Windows debug. Linux CI passes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t-facing reference ado-script-design.md was a thought-experiment exploring whether to move gate-eval logic into the Rust binary (A1) vs ship a bundled TypeScript workspace (A2). The A2 decision has been implemented for some time, so the design doc is no longer authoritative and was creating noise (two referenced sources of truth). docs/ado-script.md is rewritten to be self-contained, task-oriented, and focused on how ado-script works at runtime rather than the design history: - End-to-end data-flow ASCII diagram (compiler -> bundle -> Setup job -> SHOULD_RUN -> Agent/SafeOutputs gating) - Five-stage runtime breakdown of gate.js (decode+size-cap -> preflight -> bypass -> facts -> eval) with rationale for each stage - Full runtime env-var contract table including the new ADO_API_TIMEOUT_MS knob - Explicit lockstep contract: Fact::ado_exports() (Rust) <-> ENV_BY_FACT + FactKind (TS) -- codegen drift check covers GateSpec shape only - 'Modifying ado-script' section with three task recipes (new predicate, new pipeline-var fact, new bundle) plus local-development loop and E2E test command - Bundle-size budget reflects the new lazy-import split (78 KB entry + 2.7 MB lazy SDK chunk) Also drops the dangling reference from scripts/ado-script/README.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Looks good overall — clean migration with thorough tests and solid security handling. One real maintenance hazard to flag. Findings
|
Summary
Replaces the Python
gate-eval.pytrigger-filter evaluator with abundled TypeScript artifact
scripts/gate.js, produced by a newscripts/ado-script/workspace. This is variant A2 from thedesign walkthrough
— a per-use-site Node bundle, types codegen'd from the Rust IR via
schemarsto prevent drift.The compiler now emits a
NodeTool@0install step plusnode /tmp/ado-aw-scripts/gate.jsinstead ofpython3. The bundleships in the existing
scripts.ziprelease asset.Why
gate-eval.pywas a 388-line conflation of fact acquisition, ADO RESTcalls, predicate evaluation, and self-cancel — hand-rolled
urllib,no SDK, no real test framework. Moving to TypeScript lets us reuse
azure-devops-node-apifor REST calls, run vitest with proper mocks,and unblock future internal bundles (e.g.
poll.js) that follow thesame per-use-site pattern.
What's in the diff (53 files)
Compiler
src/compile/extensions/trigger_filters.rs— emitsNodeTool@0step;
GATE_EVAL_PATHswitched togate.js.src/compile/filter_ir.rs—python3→nodeincompile_gate_step_external; IR types gainJsonSchemaderives;new
generate_gate_spec_schema().src/main.rs— hiddenexport-gate-schemasubcommand consumed bythe workspace's
npm run codegen.TypeScript workspace —
scripts/ado-script/(40 new files)src/shared/— auth,ado-client(with retries againstazure-devops-node-api), env-facts, policy state machine,vso-logger.
src/gate/— bypass, fact acquisition, 11 predicate evaluators,self-cancel.
src/shared/types.gen.ts— auto-generated fromcargo run -- export-gate-schema→json-schema-to-typescript.guards + smoke test + shared-module units).
scripts/gate.js≈ 1.1 MB (well under the5 MB per-bundle budget).
Pipeline + CI
.github/workflows/release.yml— addssetup-node+npm ci && npm run buildbefore zippingscripts/; excludesnode_modules,dist/,schema/fromscripts.zip..github/workflows/ado-script.yml— new CI job: install, codegen,drift-check (
git diff --exit-codeontypes.gen.ts), tests,typecheck, build, smoke test, E2E.
Tests
tests/gate_e2e.rs(#[ignore]'d) — compiles a real agent,extracts
GATE_SPECfrom the emitted YAML, runsnode gate.jsend-to-end, asserts
SHOULD_RUN.tests/export_gate_schema.rs— verifies the new subcommand emitsvalid JSON Schema.
tests/compiler_tests.rs— assertion tightening: wascompiled.contains("python3")(which silently passed even afterthe migration via
base.yml's mcpg-config validation step); nowasserts the exact
node '/tmp/ado-aw-scripts/gate.js'substring.Docs
docs/ado-script.md(new) — A2 decision record, codegen pipeline,bundle-size budget, instructions for adding new bundles.
docs/filter-ir.md— Python references replaced with the Nodeevaluator;
NodeTool@0step added; scripts.zip distributiondocumented.
AGENTS.md— directory tree + tech stack + docs index updated.ado-script-design.md(new at repo root) — original designwalkthrough that produced the A2 decision.
Deleted
scripts/gate-eval.py(388 lines)scripts/gate-spec.schema.json(regenerable; the workspace ownsits own
schema/copy via codegen)tests/gate_eval_tests.py(45 cases, all ported to vitest)Behavior parity
The TypeScript port is a 1:1 reimplementation. Confirmed parity for
each of the 45 deleted Python test cases (see
scripts/ado-script/src/gate/__tests__/ports/INVENTORY.md). Twointentional minor deviations are documented inline:
pr_is_draftreturnsundefinedwhenpr_metadatais missing(Python returned the literal string
"unknown"). Topologicalordering + the policy tracker prevent the divergence from being
observable.
PolicyTrackercollapses simultaneousfail_closed+skip_dependentsreferences to"skip"for cleaner verdicts. ThePython evaluator set
should_run = falseglobally onfail_closedbut produced functionally equivalent output for the realistic fact
dependency graph.
Drift prevention
The
ado-scriptCI workflow runsnpm run codegenand thengit diff --exit-code -- scripts/ado-script/src/shared/types.gen.ts.If a contributor changes
Fact/Predicate/GateSpecshapes in Rustwithout regenerating, CI fails with a clear remediation message.
Out of scope (explicit non-goals)
ado-script:front-matter block letting authors runarbitrary TypeScript at pipeline runtime (Variant B in the design
doc). Separate RFC if pursued.
Test plan
Local validation (all green):
CI validates the same flow plus the codegen drift check.
Manual reviewer checklist:
and confirm only the
python3→nodeswitch +NodeTool@0prepend changed.INVENTORY.mdfor the 1:1 mapping of deleted Pythontests to their vitest counterparts.
docs/ado-script.mdto confirm the decision rationalereads coherently.
Note on diff scope
A handful of unrelated Rust files were rustfmt-reformatted by
sub-agents during implementation. Those reflows have been reverted
out of this commit so the diff stays surgical to the migration. If
desired, a follow-up
chore: rustfmt the treePR can land themseparately.