Skip to content

fix(scan): keep hosted --json envelope schema-consistent across discovery - #163

Open
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
fix/hosted-json-schema-consistent
Open

fix(scan): keep hosted --json envelope schema-consistent across discovery#163
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
fix/hosted-json-schema-consistent

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Problem

scan --json --mode hosted emits TWO incompatible top-level JSON shapes depending on whether discovery found packages:

  • 0 packages (gate-off): the zero-discovery early-return in crates/socket-patch-cli/src/commands/scan/mod.rs prints the CLASSIC scan object ({status, scannedPackages, lockfileOnlyPackages, totalPatches, canAccessPaidPatches, packages, updates, …}) before the hosted branch runs — with no redirect key.
  • >=1 package (gate-on): hosted::run_redirect in crates/socket-patch-cli/src/commands/scan/hosted.rs:531 emitted a BARE {status, redirect:{mode, redirected, rewrittenFiles, skipped, warnings, dryRun}} envelope — NONE of the scan-count keys, and no per-patch enumeration.

A JSON consumer parsing scannedPackages/totalPatches/canAccessPaidPatches/packages breaks the moment >=1 package is discovered. Confirmed against real prod for maven + nuget, reproduces on npm. Vendored mode does not have this flip — it NESTS a vendor block inside the classic scan object.

Fix

Follow the vendored-mode precedent: always emit the classic scan object and NEST the redirect result under a redirect key (additive), for BOTH paths. Human (non-JSON) output is unchanged.

  • run_redirect now takes the classic scan object as Option<serde_json::Value> (crates/socket-patch-cli/src/commands/scan/hosted.rs:114), built by run. A new build_redirect_json_envelope helper (hosted.rs:88) folds the redirect block into it; emit_json_error (hosted.rs:75) folds error status into it too — so error envelopes keep the same top-level keys instead of flipping schema.
  • In run (mod.rs): the HUMAN hosted path returns early (if hosted && !args.common.json, passing None); the --json hosted path now returns from inside the JSON block, after the classic result (including the packages enumeration + notInstalled flags) is built, passing Some(result) (mod.rs:927).
  • The zero-discovery early-return gains a no-op nested redirect block when hosted (mod.rs:622) so the 0-package envelope is shape-consistent with the >=1-package one.

This also fixes the missing enumeration: the hosted --json output now includes the packages array (which packages / patch UUIDs were selected), same as every other scan.

Kills sweep findings: hosted-scan-json-schema-flips-with-discovery (P2), hosted-scan-json-omits-enumeration (P2).

Test

Hermetic serialization test hosted_json_envelope_nests_redirect_into_classic_scan_object (crates/socket-patch-cli/src/commands/scan/hosted.rs) asserts the hosted --json envelope for the >=1-package case carries the classic scan keys (scannedPackages/packagesWithPatches/totalPatches/freePatches/paidPatches/canAccessPaidPatches/updates) AND the per-package/patch-uuid enumeration AND a nested redirect object preserving every sub-field. It exercises the extracted build_redirect_json_envelope — the exact schema that flipped before. cargo test -p socket-patch-cli --lib commands::scan → 39 passed. cargo build -p socket-patch-cli and cargo clippy -p socket-patch-cli both clean.

Scope

Touches only crates/socket-patch-cli/src/commands/scan/{hosted.rs,mod.rs} (the hosted-mode JSON envelope). run_redirect has a single caller (the hosted branch in run), updated in place. No cross-dependencies on other in-flight fixes.

🤖 Generated with Claude Code


Note

Low Risk
CLI JSON output shape only for hosted scan mode; redirect/lockfile behavior unchanged; human output unchanged.

Overview
Fixes inconsistent scan --json --mode hosted stdout when discovery finds packages: success used to emit only {status, redirect} and dropped classic scan fields (scannedPackages, totalPatches, packages, etc.), while the zero-package path already printed the full scan object.

Hosted JSON now follows vendored mode: run builds the classic scan result first (including packages and lockfile-only flags), then run_redirect nests redirect details under redirect via build_redirect_json_envelope. emit_json_error merges errors into that same object when scan_result is present. Human (non-JSON) hosted runs still call run_redirect early with None.

The empty-scan JSON early return adds a no-op redirect block when hosted so shape matches the ≥1-package path. A unit test locks the nested envelope schema.

Reviewed by Cursor Bugbot for commit 24d479b. Configure here.

…very

`scan --json --mode hosted` emitted two incompatible top-level shapes: the
classic scan object (scannedPackages/totalPatches/canAccessPaidPatches/
packages) on zero discovery, but a bare `{status, redirect}` envelope once
>=1 package was found — dropping every scan-count key and the per-patch
enumeration, breaking JSON consumers on the redirect path.

Follow the vendored-mode precedent: always emit the classic scan object and
NEST the redirect result under a `redirect` key, for both the zero-discovery
and >=1-discovery paths. `run_redirect` now takes the classic scan object
(`Option<Value>`) built in `run` and folds `redirect` (and error status) into
it; the zero-package early-return gains a no-op `redirect` block. Human
(non-JSON) output is unchanged.

Kills sweep findings: hosted-scan-json-schema-flips-with-discovery,
hosted-scan-json-omits-enumeration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant