Skip to content

Implement the v3 output schemas: structured why + doctor reports#51

Merged
kjanat merged 10 commits into
masterfrom
feat/schema-v3
Jun 14, 2026
Merged

Implement the v3 output schemas: structured why + doctor reports#51
kjanat merged 10 commits into
masterfrom
feat/schema-v3

Conversation

@kjanat

@kjanat kjanat commented Jun 12, 2026

Copy link
Copy Markdown
Owner

What

Implements the schema v3 data models that shipped in #50 as unreviewed
drafts. Both drafts got the same treatment: compare against the actual
codebase, implement, validate the real output against the draft, retire the
draft.

why --json v3 (default for why)

Report restructured around {task, match} candidate pairs plus a
decision block. Tasks carry identity (fqn = root:<kind>:<name>,
provider, kind — cargo aliases labeled cargo-alias), origin
(source file, source_pointer key path), and resolution data
(definition, resolved preview, cwd, sibling aliases). The match
half exposes the exact run-time selection key; decision.strategy names
the branch taken (single-candidate / ranked / filtered /
exec-fallback).

doctor --json v3 (default for doctor)

Flat v2 dump becomes a structured diagnostic inventory:
invocation/environment/runner provenance, per-ecosystems decisions
graded by confidence (typed ResolutionStep → override/manifest/lockfile
= high, PATH probe = medium, legacy npm fallback = low, failure = none),
first-class sources, fqn-keyed tasks with effective resolved
commands, PATH-probed tools, duplicate-task-name conflicts (winner,
shadowed, ranking), flattened diagnostics, and a self-describing
resolution policy block.

Verification

  • why v3 real output reproduces the former draft example verbatim
    (semantic diff: identical); promoted to schemas/why.v3.example.json.
  • doctor v3 real output validates against the original draft schema
    and the committed generated one (ajv, draft 2020-12); example committed.
  • Conflict detection proven on real data: this repo's runner task is
    defined by both the justfile and cargo-aliases; the report names
    root:just:runner the winner with the ranking that decided it.
  • 642 lib + 24 integration tests (15 new), clippy --all-targets --all-features clean, zero lint suppressions, gen-schema drift-stable.

Review deltas from the drafts (documented in module docs)

  • tasks[].resolved / source are nullable — PM resolution can fail;
    the draft would have forced lying.
  • sources[].kind aligned to the why-v3 label convention
    (cargo-alias), fixing the drafts' cross-surface inconsistency.
  • Draft shapes nothing can emit yet (rich dependency edges, workspace
    identity, probe-error status, unused severities/tool kinds) are
    deferred, not declared — contracts describe output, not ambition.

Versioning

Surfaces version independently: doctor and why are at v3, list
stays at v2 and rejects --schema-version 3 rather than mislabel an
unchanged payload. v1/v2 output unchanged and reachable via
--schema-version. Human output untouched (doctor human path pins to the
v2 builder).

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • cr:review
🚫 Excluded labels (none allowed) (2)
  • wip
  • cr:skip

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 40e892b6-0872-4b36-a163-211bceb4b307

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR rolls out JSON schema version 3 as the new default output format for runner doctor --json and runner why --json commands. The implementation splits schema version management per-command (doctor and why each get independent current-version constants and validators), updates the CLI to accept schema versions 1–3, and adds complete v3 payload implementations. Doctor v3 expands the report with structured invocation metadata, environment details, ecosystem resolution signals, task inventory with effective commands, tool probing results, conflict detection, and flattened diagnostics. Why v3 restructures the output around task/match pairs with a decision block containing strategy and rationale, including provider/source labelling and optional PM-resolved commands. Both are accompanied by committed JSON Schema documents and realistic examples. Backward compatibility is preserved: v1 and v2 are accessible via --schema-version.

Sequence Diagram(s)

sequenceDiagram
  participant User as User CLI
  participant Dispatch as lib.rs<br/>Dispatch
  participant Validator as schema::validate_*<br/>schema_version
  participant DoctorCmd as cmd::doctor<br/>why()
  participant Builder as DoctorReportV3<br/>WhyReportV3
  participant JSON as serde_json<br/>serialize
  
  User->>Dispatch: doctor --json --schema-version 3
  Dispatch->>Validator: doctor_schema_version_for_json(3)
  Validator-->>Dispatch: Ok(3)
  Dispatch->>DoctorCmd: cmd::doctor(..., schema_version=3)
  DoctorCmd->>Builder: if schema_version >= 3: build_report_v3(ctx)
  Builder-->>DoctorCmd: DoctorReportV3 with expanded metadata
  DoctorCmd->>JSON: serde(report_v3)
  JSON-->>DoctorCmd: pretty JSON string
  DoctorCmd-->>User: print to stdout
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • kjanat/runner#22: Introduced initial runner doctor/runner why JSON implementations; this PR extends those with v3 schema support and default routing.
  • kjanat/runner#37: Modified schema-generation plumbing; related to the schema emission changes in this PR.
  • kjanat/runner#50: Earlier work on why/schema generation that this PR builds upon to add v3 contracts and builders.

"Yo-ho, the schemas be polished and the JSON be sharpened,
Doctor and Why now sail on v3, no longer half-harboured,
Cargo aliases get their own little flag to fly,
Tests and examples snug in the chest — shipshape, aye aye!
Arr, go fetch yer review, or walk the plank, matey!"

🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Semver Version Bump Validation ⚠️ Warning Rust/code files changed (e.g., src/, schemas/) but Cargo.toml version is unchanged: origin/master 0.13.0 vs PR HEAD 0.13.0; no required SemVer bump detected. Bump the SemVer version in Cargo.toml (MAJOR for breaking default JSON output contract) to a new MAJOR.MINOR.PATCH value; commit the updated version file.
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarises the main change: implementing v3 output schemas for why and doctor reports.
Description check ✅ Passed The description is directly related to the changeset, providing detailed context about the v3 schema implementations, verification steps, and versioning approach.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Changelog Update ✅ Passed Non-doc code files (e.g., src/.rs, schemas/.json) changed and CHANGELOG.md was updated under ## [Unreleased] with a new ### Added section describing v3 doctor/why defaults.
Agents.Md Documentation Updated ✅ Passed No AGENTS.md files exist in the repository (0 found), so there’s no corresponding AGENTS.md that could be required to update for this PR’s CLI/workflow changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jun 12, 2026
coderabbitai[bot]

This comment was marked as resolved.

@kjanat kjanat self-assigned this Jun 13, 2026
@kjanat kjanat added the cr:skip Skip CodeRabbit review label Jun 13, 2026
coderabbitai[bot]

This comment was marked as resolved.

Comment thread schemas/doctor.v1.example.json
kjanat added 3 commits June 13, 2026 04:07
`why --json` gains `{task, match}` candidate pairs plus a decision block;
`doctor --json` becomes a diagnostic inventory (provenance, per-ecosystem
decisions, sources, fqn-keyed tasks, tools, conflicts, diagnostics).
Shims modeled generically (keyed by tool, manager as data). doctor/why
default to v3; list stays v2. v3 examples validate against the schemas.
The #50 examples carried real home paths, Volta layout, and exact tool
versions. Replace with fabricated values describing no real host.
kjanat added 5 commits June 13, 2026 23:59
Keep Node in the v3 ecosystems/tools inventory when package.json
tasks resolve via npm but no Node PM was detected, so the report
stays internally consistent. Pin the human doctor renderer to an
explicit v2 contract instead of CURRENT_VERSION, so a future
`list` version bump can't silently change human output.

Also trim overly verbose comments across the crate: war stories,
issue/version trivia, and micro-optimization rationale, keeping
the concise WHY a mature codebase wants.
Switch the v3 fqn delimiter from `<scope>:<kind>:<name>` to
`<scope>:<kind>#<name>`, so a task name that itself contains `:`
(e.g. an npm script `fmt:update`) stays unambiguous — consumers
split once on `#`. Centralise the format in `labels::fqn` so `why`
and `doctor` can't drift apart on it.

doctor: probe real tool versions by running `<binary> --version`
when detection didn't already supply one, instead of always
emitting null. The first dotted, digit-led token wins across the
varied `--version` formats; `v` prefixes stripped.

doctor: add `is_alias` to task entries so consumers can tell an
alias from a real target without reverse-engineering `definition`.
Run deno.json tasks in-process via deno_task_shell so a deno project
works without the `deno` binary: the default policy self-execs only
when deno is absent, the `unstable-deno-exec` feature makes it primary.
Tasks that invoke `deno` or declare `dependencies` still need it.

Parse deno task descriptions (string and object form) and surface them
in list/why/doctor. Factor the shell engine into a reusable
`tool::shell`; `deno_exec` is the thin deno-specific layer over it.

doctor: add per-task `self_executable` and derive the deno tool's
`required` from it; probe package-manager/runner versions via
`--version` instead of always reporting null.
Surface the canonical cargo subcommand a built-in alias renames
(`test`, `build`, …) as the task, with the short form (`t`, `b`)
folded under it instead of standing alone — `t` is an alias of
`test`, not a peer. Aliases carrying extra args (`bb`, `cl`) keep
their own rows; only bare renames fold.

`runner list` shows `test (t)`; both `runner run test` and
`runner run t` still dispatch `cargo test`. Note `cargo run` now
coexists with a `just run` task as a duplicate-name conflict.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
runner 1766c0a Commit Preview URL

Branch Preview URL
Jun 14 2026, 09:20 PM

@socket-security

socket-security Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​tokio@​1.52.35810093100100
Addedcargo/​deno_task_shell@​0.33.09510093100100

View full report

@socket-security

socket-security Bot commented Jun 14, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: cargo tokio is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: Cargo.lockcargo/tokio@1.52.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/tokio@1.52.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: cargo writeable is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?cargo/deno_task_shell@0.33.0cargo/writeable@0.6.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/writeable@0.6.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Cross-source name collisions (e.g. `cargo run` shadowed by `just
run`) were invisible in `runner list` / bare `runner` — both tasks
just showed as separate rows, hiding that the bare-name lookup
silently runs only one. Add a conflict footer that names the
winning source and the shadowed ones, resolved with the same
precedence as `runner run` so the verdict is accurate. Shown in
both the list and the info glance.
@kjanat

kjanat commented Jun 14, 2026

Copy link
Copy Markdown
Owner Author

Did a deep review pass. Implementation quality is high — doctor_v3.rs, why.rs, and the new tool/shell.rs have no production unwrap/expect/panic (all expect()s are in test modules), good doc coverage, and CI is green. The two CodeRabbit threads (human renderer pinned to an explicit v2 constant; has_node_context predicate fix) are resolved correctly.

Three things stood out that I think are implicit decisions worth making explicit before merge, since they all point the same direction — the output contract is looser / more breaking than the diff suggests:

  1. Schema strictness dropped vs. the draft. The deleted doctor.v3-draft.schema.json used additionalProperties: false in 24 places; the committed doctor.v3.schema.json (and why.v3.schema.json) use it in zero. So the schemas now silently accept unknown/extra fields — ajv will validate the committed examples but won't catch a typo'd or stray field in real output. The PR description documents every other draft delta (nullable resolved/source, cargo-alias label alignment, deferred shapes) but not this one. If it's a deliberate forward-compat choice, worth documenting alongside the others; if it's an accidental omission from the draft→final port, worth restoring.
  2. No version bump despite a default-output change. why --json and doctor --json now default to v3, but Cargo.toml stays at 0.13.0 (this is what the failing "Semver Version Bump Validation" pre-merge check is flagging). The default shape moving is effectively breaking for any consumer parsing unpinned output. Either bump the version or make a conscious call that the 0.x semantics make this fine.
  3. Stale .zed/settings.json mapping. The reformatted config still maps doctor.v3-draft.example.json → doctor.v3-draft.schema.json, but that schema file is deleted in this PR, and there are no mappings for the new doctor.v3.example.json / why.v3.example.json fixtures. Minor, but it points at a missing file and leaves the new examples without editor validation.
    Also worth a sanity check (likely fine): this PR adds deno_task_shell + a tokio runtime to power the deno self-exec path — a broader runtime surface than the "output schemas" title implies, and the two crates Socket flagged. Just confirming that dependency expansion was meant to ride along here rather than be split out.

None of these are code-quality blockers — the Rust is solid. They're contract/release-hygiene decisions that are currently unstated.

Add `deny_unknown_fields` to the doctor/why v3 types so the generated
schemas set `additionalProperties: false` again (the draft had it; the
schemars-generated finals had dropped it). Stray or misspelled fields in
real output now fail validation instead of passing silently.

Release prep for 0.13.1 (the v3 default-output move warrants a bump;
patch per 0.x semantics — consumers pin to avoid the shift):
- bump Cargo.toml/lock to 0.13.1
- fix the stale `.zed` schema mapping (drop the deleted v3-draft entry,
  add doctor.v3 / why.v3 example->schema mappings)
- move the Unreleased v3 work into a [0.13.1] section and document the
  deno self-exec, task descriptions, cargo-alias fold, and conflict footer
@kjanat kjanat merged commit d125c04 into master Jun 14, 2026
19 checks passed
@kjanat kjanat deleted the feat/schema-v3 branch June 14, 2026 21:33
kjanat added a commit that referenced this pull request Jun 14, 2026
* feat(schema): structured v3 reports for why and doctor

`why --json` gains `{task, match}` candidate pairs plus a decision block;
`doctor --json` becomes a diagnostic inventory (provenance, per-ecosystem
decisions, sources, fqn-keyed tasks, tools, conflicts, diagnostics).
Shims modeled generically (keyed by tool, manager as data). doctor/why
default to v3; list stays v2. v3 examples validate against the schemas.

* chore(schemas): replace host data in v1/v2 example fixtures

The #50 examples carried real home paths, Volta layout, and exact tool
versions. Replace with fabricated values describing no real host.

* fix(ci): pages sparse-checkout needs non-cone mode for globs

* fix(doctor): keep Node in v3 report; pin human v2

Keep Node in the v3 ecosystems/tools inventory when package.json
tasks resolve via npm but no Node PM was detected, so the report
stays internally consistent. Pin the human doctor renderer to an
explicit v2 contract instead of CURRENT_VERSION, so a future
`list` version bump can't silently change human output.

Also trim overly verbose comments across the crate: war stories,
issue/version trivia, and micro-optimization rationale, keeping
the concise WHY a mature codebase wants.

* style(zed): trailing commas + multiline json schema list

* feat(schema): disambiguate fqn, probe tool versions

Switch the v3 fqn delimiter from `<scope>:<kind>:<name>` to
`<scope>:<kind>#<name>`, so a task name that itself contains `:`
(e.g. an npm script `fmt:update`) stays unambiguous — consumers
split once on `#`. Centralise the format in `labels::fqn` so `why`
and `doctor` can't drift apart on it.

doctor: probe real tool versions by running `<binary> --version`
when detection didn't already supply one, instead of always
emitting null. The first dotted, digit-led token wins across the
varied `--version` formats; `v` prefixes stripped.

doctor: add `is_alias` to task entries so consumers can tell an
alias from a real target without reverse-engineering `definition`.

* feat(deno): self-exec tasks via embedded shell

Run deno.json tasks in-process via deno_task_shell so a deno project
works without the `deno` binary: the default policy self-execs only
when deno is absent, the `unstable-deno-exec` feature makes it primary.
Tasks that invoke `deno` or declare `dependencies` still need it.

Parse deno task descriptions (string and object form) and surface them
in list/why/doctor. Factor the shell engine into a reusable
`tool::shell`; `deno_exec` is the thin deno-specific layer over it.

doctor: add per-task `self_executable` and derive the deno tool's
`required` from it; probe package-manager/runner versions via
`--version` instead of always reporting null.

* feat(cargo): fold short aliases under subcommands

Surface the canonical cargo subcommand a built-in alias renames
(`test`, `build`, …) as the task, with the short form (`t`, `b`)
folded under it instead of standing alone — `t` is an alias of
`test`, not a peer. Aliases carrying extra args (`bb`, `cl`) keep
their own rows; only bare renames fold.

`runner list` shows `test (t)`; both `runner run test` and
`runner run t` still dispatch `cargo test`. Note `cargo run` now
coexists with a `just run` task as a duplicate-name conflict.

* feat(list): surface duplicate-name conflicts as a footer

Cross-source name collisions (e.g. `cargo run` shadowed by `just
run`) were invisible in `runner list` / bare `runner` — both tasks
just showed as separate rows, hiding that the bare-name lookup
silently runs only one. Add a conflict footer that names the
winning source and the shadowed ones, resolved with the same
precedence as `runner run` so the verdict is accurate. Shown in
both the list and the info glance.

* fix(schema): reject unknown fields in v3 output

Add `deny_unknown_fields` to the doctor/why v3 types so the generated
schemas set `additionalProperties: false` again (the draft had it; the
schemars-generated finals had dropped it). Stray or misspelled fields in
real output now fail validation instead of passing silently.

Release prep for 0.13.1 (the v3 default-output move warrants a bump;
patch per 0.x semantics — consumers pin to avoid the shift):
- bump Cargo.toml/lock to 0.13.1
- fix the stale `.zed` schema mapping (drop the deleted v3-draft entry,
  add doctor.v3 / why.v3 example->schema mappings)
- move the Unreleased v3 work into a [0.13.1] section and document the
  deno self-exec, task descriptions, cargo-alias fold, and conflict footer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cr:skip Skip CodeRabbit review enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant