upstream-radar 0.32.0 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +616 -43
  2. package/cordis.patch.yml +1 -0
  3. package/dist/src/cli.js +1123 -34
  4. package/dist/src/cli.js.map +1 -1
  5. package/dist/src/compatibility.d.ts +3 -1
  6. package/dist/src/compatibility.d.ts.map +1 -1
  7. package/dist/src/compatibility.js +146 -12
  8. package/dist/src/compatibility.js.map +1 -1
  9. package/dist/src/demo.d.ts +13 -0
  10. package/dist/src/demo.d.ts.map +1 -0
  11. package/dist/src/demo.js +85 -0
  12. package/dist/src/demo.js.map +1 -0
  13. package/dist/src/doctor.d.ts.map +1 -1
  14. package/dist/src/doctor.js +62 -0
  15. package/dist/src/doctor.js.map +1 -1
  16. package/dist/src/dsh-analysis.d.ts.map +1 -1
  17. package/dist/src/dsh-analysis.js +2 -1
  18. package/dist/src/dsh-analysis.js.map +1 -1
  19. package/dist/src/dsh-plugin.d.ts +9 -3
  20. package/dist/src/dsh-plugin.d.ts.map +1 -1
  21. package/dist/src/dsh-plugin.js +87 -11
  22. package/dist/src/dsh-plugin.js.map +1 -1
  23. package/dist/src/dsh-profile-check.d.ts +79 -0
  24. package/dist/src/dsh-profile-check.d.ts.map +1 -0
  25. package/dist/src/dsh-profile-check.js +600 -0
  26. package/dist/src/dsh-profile-check.js.map +1 -0
  27. package/dist/src/dsh-runtime.d.ts +7 -0
  28. package/dist/src/dsh-runtime.d.ts.map +1 -1
  29. package/dist/src/dsh-runtime.js +21 -0
  30. package/dist/src/dsh-runtime.js.map +1 -1
  31. package/dist/src/github-advisory.d.ts +25 -0
  32. package/dist/src/github-advisory.d.ts.map +1 -0
  33. package/dist/src/github-advisory.js +340 -0
  34. package/dist/src/github-advisory.js.map +1 -0
  35. package/dist/src/graph.d.ts +2 -0
  36. package/dist/src/graph.d.ts.map +1 -1
  37. package/dist/src/graph.js +600 -4
  38. package/dist/src/graph.js.map +1 -1
  39. package/dist/src/index.d.ts +16 -6
  40. package/dist/src/index.d.ts.map +1 -1
  41. package/dist/src/index.js +15 -5
  42. package/dist/src/index.js.map +1 -1
  43. package/dist/src/init.d.ts +44 -2
  44. package/dist/src/init.d.ts.map +1 -1
  45. package/dist/src/init.js +62 -5
  46. package/dist/src/init.js.map +1 -1
  47. package/dist/src/installed-graph.d.ts +3 -1
  48. package/dist/src/installed-graph.d.ts.map +1 -1
  49. package/dist/src/installed-graph.js +101 -2
  50. package/dist/src/installed-graph.js.map +1 -1
  51. package/dist/src/inventory.d.ts.map +1 -1
  52. package/dist/src/inventory.js +76 -2
  53. package/dist/src/inventory.js.map +1 -1
  54. package/dist/src/notification-policy.d.ts +20 -0
  55. package/dist/src/notification-policy.d.ts.map +1 -0
  56. package/dist/src/notification-policy.js +87 -0
  57. package/dist/src/notification-policy.js.map +1 -0
  58. package/dist/src/npm-release.d.ts.map +1 -1
  59. package/dist/src/npm-release.js +18 -7
  60. package/dist/src/npm-release.js.map +1 -1
  61. package/dist/src/npm.d.ts.map +1 -1
  62. package/dist/src/npm.js +9 -1
  63. package/dist/src/npm.js.map +1 -1
  64. package/dist/src/osv.d.ts.map +1 -1
  65. package/dist/src/osv.js +1 -0
  66. package/dist/src/osv.js.map +1 -1
  67. package/dist/src/quickstart.d.ts +31 -0
  68. package/dist/src/quickstart.d.ts.map +1 -0
  69. package/dist/src/quickstart.js +266 -0
  70. package/dist/src/quickstart.js.map +1 -0
  71. package/dist/src/radar-history.d.ts +22 -0
  72. package/dist/src/radar-history.d.ts.map +1 -0
  73. package/dist/src/radar-history.js +80 -0
  74. package/dist/src/radar-history.js.map +1 -0
  75. package/dist/src/radar-render.d.ts.map +1 -1
  76. package/dist/src/radar-render.js +106 -1
  77. package/dist/src/radar-render.js.map +1 -1
  78. package/dist/src/radar-state.d.ts.map +1 -1
  79. package/dist/src/radar-state.js +264 -6
  80. package/dist/src/radar-state.js.map +1 -1
  81. package/dist/src/radar-status.d.ts +36 -1
  82. package/dist/src/radar-status.d.ts.map +1 -1
  83. package/dist/src/radar-status.js +199 -16
  84. package/dist/src/radar-status.js.map +1 -1
  85. package/dist/src/radar-types.d.ts +128 -3
  86. package/dist/src/radar-types.d.ts.map +1 -1
  87. package/dist/src/radar-types.js +4 -0
  88. package/dist/src/radar-types.js.map +1 -1
  89. package/dist/src/radar.d.ts +7 -1
  90. package/dist/src/radar.d.ts.map +1 -1
  91. package/dist/src/radar.js +432 -70
  92. package/dist/src/radar.js.map +1 -1
  93. package/dist/src/render.d.ts.map +1 -1
  94. package/dist/src/render.js +27 -2
  95. package/dist/src/render.js.map +1 -1
  96. package/dist/src/scan.d.ts.map +1 -1
  97. package/dist/src/scan.js +39 -0
  98. package/dist/src/scan.js.map +1 -1
  99. package/dist/src/threat-intel.d.ts +46 -0
  100. package/dist/src/threat-intel.d.ts.map +1 -0
  101. package/dist/src/threat-intel.js +243 -0
  102. package/dist/src/threat-intel.js.map +1 -0
  103. package/dist/src/upstream-observer.d.ts +224 -0
  104. package/dist/src/upstream-observer.d.ts.map +1 -0
  105. package/dist/src/upstream-observer.js +1342 -0
  106. package/dist/src/upstream-observer.js.map +1 -0
  107. package/dist/src/version.d.ts +1 -1
  108. package/dist/src/version.js +1 -1
  109. package/dist/src/vulnerability-priority.d.ts +16 -0
  110. package/dist/src/vulnerability-priority.d.ts.map +1 -0
  111. package/dist/src/vulnerability-priority.js +20 -0
  112. package/dist/src/vulnerability-priority.js.map +1 -0
  113. package/dist/src/webhook.d.ts +84 -0
  114. package/dist/src/webhook.d.ts.map +1 -0
  115. package/dist/src/webhook.js +432 -0
  116. package/dist/src/webhook.js.map +1 -0
  117. package/docs/README.zh-CN.md +593 -40
  118. package/package.json +30 -3
  119. package/schemas/quickstart.schema.json +56 -0
  120. package/schemas/webhook.schema.json +53 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <h1 align="center">Upstream Radar</h1>
2
2
 
3
- <p align="center"><strong>Always-on dependency radar for DeepSeek Harness plugins: exact paths, breaking-change signals, and project-aware Agent follow-up.</strong></p>
3
+ <p align="center"><strong>Always-on dependency radar for DeepSeek Harness plugins: exact paths, CISA KEV/EPSS priority signals, breaking-change detection, and project-aware Agent follow-up.</strong></p>
4
4
 
5
5
  <p align="center">
6
6
  English · <a href="docs/README.zh-CN.md">简体中文</a>
@@ -15,31 +15,134 @@
15
15
  <a href="LICENSE"><img alt="Apache-2.0 license" src="https://img.shields.io/badge/license-Apache--2.0-0f766e?style=flat-square"></a>
16
16
  </p>
17
17
 
18
+ <p align="center">
19
+ <picture>
20
+ <source media="(max-width: 600px)" srcset="docs/assets/upstream-radar-hero-mobile.jpg">
21
+ <img src="docs/assets/upstream-radar-hero.jpg" alt="Upstream Radar watches a dependency graph, highlights one affected path, and routes one signal to a DSH Agent." width="100%">
22
+ </picture>
23
+ </p>
24
+
25
+ <p align="center"><em>Upstream signal → exact installed path → durable incident → project-specific DSH Agent analysis</em></p>
26
+
18
27
  <p align="center">
19
28
  <a href="#try-it-in-60-seconds">Try it in 60 seconds</a> ·
20
29
  <a href="#see-one-incident">See one incident</a> ·
21
30
  <a href="#install-in-dsh">Install in DSH</a> ·
31
+ <a href="#notify-feishu-or-an-https-endpoint">Notify Feishu</a> ·
32
+ <a href="#observe-dsh-plugin-upstream-changes">Observe upstream changes</a> ·
22
33
  <a href="#run-the-proof">Run the proof</a> ·
23
34
  <a href="#run-it-in-github-actions">Run in GitHub Actions</a> ·
35
+ <a href="https://github.com/MicroMilo/upstream-radar/issues/new?template=trial.yml">Share feedback</a> ·
24
36
  <a href="#how-the-loop-works">How it works</a> ·
25
37
  <a href="ROADMAP.md">Roadmap</a>
26
38
  </p>
27
39
 
40
+ ## Start without guessing
41
+
42
+ If you are not sure whether this project should use a DSH profile, a lockfile, or the packaged demo, run the read-only guide first:
43
+
44
+ ```bash
45
+ npx --yes upstream-radar@latest quickstart
46
+ ```
47
+
48
+ It looks only at the current directory and local DSH profile metadata. It recommends one of the real paths below, labels every suggested command as read-only, local-file creation, or install/start, and refuses to choose between two lockfiles or multiple DSH profiles. The guide itself never installs packages, starts DSH, queries vulnerability sources, or executes plugin code. Use `--json` to feed the result into a setup page or internal launcher.
49
+
50
+ ## Choose the smallest path
51
+
52
+ | Your goal | Start here | What you get |
53
+ | --- | --- | --- |
54
+ | Keep a live DSH Agent informed | [`setup`](#install-in-dsh) | A profile-aware monitor that refreshes the installed graph and routes only changed incidents to the matching Agent. |
55
+ | Observe DSH plugin upstream changes | [`observe`](#observe-dsh-plugin-upstream-changes) | A scheduled old → new comparison of GitHub commits, npm releases, manifests, and optional lockfile graphs; only meaningful changes wake an Agent. |
56
+ | Respond to the first alert | [`radar next`](#install-in-dsh) | One read-only command selects the highest-priority incident and points to the DSH task, verified analysis, or next check. |
57
+ | Check a DSH profile before starting it | [`profile-check`](#check-a-dsh-profile-before-starting-it) | Reads the actual lockfile and patch rows and blocks missing loader packages, duplicate loader ids, and release-age rollback risk. |
58
+ | Add a scheduled CI gate | [GitHub Actions example](examples/github-actions/upstream-radar.yml) | A frozen check from a reviewed config or one lockfile, with a concise Job Summary and a machine-readable JSON report. |
59
+ | Check a plugin before installing it | [`graph` / `init` for npm or pnpm lockfiles](#inspect-an-npm-or-pnpm-lockfile-before-installation) | Exact dependency paths and OSV/GitHub Advisory results without running the plugin or its lifecycle scripts. |
60
+ | Review one exact published artifact | `upstream-radar inspect npm:<package>@<exact-version> --deep` | Package, dependency, vulnerability, and provenance evidence for one release. |
61
+ | Publish and maintain a DSH plugin | [Plugin author path](#for-dsh-plugin-authors) | Start from a real DSH scaffold, review its locked graph, and add a two-step CI gate before users install it. |
62
+ | Send changed events to Feishu | [Feishu or HTTPS webhook](#notify-feishu-or-an-https-endpoint) | Native Feishu V2 text, environment-only secrets, durable acknowledgement, and retry. |
63
+
64
+ If you want project-specific reasoning from DSH, use the first path. If you only need an independent admission or regression gate, use the second or third; they do not require a running DSH profile.
65
+
28
66
  ## Try it in 60 seconds
29
67
 
30
- Use a DSH profile that already contains at least one third-party bundle. Replace `web` with your profile name. The commands below are split between two terminals because DSH normally stays running:
68
+ Want to see the core value before touching DSH? Run the packaged, network-free demo:
69
+
70
+ ```bash
71
+ npx --yes upstream-radar@latest demo
72
+ ```
73
+
74
+ It prints one exact transitive path, independent advisory-source evidence (including an explicit source conflict), CISA KEV/EPSS prioritization evidence, the read-only DSH Agent handoff, and the next setup command. It uses a local fixture only; it does not inspect your repository, install a plugin, or claim that the demo advisory is real. Use `--json` when you want the same proof as a machine-readable artifact.
75
+
76
+ The core result looks like this (the demo uses a local fixture; fields are abbreviated):
77
+
78
+ ```text
79
+ [HIGH][NEW] Dependency vulnerability
80
+ Affected: parser@2.9.0
81
+ Paths:
82
+ demo-plugin@1.0.0 -> logger@4.0.2 -> parser@2.9.0
83
+ Threat signal: CISA KEV lists this CVE as exploited in the wild.
84
+ FIRST EPSS estimated exploitation probability: 97.2% (percentile 100.0%)
85
+ Next: Review the fixed version with the DSH Agent in this project.
86
+ ```
87
+
88
+ The useful part is the exact path and project-specific next step—not another generic list of vulnerable package names.
89
+
90
+ Want to try it on a real published DSH plugin immediately?
91
+
92
+ ```bash
93
+ npx --yes upstream-radar@0.33.1 inspect npm:dsh-feishu-bot@0.15.4 --deep
94
+ ```
95
+
96
+ This runs from an otherwise empty directory and returns a short admission,
97
+ coverage, dependency-count, vulnerability-count, and next-step summary.
98
+
99
+ Want to see a real author-actionable result? This exact published DSH plugin
100
+ currently cannot produce a complete dependency graph in a clean npm resolver:
101
+
102
+ ```bash
103
+ npx --yes upstream-radar@0.33.1 inspect \
104
+ npm:@sanqi-normal/dsh-webui-market-plugin@0.5.4 \
105
+ --deep --fail-on never
106
+ ```
107
+
108
+ The result is `review / incomplete`, with the concrete cause
109
+ `@deepseek-ai/dsh-compact@^0.0.1-rc.1` not published. No DSH profile, plugin
110
+ execution, or LLM is required. See the [reproducible author report](examples/dsh/reports/sanqi-market-plugin-dependency-resolution.md).
111
+
112
+ Tried the demo or a real DSH setup? [Share a short trial result](https://github.com/MicroMilo/upstream-radar/issues/new?template=trial.yml) with the versions, path, and redacted outcome. Never include source code, secrets, or private paths.
113
+
114
+ Every command has its own short guide: `npx --yes upstream-radar@latest setup --help`, `npx --yes upstream-radar@latest inspect --help`, and `npx --yes upstream-radar@latest radar status --help` are useful starting points when you are not sure which path to choose.
115
+
116
+ Use DSH with at least one third-party bundle. When it has exactly one such profile, `setup` selects it automatically; pass `--profile <name>` only when you have multiple profiles. The commands below are split between two terminals because DSH normally stays running:
117
+
118
+ Before running `setup`, confirm that DeepSeek Harness is installed and `dsh --help` works. If `setup` cannot find `dsh`, it prints this recovery step again.
119
+
120
+ If DSH has no third-party plugin profile yet, install the plugin you want to monitor first: `dsh plugin --profile <name> add <package>@<exact-version>`.
31
121
 
32
122
  ```bash
33
123
  # Terminal 1
34
124
  pnpm dlx --package=upstream-radar@latest upstream-radar setup \
35
- --profile web \
36
- --project-name "My DSH project" \
37
- --output ./upstream-radar.config.json \
38
- --dsh-patch ./upstream-radar.dsh.yml
125
+ --project-name "My DSH project"
126
+ # Use the profile name printed by setup; `web` is only an example.
39
127
  dsh --profile web --patch ./upstream-radar.dsh.yml
40
128
  ```
41
129
 
42
- `setup` explicitly installs the exact Radar version used by the command into the selected DSH profile, discovers the installed graph, writes the reviewable config and overlay, and runs the network-free wiring check. It does not start DSH or execute plugin business actions; review the generated files before starting the process. If Radar is already installed, add `--no-install`.
130
+ If you explicitly want setup to launch DSH in the same invocation after its local doctor check passes, add `--start`:
131
+
132
+ ```bash
133
+ pnpm dlx --package=upstream-radar@latest upstream-radar setup \
134
+ --project-name "My DSH project" --start
135
+ ```
136
+
137
+ Without `--start`, setup never launches DSH and gives you a pause to review the generated files. The explicit flag is the one-command path; doctor verifies local wiring, but it is not a human review or a package-safety certificate.
138
+
139
+ The one-command path is also covered by a network-free showcase: `pnpm run showcase:setup-start`.
140
+
141
+ If you use npm rather than pnpm, the equivalent launcher is `npx --yes upstream-radar@latest setup --project-name "My DSH project"`. For a reproducible team workflow, replace `latest` with the exact release you have reviewed.
142
+
143
+ `setup` explicitly installs the exact Radar version used by the command into the selected DSH profile, discovers the installed graph, writes `./upstream-radar.config.json` and `./upstream-radar.dsh.yml` by default, and runs the network-free wiring check. By default it does not start DSH or execute plugin business actions; `--start` opts into launching DSH only after the doctor check passes. Use `--output` or `--dsh-patch` for different paths; if Radar is already installed, add `--no-install`.
144
+
145
+ The printed doctor command uses `npx --yes` with the exact same Radar version, so the handoff also works when the first command was launched with npm rather than pnpm.
43
146
 
44
147
  After DSH is running, use a second terminal for the read-only status check:
45
148
 
@@ -50,6 +153,14 @@ pnpm dlx --package=upstream-radar@latest upstream-radar radar status ./upstream-
50
153
 
51
154
  The setup command writes a reviewable inventory and an explicit DSH overlay. Its local doctor check verifies the wiring before DSH starts; `radar status` confirms the first completed check without another network request. Read the [full DSH setup](#install-in-dsh) for the legacy environment-variable path, profile boundaries, and the real runtime proof.
52
155
 
156
+ The same state file keeps a bounded audit trail of real transitions. To answer “what changed and when?” without polling any source:
157
+
158
+ ```bash
159
+ pnpm dlx --package=upstream-radar@latest upstream-radar radar history ./upstream-radar.config.json
160
+ ```
161
+
162
+ It shows `new`, `updated`, `resolved`, and source-health transitions, including the exact affected path. Use `--json` for a dashboard or relay; the ledger keeps the latest 1,000 transitions and deduplicates stable event ids.
163
+
53
164
  If you want to try the monitoring loop without booting a DSH profile, run one cycle from a reviewed inventory:
54
165
 
55
166
  ```bash
@@ -58,19 +169,286 @@ pnpm dlx --package=upstream-radar@latest upstream-radar radar watch ./upstream-r
58
169
 
59
170
  Remove `--once` to keep a local monitor alive. This is a lightweight CLI surface for demos, CI, and diagnosis; the native DSH bundle remains the recommended always-on path because it can deliver the task to a live Agent.
60
171
 
61
- <p align="center">
62
- <picture>
63
- <source media="(max-width: 600px)" srcset="docs/assets/upstream-radar-hero-mobile.jpg">
64
- <img src="docs/assets/upstream-radar-hero.jpg" alt="Upstream Radar watches a dependency graph, highlights one affected path, and routes one signal to a DSH Agent." width="100%">
65
- </picture>
66
- </p>
172
+ ## Check a DSH profile before starting it
173
+
174
+ When the concern is “will this profile boot with the packages and patch rows it
175
+ actually has?”, use the static profile check first:
176
+
177
+ ```bash
178
+ pnpm run build
179
+ node dist/src/cli.js profile-check "$DSH_HOME/profiles/web" \
180
+ --report ./dsh-profile-check.md
181
+ ```
182
+
183
+ For the shortest answer, add `--summary`:
184
+
185
+ ```bash
186
+ pnpm dlx --package=upstream-radar@latest upstream-radar profile-check \
187
+ "$DSH_HOME/profiles/web" --summary
188
+ ```
189
+
190
+ When `DSH_HOME` contains exactly one profile with third-party bundles, the
191
+ directory can be omitted:
192
+
193
+ ```bash
194
+ npx --yes upstream-radar@latest profile-check --summary
195
+ ```
196
+
197
+ With no eligible profile, or more than one, Radar prints the names it found and
198
+ asks for an explicit directory; it never guesses between multiple profiles.
199
+
200
+ It prints only the status, the important evidence, the reason, and the next
201
+ repair. The exit code remains `2` for a blocked profile and `0` for a pass.
202
+
203
+ It reads the profile manifest, pnpm/npm lockfile, package metadata,
204
+ `pnpm-workspace.yaml`, and `cordis.patch.yml`. It catches the two concrete
205
+ failure shapes from [dsh-web-ui #71](https://github.com/zhu1090093659/dsh-web-ui/issues/71)
206
+ and [#35](https://github.com/zhu1090093659/dsh-web-ui/issues/35): a loader row
207
+ that names a package absent from the locked profile, and the same loader id
208
+ being inserted twice. It also points out a pnpm `minimumReleaseAge` policy that
209
+ does not exempt the plugin, because that can keep a newly fixed plugin on an
210
+ older release during its cooling window.
211
+
212
+ This is deliberately a pre-start check: no network, installation, plugin code,
213
+ DSH process, Agent, or LLM is involved. A blocked result exits with code `2`.
214
+ The complete replay is `pnpm run showcase:dsh-profile-check`; it runs the
215
+ public case before the fix, after the manual package workaround, and after the
216
+ correct bundled-carrier fix.
217
+
218
+ The short, author-facing result is `pnpm run showcase:dsh-case`. It turns the
219
+ same three static checks into one repair story: the old profile is blocked,
220
+ manually adding the missing package creates a duplicate loader, and the
221
+ bundled-carrier update reaches `pass`. If an OpenAI-compatible `issue-locator`
222
+ model is available, pass its env file with
223
+ `ISSUE_LOCATOR_ENV_FILE=/path/to/issue-locator/.env`; the model only explains
224
+ the already-checked facts. If the endpoint is unavailable, the command still
225
+ prints the deterministic evidence explanation and records that the fallback was
226
+ used. Add `:report` to write the [case analysis result](examples/dsh/reports/dsh-web-ui-issue-71-analysis.json).
227
+
228
+ We also ran the current static checks against the first 50 entries in the DSH
229
+ plugin registry. That batch found **0 confirmed runtime dependency
230
+ vulnerabilities**. It did find real monitoring-quality problems—development-only
231
+ dependencies mixed into source lockfiles, three plugin lockfiles whose root
232
+ version lagged the source manifest, and a tarball format the scanner could not
233
+ parse. The [batch report](examples/dsh/reports/dsh-batch-50-2026-08-17.md)
234
+ keeps those results honest; it is not marketed as a vulnerability hit list.
235
+
236
+ ## Observe DSH plugin upstream changes
237
+
238
+ This is the upstream-change loop: instead of polling every vulnerability source on
239
+ every run, Radar remembers one observation point per plugin and asks what changed
240
+ since then.
241
+
242
+ ```text
243
+ targets.yml
244
+
245
+ GitHub commit + npm package metadata + optional lockfile
246
+
247
+ observations.json
248
+
249
+ old → new comparison
250
+
251
+ only meaningful changes → DSH Agent task → report
252
+ ```
253
+
254
+ Start from the [copyable target example](examples/upstream-observer/targets.yml):
255
+
256
+ ```yaml
257
+ schema: upstream-radar.observer-targets/v1alpha1
258
+ targets:
259
+ - id: my-dsh-plugin
260
+ ecosystem: dsh
261
+ repository: acme/my-dsh-plugin
262
+ ref: main
263
+ package: my-dsh-plugin
264
+ packagePath: plugin/package.json
265
+ lockfile: plugin/pnpm-lock.yaml
266
+ lockfileType: pnpm
267
+ ```
268
+
269
+ Then run one cycle:
270
+
271
+ ```bash
272
+ export GITHUB_TOKEN='a read-only GitHub token'
273
+ pnpm run build
274
+ node dist/src/cli.js observe \
275
+ ./targets.yml \
276
+ --state ./observations.json \
277
+ --report ./upstream-radar-observer.md
278
+ ```
279
+
280
+ This command uses the checked-out source so it is runnable before the next npm
281
+ release. After a release includes `observe`, pin that exact version in CI rather
282
+ than relying on `latest`.
283
+
284
+ The first cycle only creates a baseline. Later cycles compare:
285
+
286
+ - the source commit and changed files;
287
+ - the published npm version and integrity value;
288
+ - the package entrypoint, exports, Node requirement, DSH bundle metadata and dependency declarations;
289
+ - the real npm or pnpm lockfile graph, when a lockfile is configured.
290
+
291
+ README/docs/tests-only changes advance the observation point without waking the
292
+ Agent. Runtime source, DSH bundle, package entry, dependency graph, npm version,
293
+ or npm integrity changes create an old → new task. If the Agent is not configured,
294
+ the task stays in `observations.json`; no plugin is installed or executed.
295
+
296
+ If you do not have a DSH wrapper configured yet, you can point the observer at
297
+ an existing issue-locator/OpenAI-compatible `.env` file instead:
298
+
299
+ ```bash
300
+ upstream-radar observe ./targets.yml \
301
+ --state ./observations.json \
302
+ --llm-env-file /path/to/issue-locator/.env
303
+ ```
304
+
305
+ Radar reads only the endpoint, API key, and model name for that call. It never
306
+ writes the key or endpoint into the observation state or report. The model is
307
+ called only after a meaningful upstream change; a baseline or docs-only change
308
+ does not call it. If the endpoint is unavailable, the deterministic change
309
+ record remains pending and can be retried with `--retry-pending`.
310
+
311
+ The env file may use the issue-locator names (`ISSUE_LOCATOR_LLM_*`), the common
312
+ OpenAI names (`OPENAI_BASE_URL`, `OPENAI_API_KEY`, `OPENAI_MODEL`), or `MODEL` /
313
+ `CODEX_MODEL` for the model name.
314
+ For ModelBest-style URLs, a 404 on `/llm/v1` also retries `/llm/openai/v1`.
315
+
316
+ The scheduled workflow is [examples/github-actions/upstream-observer.yml](examples/github-actions/upstream-observer.yml).
317
+ The checked-in workflow is a dogfood workflow for this repository: it checks out
318
+ and builds Radar before running the observer. It persists only the observation
319
+ point. A quiet run does not create a daily commit.
320
+
321
+ The workflow also supports three optional repository secrets—
322
+ `ISSUE_LOCATOR_LLM_BASE_URL`, `ISSUE_LOCATOR_LLM_API_KEY`, and
323
+ `ISSUE_LOCATOR_LLM_MODEL`. When all three exist, the job sends meaningful tasks
324
+ to the issue-locator/OpenAI-compatible model. When they do not exist, static
325
+ upstream observation still runs and the job does not pretend that model analysis
326
+ was performed.
327
+
328
+ ### The DSH Agent boundary
329
+
330
+ The observer accepts an explicit executable through `--dsh-agent-command`. It
331
+ writes one bounded, read-only task prompt to stdin and expects one JSON conclusion
332
+ on stdout. The command is started without a shell, and the prompt treats every
333
+ remote repository string and release field as untrusted evidence.
334
+
335
+ ```bash
336
+ upstream-radar observe ./targets.yml \
337
+ --state ./observations.json \
338
+ --dsh-agent-command /path/to/reviewed-dsh-agent-wrapper \
339
+ --dsh-agent-arg --json
340
+ ```
341
+
342
+ Radar does not guess an undocumented `dsh` CLI subcommand. A reviewed DSH
343
+ headless wrapper is the integration boundary; this keeps the observer usable in
344
+ GitHub Actions and lets the DSH adapter evolve without changing observation or
345
+ diff logic. Use `--retry-pending` to deliver tasks left by a previous run.
346
+
347
+ ## Notify Feishu or an HTTPS endpoint
348
+
349
+ To also notify a team-owned HTTPS endpoint when an incident changes, keep the endpoint outside the reviewed config and state:
350
+
351
+ ```bash
352
+ export UPSTREAM_RADAR_WEBHOOK_URL='https://alerts.example.test/upstream-radar?token=replace-me'
353
+
354
+ # Native DSH path: the bundle reads the variable at runtime.
355
+ dsh --profile web --patch ./upstream-radar.dsh.yml
356
+
357
+ # Or use the CLI path for a persistent one-shot/continuous monitor.
358
+ pnpm dlx --package=upstream-radar@latest upstream-radar radar watch \
359
+ ./upstream-radar.config.json --webhook "$UPSTREAM_RADAR_WEBHOOK_URL"
360
+ ```
361
+
362
+ The webhook receives only `new`, `updated`, and `resolved` changes (including source-health changes) in the bounded `upstream-radar.webhook/v1alpha1` JSON format described by the [schema](schemas/webhook.schema.json). A successful HTTP 2xx response records the event id; a failed request remains retryable on the next cycle. The state stores only a SHA-256 endpoint fingerprint, delivery ids, and a bounded copy of events waiting for retry or a quiet window; it never stores the URL or its token. Vulnerability summaries include the same short priority evidence as `radar status`—CISA KEV, then EPSS, then severity—so a Feishu message does not require a second interpretation. For a normal endpoint, this provider-neutral JSON can be turned into a Feishu or Slack card by a relay. For a Feishu/Lark V2 custom bot, Radar recognizes the `/open-apis/bot/v2/hook/` URL and sends the native text body directly:
363
+
364
+ ```bash
365
+ export UPSTREAM_RADAR_WEBHOOK_URL='https://open.feishu.cn/open-apis/bot/v2/hook/replace-me'
366
+ # Only needed when the Feishu bot has signature validation enabled.
367
+ export UPSTREAM_RADAR_FEISHU_SECRET='replace-me'
368
+
369
+ dsh --profile web --patch ./upstream-radar.dsh.yml
370
+ ```
371
+
372
+ The Feishu secret is read only from the environment and is never written to the Radar config or state. Follow the [official Feishu custom-bot guide](https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN?lang=zh-CN) when creating the V2 bot. Use the V2 URL; the older `/open-apis/bot/hook/` form is rejected with an actionable error. Run `pnpm run showcase:webhook` to see deduplication and retry behavior without contacting a real endpoint.
373
+
374
+ For more than one monitored project, give each project its own environment-variable route. The config stores only the variable names, never the webhook URL or secret:
375
+
376
+ ```json
377
+ {
378
+ "project": {
379
+ "id": "payments-api",
380
+ "name": "Payments API",
381
+ "webhookUrlEnv": "UPSTREAM_RADAR_PAYMENTS_WEBHOOK_URL",
382
+ "webhookSecretEnv": "UPSTREAM_RADAR_PAYMENTS_FEISHU_SECRET"
383
+ }
384
+ }
385
+ ```
386
+
387
+ ```bash
388
+ export UPSTREAM_RADAR_PAYMENTS_WEBHOOK_URL='https://open.feishu.cn/open-apis/bot/v2/hook/replace-me'
389
+ export UPSTREAM_RADAR_PAYMENTS_FEISHU_SECRET='replace-me'
390
+
391
+ pnpm dlx --package=upstream-radar@latest upstream-radar setup \
392
+ --webhook-url-env UPSTREAM_RADAR_PAYMENTS_WEBHOOK_URL \
393
+ --webhook-secret-env UPSTREAM_RADAR_PAYMENTS_FEISHU_SECRET
394
+ ```
395
+
396
+ Each project's changed events go only to its configured endpoint. If two projects intentionally share one endpoint, Radar combines them into one delivery target; conflicting Feishu secrets fail the local `doctor` check instead of choosing one silently. The existing global `UPSTREAM_RADAR_WEBHOOK_URL` and CLI `--webhook` paths remain broadcast-compatible for a single team endpoint. Project webhook ledgers are kept separately under endpoint fingerprints, so a failed Payments delivery cannot acknowledge a Platform event.
397
+
398
+ ## Control notification noise without losing evidence
399
+
400
+ The generated inventory can hold ordinary notices while keeping the full incident, dependency path, history, and DSH task intact. For a first setup, use flags so you do not need to edit JSON by hand:
401
+
402
+ ```bash
403
+ pnpm dlx --package=upstream-radar@latest upstream-radar setup \
404
+ --minimum-severity high \
405
+ --quiet-hours 'Asia/Shanghai,22:00-08:00'
406
+ ```
407
+
408
+ `init --profile`, `init --pnpm-lock`, and `init --npm-lock` accept the same two flags. `--minimum-severity` accepts `info`, `low`, `medium`, `high`, or `critical`. `--quiet-hours` uses `<IANA timezone>,<HH:MM>-<HH:MM>`; the window may cross midnight. The equivalent generated block is:
409
+
410
+ ```json
411
+ {
412
+ "notificationPolicy": {
413
+ "minimumSeverity": "high",
414
+ "quietHours": {
415
+ "timezone": "Asia/Shanghai",
416
+ "start": "22:00",
417
+ "end": "08:00"
418
+ }
419
+ }
420
+ }
421
+ ```
422
+
423
+ `minimumSeverity` applies to vulnerability notices; `critical` vulnerabilities and malicious-package alerts always pass. `quietHours` uses the configured IANA timezone and also supports a window crossing midnight. Compatibility and source-health notices follow the quiet window but are not hidden by a vulnerability severity threshold. With a policy in effect, DSH tasks stay in the durable outbox until they can be delivered, and the webhook outbox keeps pending events for retry or a later policy change. `radar status` shows how many tasks are currently held. Omitting the block keeps the current behavior and delivers every notice. Run `pnpm run showcase:notifications` for a network-free proof of the hold, later delivery, and durable webhook outbox.
424
+
425
+ If one active incident is noisy, mute only that incident for a bounded period:
426
+
427
+ ```bash
428
+ upstream-radar radar next ./upstream-radar.config.json
429
+ upstream-radar mute './upstream-radar.config.json.state.json' '<incident-id>' \
430
+ --until '2026-08-17T12:00:00Z'
431
+ ```
432
+
433
+ This pauses only DSH and webhook delivery. The active incident, exact dependency path, history, and status remain visible; the mute expires automatically, and `radar next` prints the matching `unmute` command. A later event version is delivered again, so muting an old fact cannot hide a new fact. Critical and malware incidents require an explicit `--force`.
434
+
435
+ Record the human handoff next to the same incident:
436
+
437
+ ```bash
438
+ upstream-radar triage './upstream-radar.config.json.state.json' '<incident-id>' \
439
+ --status in-progress --owner security-team \
440
+ --note 'Trace the parser input path' \
441
+ --due '2026-08-17T12:00:00Z'
442
+ ```
443
+
444
+ The available states are `open`, `in-progress`, `blocked`, and `accepted-risk`. `blocked` and `accepted-risk` require a note. `--due` is an optional human deadline; `radar status` and `radar next` mark it as overdue when it passes. This is workflow context only: it never marks an active vulnerability resolved or suppresses its evidence. The record is bound to the exact event id, so an updated upstream fact requires a fresh review; `radar status` and `radar next` show the current owner, note, and deadline.
67
445
 
68
446
  ---
69
447
 
70
448
  A vulnerability feed stops at “package X is affected.” Upstream Radar keeps going: it identifies the exact installed dependency path, maintains one durable incident, and wakes a [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Agent with the project evidence needed for a useful investigation.
71
449
 
72
450
  ```text
73
- OSV advisory or npm release
451
+ OSV/GitHub Advisory or npm release
74
452
  -> exact installed plugin path
75
453
  -> new / updated / resolved incident
76
454
  -> project-specific DSH Agent analysis task
@@ -78,6 +456,8 @@ OSV advisory or npm release
78
456
 
79
457
  **No matching installed path means no Agent wake-up.** Version matching and compatibility facts are calculated by code; the model handles only repository-specific judgment.
80
458
 
459
+ Radar checks OSV and the GitHub Advisory Database as independent vulnerability sources. If both sources describe the same issue through a GHSA or CVE alias, Radar emits one incident and keeps the source identifiers, source list, and fix versions together; human-readable output says `Sources: OSV + GitHub Advisory Database` for this cross-confirmed case. If their severity or fixed-version claims differ, the event also says `Source conflict` and shows each claim instead of silently making the operator infer why multiple fixes are listed. If one source times out, the last confirmed vulnerability evidence and incident identity are retained; the source itself becomes a visible health incident after three consecutive failures instead of being treated as clean. The CLI and DSH adapter use the GitHub source by default, accept an optional `GITHUB_TOKEN` from the environment for API rate limits, and expose `--no-github-advisories` when an operator deliberately needs an OSV-only run. See the [GitHub Advisory Database API](https://docs.github.com/en/rest/security-advisories/global-advisories?apiVersion=2026-03-10) for the upstream query contract.
460
+
81
461
  ## The missing middle: candidate dependency graphs
82
462
 
83
463
  An upgrade can look clean at the top level while introducing a vulnerable transitive package. Radar therefore does not stop at `plugin@1.3.0`'s manifest:
@@ -90,6 +470,104 @@ candidate plugin@1.1.0
90
470
 
91
471
  For the earliest bounded set of newer versions, Radar resolves npm metadata into a temporary `package-lock.json` with lifecycle scripts disabled, queries every resolved node against OSV, and keeps the exact path in the compatibility event. A missing required edge, resolver failure, or OSV failure is shown as incomplete or unavailable; it is never presented as “no vulnerability found”. Later versions are marked as unchecked when the candidate list is larger than the bounded prefix. The result is still a starting point for DSH project analysis, not an upgrade certificate.
92
472
 
473
+ ## Inspect an npm or pnpm lockfile before installation
474
+
475
+ If a DSH plugin is managed with pnpm, inspect the exact locked tree before putting it into a DSH profile:
476
+
477
+ ```bash
478
+ pnpm dlx --package=upstream-radar@latest upstream-radar graph pnpm-lock \
479
+ ./pnpm-lock.yaml \
480
+ --json
481
+ ```
482
+
483
+ This reads only the lockfile. It does not run `pnpm install`, lifecycle scripts, plugin code, or network requests. It understands pnpm v6/v9 package locators, peer-context variants, duplicate versions, and a project root declared through the `importers` section. An unresolved or ambiguous dependency remains visible instead of being guessed away. The JSON is the same canonical graph shape used by Radar's OSV path matching, so a CI job can review the graph before the plugin is admitted to DSH. Run `pnpm run showcase:pnpm-lock` for a real repository example.
484
+
485
+ To turn that graph into a monitorable inventory and run the first vulnerability check:
486
+
487
+ ```bash
488
+ pnpm dlx --package=upstream-radar@latest upstream-radar init \
489
+ --pnpm-lock ./pnpm-lock.yaml \
490
+ --project-name "My DSH plugin"
491
+
492
+ pnpm dlx --package=upstream-radar@latest upstream-radar radar check \
493
+ ./upstream-radar.config.json --frozen --fail-on high
494
+ ```
495
+
496
+ `init --pnpm-lock` does not need a DSH profile and writes a normal Radar config; `radar check` then queries exact locked versions and emits the same DSH-ready event shape. Use the native DSH `setup` path when the plugin is installed and should receive follow-up analysis in a live Agent. Run `pnpm run showcase:pnpm-lock:monitor` to see the complete lockfile-to-OSV event locally.
497
+
498
+ When `package.json` sits beside `pnpm-lock.yaml`, `--root` can be omitted; Radar reads the exact package name and version from that manifest. Keep `--root` when the lockfile belongs to another workspace root or when you want the admission coordinate to be explicit.
499
+
500
+ The same path works for npm projects with a committed `package-lock.json`:
501
+
502
+ ```bash
503
+ pnpm dlx --package=upstream-radar@latest upstream-radar graph npm-lock \
504
+ ./package-lock.json --json
505
+
506
+ pnpm dlx --package=upstream-radar@latest upstream-radar init \
507
+ --npm-lock ./package-lock.json \
508
+ --project-name "My DSH plugin"
509
+ ```
510
+
511
+ For an npm project root, Radar reads `packages[""]` from the lockfile and ignores the root package's development-only dependencies. The command still does not install packages, run lifecycle scripts, load plugin code, or make network requests until the subsequent `radar check` queries OSV.
512
+
513
+ Run `pnpm run showcase:npm-lock:monitor` for a deterministic local proof of this npm lockfile-to-OSV-to-DSH event path.
514
+
515
+ ## For DSH plugin authors
516
+
517
+ If you start with the real [`create-dsh-plugin`](https://www.npmjs.com/package/create-dsh-plugin) scaffold, the shortest review-first path is:
518
+
519
+ ```bash
520
+ npx create-dsh-plugin my-dsh-plugin -t tool --yes --skip-install
521
+ cd my-dsh-plugin
522
+ pnpm install --ignore-scripts
523
+
524
+ # Read the exact graph before adding the plugin to a DSH profile.
525
+ pnpm dlx --package=upstream-radar@0.33.1 upstream-radar graph pnpm-lock pnpm-lock.yaml --json
526
+ ```
527
+
528
+ The graph includes the exact DSH package versions and keeps unresolved optional peers visible. It does not load the generated plugin or run lifecycle scripts. After reviewing it, copy this complete workflow into `.github/workflows/upstream-radar.yml`:
529
+
530
+ ```yaml
531
+ name: Upstream Radar
532
+
533
+ on:
534
+ workflow_dispatch:
535
+ pull_request:
536
+ schedule:
537
+ - cron: '17 6 * * *'
538
+
539
+ permissions:
540
+ contents: read
541
+
542
+ jobs:
543
+ dependency-radar:
544
+ runs-on: ubuntu-latest
545
+ steps:
546
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
547
+ - uses: MicroMilo/upstream-radar@v0.33.1
548
+ with:
549
+ fail-on: high
550
+ fail-on-compatibility: breaking
551
+ ```
552
+
553
+ The Action auto-detects the one `pnpm-lock.yaml`, checks the same exact graph, and writes the result to the Job Summary. This is a pre-install and CI gate; it does not install the plugin into DSH. After the graph is reviewed, use the normal `dsh plugin` flow to install it and `upstream-radar setup` to start project-aware monitoring.
554
+
555
+ To check a real published DSH artifact directly, run one command:
556
+
557
+ ```bash
558
+ npx --yes upstream-radar@0.33.1 inspect npm:dsh-feishu-bot@0.15.4 --deep
559
+ ```
560
+
561
+ The checked result is `REVIEW`: registry integrity, signature, provenance, and
562
+ 89 resolved packages are verified; known vulnerabilities are `0`, while 12
563
+ optional dependency edges remain unresolved. That is a useful author result:
564
+ the empty vulnerability list is visible, but it is not mislabeled as a full
565
+ `ALLOW` decision.
566
+
567
+ The same exact tarball loads in both DSH `0.1.0-rc.6` and `0.1.0-rc.7` disposable
568
+ profiles. See the [real compatibility probe](examples/dsh/reports/dsh-feishu-bot-0.15.4-probe.md)
569
+ for the command and boundary.
570
+
93
571
  ## See one incident
94
572
 
95
573
  If an advisory affects only one of two installed `parser` versions, Radar reports the path that actually matched:
@@ -101,13 +579,30 @@ Plugin: plugin@1.0.0
101
579
  Affected: parser@2.9.0
102
580
  Origin: plugin profile
103
581
  Advisory: GHSA-demo-2026-parser / CVE-2026-1234
582
+ Sources: OSV + GitHub Advisory Database
583
+ Source conflict: fixed versions — OSV=3.0.0; GitHub Advisory Database=3.1.0
104
584
  Paths:
105
585
  plugin@1.0.0 -> logger@4.0.2 -> parser@2.9.0
106
- Fixed versions: 3.0.0
586
+ Fixed versions: 3.0.0, 3.1.0
107
587
  Route: payments-platform via feishu:payments-security
108
588
  ```
109
589
 
110
- That incident becomes a plugin-originated DSH notice. It is not copied into a generic chatbot prompt.
590
+ That incident becomes a plugin-originated DSH notice. Radar keeps both source claims visible instead of silently picking one fixed version; the DSH Agent then decides which fix is appropriate for the project. It is not copied into a generic chatbot prompt.
591
+
592
+ For a CVE, native DSH also adds two prioritization signals:
593
+
594
+ ```text
595
+ Threat signal: CISA KEV lists this CVE as exploited in the wild.
596
+ FIRST EPSS estimated exploitation probability: 97.2% (percentile 100.0%)
597
+ ```
598
+
599
+ [CISA KEV](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) answers “is this CVE known to be exploited in the wild?” [FIRST EPSS](https://www.first.org/epss/) gives a daily estimate of exploitation probability and relative percentile. They help the Agent and the team decide what to inspect first; they do not change the exact dependency match, and a missing signal is not a safety certificate.
600
+
601
+ To replay the two signals and a source outage without network access:
602
+
603
+ ```bash
604
+ pnpm run showcase:threat-intel
605
+ ```
111
606
 
112
607
  | Upstream signal | Radar proves deterministically | DSH Agent investigates |
113
608
  | --- | --- | --- |
@@ -120,13 +615,17 @@ Upstream Radar is an npm-published DSH bundle, so no install-time build permissi
120
615
 
121
616
  ```bash
122
617
  pnpm dlx --package=upstream-radar@latest upstream-radar setup \
123
- --profile web \
124
- --project-name "My DSH project" \
125
- --output ./upstream-radar.config.json \
126
- --dsh-patch ./upstream-radar.dsh.yml
618
+ --project-name "My DSH project"
127
619
  ```
128
620
 
129
- `setup` delegates the package installation to DSH using the exact Radar version currently being run. It then generates the inventory and overlay and runs `doctor` locally without contacting OSV, npm, or GitHub. It does not start DSH. Review the two generated files, then start the profile:
621
+ When exactly one DSH profile contains third-party bundles, `setup` selects it automatically; pass `--profile <name>` when more than one profile is eligible. `setup` delegates the package installation to DSH using the exact Radar version currently being run. It then generates `upstream-radar.config.json` and `upstream-radar.dsh.yml` by default, and runs `doctor` locally without contacting OSV, npm, or GitHub. By default it does not start DSH. The explicit one-command path is:
622
+
623
+ ```bash
624
+ pnpm dlx --package=upstream-radar@latest upstream-radar setup \
625
+ --project-name "My DSH project" --start
626
+ ```
627
+
628
+ For the review-first path, start the generated overlay yourself:
130
629
 
131
630
  ```bash
132
631
  dsh --profile web --patch ./upstream-radar.dsh.yml --dump-config
@@ -146,15 +645,23 @@ pnpm dlx --package=upstream-radar@latest upstream-radar doctor ./upstream-radar.
146
645
  --patch ./upstream-radar.dsh.yml
147
646
  ```
148
647
 
149
- `doctor` does not contact OSV, npm, GitHub, or execute plugin code. It checks that the config parses, the selected DSH profile actually registers `upstream-radar`, the overlay points to the same config and state files, the dependency coverage is complete, and the durable state can be read. It exits non-zero only for a blocked setup; a missing first-run state is shown as a warning with the next command to run. Add `--json` when another tool needs the result.
648
+ `doctor` does not contact OSV, npm, GitHub, or execute plugin code. It checks that the config parses, the selected DSH profile actually registers `upstream-radar`, the overlay points to the same config and state files, the dependency coverage is complete, and the durable state can be read. When `UPSTREAM_RADAR_WEBHOOK_URL` is set, it also checks the HTTPS route locally, recognizes the native Feishu/Lark V2 path, and blocks the retired V1 path before the first poll; it never prints the URL or `UPSTREAM_RADAR_FEISHU_SECRET`. It exits non-zero only for a blocked setup; a missing first-run state is shown as a warning with the next command to run. Add `--json` when another tool needs the result.
649
+
650
+ The generated overlay points DSH at the config and state files explicitly and records the selected profile. If `--registry <url>` was used during initialization, the same registry is carried into the running DSH monitor; otherwise release and candidate checks use the public npm registry. Before each native DSH polling cycle, and before each CLI `radar check` or `radar watch` cycle, it re-reads that profile's installed graph, so later plugin installs, upgrades, removals, and host-runtime changes are not silently missed. If the refresh fails, that cycle stops without replacing the last durable state. `radar status` remains read-only and reports whether a check has completed, which source is unhealthy, whether dependency coverage is complete, the most important active incidents with their exact path or candidate, a suggested next step, pending DSH tasks, and verified model conclusions. Its Attention list is ordered by CISA KEV evidence, then EPSS score, then advisory severity; each vulnerability line shows the evidence that was actually available. Missing signals are not treated as a safety claim. `radar history` is also read-only and shows the bounded transition ledger from the same state file, including resolved incidents that no longer appear in the active summary. You can inspect a stored conclusion with `upstream-radar analysis list <state.json>` or `analysis show <state.json>`. Radar accepts a conclusion only when the response is strict JSON from the matching DSH model session; it never treats arbitrary chat as an analysis result. `radar compare` remains a manual comparison of the files you provide. If you prefer environment variables or need to override the polling interval, omit `--dsh-patch` and use `UPSTREAM_RADAR_CONFIG`, `UPSTREAM_RADAR_STATE`, `UPSTREAM_RADAR_INTERVAL_SECONDS`, `UPSTREAM_RADAR_REGISTRY`, and `UPSTREAM_RADAR_DEEP_CANDIDATES` as before.
150
651
 
151
- The generated overlay points DSH at the config and state files explicitly and records the selected profile. If `--registry <url>` was used during initialization, the same registry is carried into the running DSH monitor; otherwise release and candidate checks use the public npm registry. Before each native DSH polling cycle, and before each CLI `radar check` or `radar watch` cycle, it re-reads that profile's installed graph, so later plugin installs, upgrades, removals, and host-runtime changes are not silently missed. If the refresh fails, that cycle stops without replacing the last durable state. `radar status` remains read-only and reports whether a check has completed, which source is unhealthy, whether dependency coverage is complete, the most important active incidents with their exact path or candidate, a suggested next step, pending DSH tasks, and verified model conclusions. You can inspect a stored conclusion with `upstream-radar analysis list <state.json>` or `analysis show <state.json>`. Radar accepts a conclusion only when the response is strict JSON from the matching DSH model session; it never treats arbitrary chat as an analysis result. `radar compare` remains a manual comparison of the files you provide. If you prefer environment variables or need to override the polling interval, omit `--dsh-patch` and use `UPSTREAM_RADAR_CONFIG`, `UPSTREAM_RADAR_STATE`, `UPSTREAM_RADAR_INTERVAL_SECONDS`, `UPSTREAM_RADAR_REGISTRY`, and `UPSTREAM_RADAR_DEEP_CANDIDATES` as before.
652
+ When you only want the next action after an alert, use the shorter read-only view:
653
+
654
+ ```bash
655
+ pnpm dlx --package=upstream-radar@latest upstream-radar radar next ./upstream-radar.config.json
656
+ ```
152
657
 
153
- The generated graph is the actual installed profile graph. During a native DSH run, Radar also reads the exact DSH CLI entrypoint (`@deepseek-ai/dsh/lib/bin.js`) and discovers the `node_modules` plane that process is using. It does this with bounded, read-only manifest checks: it does not import DSH, load a plugin, or run an install hook. Packages resolved from that plane are marked as `dsh-host`, and their exact versions are checked for advisories. `radar status` says whether the host plane came from the running DSH process or the profile fallback. If a required dependency is declared but cannot be resolved from either place, it remains visible as incomplete coverage instead of being treated as absent. Missing optional platform packages are retained as evidence but do not make coverage incomplete. Passing `--registry <url>` explicitly selects the older public npm artifact graph path, which is useful for comparing a profile against registry resolution but is not the default.
658
+ It selects the same first incident as `radar status`, then points to its queued DSH task, verified analysis, or the next check command. When a DSH conclusion is already verified, the output also prints its urgency, recommendation, and bounded evidence list so the operator can act without opening a second report. If a task is queued, it also prints the explicit `task ack` command; acknowledging removes only that delivery item, not the active incident or its evidence.
659
+
660
+ The generated graph is the actual installed profile graph. During a native DSH run, Radar also reads the exact DSH CLI entrypoint (`@deepseek-ai/dsh/lib/bin.js`) and discovers the `node_modules` plane that process is using. It does this with bounded, read-only manifest checks: it does not import DSH, load a plugin, or run an install hook. Packages resolved from that plane are marked as `dsh-host`, and their exact versions are checked for advisories. Radar also records the exact `@deepseek-ai/dsh` executable package that owns the plane, so the DSH core and its reachable host dependencies get OSV and npm release checks even when they are not declared plugin dependencies. The graph uses an explicit `host-runtime` boundary edge; a host finding is never presented as an ordinary plugin dependency. `radar status` says whether the host plane came from the running DSH process or the profile fallback. If a required dependency is declared but cannot be resolved from either place, it remains visible as incomplete coverage instead of being treated as absent. Missing optional platform packages are retained as evidence but do not make coverage incomplete. Passing `--registry <url>` explicitly selects the older public npm artifact graph path, which is useful for comparing a profile against registry resolution but is not the default.
154
661
 
155
662
  For a hand-written or CI fixture, use [the example inventory](examples/radar/config.json). If neither a generated `--patch` overlay nor `UPSTREAM_RADAR_CONFIG` is provided, the bundle stays dormant and performs no polling.
156
663
 
157
- Once running, Radar polls OSV, npm, and public GitHub Releases, persists incident state before delivery, and submits only changed incidents to the matching DSH project session. With one root Agent, delivery remains automatic; with multiple roots, Radar requires an exact match between `project.workspace` and `Agent.session.header.cwd`, and keeps the task queued when it cannot prove the route. The native adapter records the exact message id, DSH session, task id, and event id; it writes back only a matching `assistant/message` from that session whose visible text is the six-field JSON result. A new or updated upstream event invalidates the previous result, so an old model conclusion cannot survive a changed dependency fact. If a source is temporarily unavailable, Radar keeps the last confirmed state instead of claiming that the project is clean, continues delivering already queued tasks, and creates one source-health notice after three consecutive failures.
664
+ Once running, Radar polls OSV, GitHub Advisory Database, npm, and public GitHub Releases, then queries CISA KEV and FIRST EPSS for matched CVEs. Native DSH enables those two prioritization feeds by default; set `UPSTREAM_RADAR_THREAT_INTEL=false` when a lean run should omit them. The signals do not decide whether a package is vulnerable: they only explain which confirmed incidents deserve attention first. Radar persists incident state before delivery, and submits only changed incidents to the matching DSH project session. With one root Agent, delivery remains automatic; with multiple roots, Radar requires an exact match between `project.workspace` and `Agent.session.header.cwd`, and keeps the task queued when it cannot prove the route. The native adapter records the exact message id, DSH session, task id, and event id; it writes back only a matching `assistant/message` from that session whose visible text is the six-field JSON result. A new or updated upstream event invalidates the previous result, so an old model conclusion cannot survive a changed dependency fact. If a source is temporarily unavailable, Radar keeps the last confirmed state instead of claiming that the project is clean, continues delivering already queued tasks, and creates one source-health notice for that source after three consecutive failures.
158
665
 
159
666
  Each release cycle also checks a bounded prefix of candidate dependency graphs. This is enabled by default in the DSH adapter and CLI; use `--no-deep-candidates` only when you deliberately want manifest-only compatibility checks. The graph resolver is isolated in a temporary directory and uses `package-lock-only` plus `ignore-scripts`, so candidate package code is not loaded or executed.
160
667
 
@@ -188,12 +695,24 @@ The command fails unless DSH proves all five facts:
188
695
 
189
696
  This proof runs in CI on Node.js 22. See the executable [showcase contract](examples/dsh/README.md) and its checked-in [result](examples/dsh/reports/headless-smoke.json). Run `pnpm run try:dsh:live` to include a current OSV and npm poll before the DSH handoff.
190
697
 
698
+ To demonstrate the host-runtime dependency path specifically, run `pnpm run showcase:dsh-runtime`. It starts a real DSH `headless` process, begins at the exact DSH executable package, walks the reachable host dependency closure behind an explicit `host-runtime` boundary, queries a local OSV-compatible feed for the real `@deepseek-ai/cordis` version, persists a `dsh-host` vulnerability path, and hands it to the DSH Agent. The model and advisory are deterministic local stubs; this proves integration and provenance, not the safety of a real advisory.
699
+
700
+ To see why one shared host bug should not page every plugin separately, run `pnpm run showcase:dsh-host-alert`. Two plugin roots share the same exact `@deepseek-ai/cordis` version; Radar emits one project event, keeps both exact paths, and creates one DSH analysis task. Add `:report` to refresh the checked-in [deduplication result](examples/dsh/reports/dsh-host-alert-dedup.json).
701
+
702
+ To validate the actual first-use path against several real published DSH plugins, run `pnpm run showcase:dsh-adoption`. It creates a disposable `DSH_HOME`, packs exact Radar and plugin tarballs with lifecycle scripts disabled, lets DSH build its own host runtime, runs `setup --no-install`, `doctor`, a frozen OSV/npm/GitHub check, and the human-readable status surface. The checked-in trial covers [`dsh-cloudflare-browser-run@0.1.1`](https://www.npmjs.com/package/dsh-cloudflare-browser-run), [`@open-agfs/dsh-agfs@0.1.9`](https://www.npmjs.com/package/@open-agfs/dsh-agfs), and [`dsh-feishu-bot@0.14.0`](https://www.npmjs.com/package/dsh-feishu-bot). The first two install and become monitorable; the Feishu bridge is intentionally recorded as blocked because a clean DSH profile stops on its transitive `protobufjs` build script until a human approves it. A blocked install is not reported as a clean security result. The showcase does not start a DSH Agent or call a model; the separate `try:dsh` proof covers that handoff. The checked-in [adoption result](examples/dsh/reports/adoption-smoke.json) records each plugin's install state, graph coverage, source health, and boundary.
703
+
704
+ The DSH Agent handoff is optional for dependency analysis. If you want to smoke-test that integration with a real published plugin, run `pnpm run try:dsh:real`. It installs the exact published `dsh-find-plugin@0.3.6` into a disposable headless profile and proves that a real DSH Agent receives, consumes, and writes back a Radar analysis task. The model is still a local deterministic stub; no plugin business action or paid endpoint is called. Set `DSH_REAL_PLUGINS` to another exact package only after checking its required profile and credentials.
705
+
706
+ For a public compatibility case, run `pnpm run try:dsh:public-case`. It replays [`dsh-web-ui #35`](https://github.com/zhu1090093659/dsh-web-ui/issues/35) and [`#71`](https://github.com/zhu1090093659/dsh-web-ui/issues/71): the old profile is blocked by a missing loader, the manual package workaround is blocked by a duplicate loader id, and the maintainer's bundled-carrier fix passes. The same compatibility event is then admitted to a real DSH `headless` session and written back as one verified `analysisResult`; see the checked-in [case result](examples/dsh/reports/dsh-web-ui-public-case.json). This is an end-to-end delivery proof with a local deterministic model stub, not a claim about online model quality. The command does not use your DSH credentials or call a paid model endpoint.
707
+
708
+ To see the two-source vulnerability contract without contacting the network, run `pnpm run showcase:github-advisories`. It feeds the same parser issue through OSV and a deterministic GitHub Advisory Database client, proves that two reports become one Radar incident with explicit source provenance and a visible fixed-version conflict, then simulates three GitHub failures and recovery. The existing vulnerability remains active throughout; only the GitHub source-health incident changes.
709
+
191
710
  ## Validate the compatibility rules
192
711
 
193
712
  Before wiring a project into a compatibility gate, run the offline rule benchmark:
194
713
 
195
714
  ```bash
196
- pnpm dlx --package=upstream-radar@0.32.0 upstream-radar benchmark compatibility
715
+ pnpm dlx --package=upstream-radar@0.33.1 upstream-radar benchmark compatibility
197
716
  ```
198
717
 
199
718
  It covers six contracts: a safe patch, a change that only needs project analysis, an incompatible DSH peer, a publisher-declared breaking release, a vulnerable candidate dependency, and an incomplete candidate graph. The command does not access the network, install a package, load a plugin, or start DSH. It checks the behavior of Radar's deterministic rules and the `breaking`/`any` gates; it is not a runtime compatibility proof.
@@ -206,7 +725,7 @@ When you have an exact plugin artifact and want to know whether one exact DSH re
206
725
  # Pack an exact npm release without running its lifecycle scripts.
207
726
  npm pack --ignore-scripts dsh-plugin@1.2.3
208
727
 
209
- pnpm dlx --package=upstream-radar@0.32.0 upstream-radar probe dsh-load \
728
+ pnpm dlx --package=upstream-radar@0.33.1 upstream-radar probe dsh-load \
210
729
  ./dsh-plugin-1.2.3.tgz \
211
730
  --dsh-version 0.1.0-rc.6
212
731
  ```
@@ -232,7 +751,7 @@ It exercises a loadable bundle, a bundle patch DSH rejects, and a package that r
232
751
  To compare a plugin against more than one DSH release, use the matrix form:
233
752
 
234
753
  ```bash
235
- pnpm dlx --package=upstream-radar@0.32.0 upstream-radar probe dsh-matrix \
754
+ pnpm dlx --package=upstream-radar@0.33.1 upstream-radar probe dsh-matrix \
236
755
  ./dsh-plugin-1.2.3.tgz \
237
756
  --dsh-version 0.1.0-rc.3 \
238
757
  --dsh-version 0.1.0-rc.6 \
@@ -243,25 +762,62 @@ The matrix runs versions one at a time in separate temporary profiles and evalua
243
762
 
244
763
  ## Run it in GitHub Actions
245
764
 
246
- If your team wants a scheduled CI gate before wiring a machine to a live DSH profile, commit the reviewed `upstream-radar.config.json` and copy [the example workflow](examples/github-actions/upstream-radar.yml). The reusable Action keeps the workflow to two meaningful steps, with an optional third step for DSH load compatibility:
765
+ If your team wants the shortest scheduled CI gate before wiring a machine to a live DSH profile, copy [the example workflow](examples/github-actions/upstream-radar.yml). It auto-detects the only `pnpm-lock.yaml` or `package-lock.json` after checkout, so no Radar config is required for the first run. If you already maintain a reviewed `upstream-radar.config.json`, pass it explicitly instead. The reusable Action keeps the workflow to two meaningful steps, with an optional third step for DSH load compatibility:
247
766
 
248
767
  ```yaml
249
768
  steps:
250
769
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
251
- - uses: MicroMilo/upstream-radar@v0.32.0
770
+ - uses: MicroMilo/upstream-radar@v0.33.1
252
771
  with:
253
- config: upstream-radar.config.json
254
772
  fail-on: high
255
773
  # Optional: also fail on deterministic DSH/plugin compatibility breaks.
256
774
  fail-on-compatibility: breaking
775
+ # Optional: add CISA KEV and FIRST EPSS signals to matched CVEs.
776
+ threat-intel: true
777
+ ```
778
+
779
+ The Action is a thin wrapper around `radar check --frozen --state :memory: --fail-on high --json`; when the optional compatibility input is enabled, it also passes `--fail-on-compatibility breaking` or `any`. `--frozen` is deliberate: it uses the graph in the reviewed config and does not try to read a developer's local DSH profile. `threat-intel` is false by default so an ordinary CI gate stays lean; set it to `true` when the Job Summary and raw JSON should include CISA KEV and FIRST EPSS prioritization evidence. Each run is independent, exits `2` when an active vulnerability or opted-in compatibility change meets its threshold, and exits `1` for an operational or source error. `breaking` catches confirmed or strong incompatibility signals; `any` catches every active compatibility event. The default is `never`, so vulnerability-only behavior stays unchanged. In addition to the raw JSON log, the Action writes a short escaped summary to the GitHub Job Summary so a scheduled failure immediately shows the affected package, exact path, published fix version when available, one-line priority evidence, and a suggested next step. The Action does not deliver a DSH Agent task or modify a branch; the native DSH bundle remains the always-on analysis path. Pin the Action to a release tag such as `v0.33.1`, and pin the checkout Action in your workflow according to your repository's policy.
780
+
781
+ If the repository has no committed Radar config yet, the smallest setup is to omit `config`, `pnpm-lock`, and `npm-lock`. After checkout, the Action automatically uses the only one of `pnpm-lock.yaml` or `package-lock.json` that exists, generates a temporary reviewed config, and runs the same frozen check:
782
+
783
+ ```yaml
784
+ - uses: MicroMilo/upstream-radar@v0.33.1
785
+ with:
786
+ fail-on: high
787
+ ```
788
+
789
+ An existing `config` wins over auto-detection. If both lockfiles exist, or neither a config nor a supported lockfile exists, the Action stops with a direct message instead of guessing.
790
+
791
+ To review the exact plugin artifact before it enters DSH, add `inspect-package`:
792
+
793
+ ```yaml
794
+ - uses: MicroMilo/upstream-radar@v0.33.1
795
+ with:
796
+ inspect-package: dsh-cloudflare-browser-run@0.1.1
797
+ # review is the safe default; use block only when incomplete coverage may pass.
798
+ inspect-fail-on: review
257
799
  ```
258
800
 
259
- The Action is a thin wrapper around `radar check --frozen --state :memory: --fail-on high --json`; when the optional compatibility input is enabled, it also passes `--fail-on-compatibility breaking` or `any`. `--frozen` is deliberate: it uses the graph in the reviewed config and does not try to read a developer's local DSH profile. Each run is independent, exits `2` when an active vulnerability or opted-in compatibility change meets its threshold, and exits `1` for an operational or source error. `breaking` catches confirmed or strong incompatibility signals; `any` catches every active compatibility event. The default is `never`, so vulnerability-only behavior stays unchanged. The Action does not deliver a DSH Agent task or modify a branch; the native DSH bundle remains the always-on analysis path. Pin the Action to a release tag such as `v0.32.0`, and pin the checkout Action in your workflow according to your repository's policy.
801
+ This downloads that exact npm tarball, verifies the registry integrity/signature and provenance when available, resolves dependencies with lifecycle scripts disabled, and puts the admission verdict, coverage, findings, and next step in the Job Summary. Write the input as `package@version`; the Action adds the internal `npm:` prefix. It does not install or execute the plugin. The optional `inspect-verdict` output exposes `allow`, `warn`, `review`, or `block` to later workflow steps. An empty finding list with incomplete coverage remains a review result, not a safety certificate.
802
+
803
+ If the repository has a pnpm lockfile but no committed Radar config yet, the Action can generate the config in the same job. See the [copyable pnpm workflow](examples/github-actions/upstream-radar-pnpm.yml):
804
+
805
+ ```yaml
806
+ - uses: MicroMilo/upstream-radar@v0.33.1
807
+ with:
808
+ pnpm-lock: pnpm-lock.yaml
809
+ fail-on: high
810
+ ```
811
+
812
+ This mode runs `init --pnpm-lock` first and then the same frozen check. `root` is optional when `package.json` is beside the lockfile, and can be supplied for an explicit or non-adjacent workspace root. It never installs the project or executes the plugin; `config` is the output path (default `upstream-radar.config.json`). Leave `pnpm-lock` empty to keep the reviewed-config mode above.
813
+
814
+ Set `npm-lock: package-lock.json` instead for npm projects; `pnpm-lock` and `npm-lock` are mutually exclusive. Both modes infer the root from the adjacent `package.json` unless `root` is supplied.
815
+ See the [copyable npm workflow](examples/github-actions/upstream-radar-npm.yml) for that form.
260
816
 
261
817
  The Action requires the caller to check out the repository first. It does not install the project's dependencies or run their lifecycle scripts; it only reads the committed graph and queries the configured upstream sources. For a fully explicit, lower-level invocation, the equivalent command is:
262
818
 
263
819
  ```bash
264
- pnpm dlx --package=upstream-radar@0.32.0 upstream-radar radar check \
820
+ pnpm dlx --package=upstream-radar@0.33.1 upstream-radar radar check \
265
821
  ./upstream-radar.config.json --frozen --state :memory: --fail-on high \
266
822
  --fail-on-compatibility breaking --json
267
823
  ```
@@ -269,7 +825,7 @@ pnpm dlx --package=upstream-radar@0.32.0 upstream-radar radar check \
269
825
  To add the optional DSH load matrix for a published plugin, provide an exact npm package and at least two exact DSH versions:
270
826
 
271
827
  ```yaml
272
- - uses: MicroMilo/upstream-radar@v0.32.0
828
+ - uses: MicroMilo/upstream-radar@v0.33.1
273
829
  id: radar
274
830
  with:
275
831
  config: upstream-radar.config.json
@@ -282,18 +838,20 @@ The Action packs the exact package with `--ignore-scripts`, runs `probe dsh-matr
282
838
 
283
839
  For a runnable consumer example using the real [`dsh-cloudflare-browser-run@0.1.1`](examples/github-actions/consumer/upstream-radar.config.json) graph, see the [consumer smoke README](examples/github-actions/consumer/README.md) and its [copyable workflow](examples/github-actions/consumer/upstream-radar.yml).
284
840
 
285
- Run the same released Action locally from this repository with:
841
+ Run the consumer path locally from this repository with:
286
842
 
287
843
  ```bash
288
844
  pnpm run try:consumer
289
845
  ```
290
846
 
847
+ This builds the current checkout and runs the same frozen consumer check with the local CLI, so it also works before the next npm version is published. To deliberately verify the public npm artifact instead, run `pnpm run try:consumer:published`; it resolves the package version in `package.json` from npm and should only be used after that version exists publicly.
848
+
291
849
  For a local or self-hosted DSH machine, omit `--frozen` so Radar refreshes the selected profile before each cycle. Use `--fail-on` or `--fail-on-compatibility` only with `radar check`, `radar status`, or `radar watch --once`; a long-running watch should continue routing incidents instead of terminating on the first one.
292
850
 
293
851
  ## How the loop works
294
852
 
295
853
  1. Read the project inventory and exact installed npm graph.
296
- 2. Query OSV with every installed `name@version` pair.
854
+ 2. Query OSV and GitHub Advisory Database with every installed `name@version` pair, then merge matching GHSA/CVE aliases while preserving which source(s) confirmed the result. Native DSH also queries CISA KEV and FIRST EPSS for matched CVEs; CLI and Action users opt in with `--threat-intel` or `threat-intel: true`.
297
855
  3. Watch npm releases for the installed plugin and DSH/Cordis packages.
298
856
  4. Create or update one durable incident with the exact dependency path.
299
857
  5. Persist a constrained analysis task before delivery.
@@ -370,7 +928,7 @@ which API or Cordis configuration would the upgrade disturb?
370
928
  what is the least disruptive project-specific action?
371
929
  ```
372
930
 
373
- When one DSH runtime release changes several `@deepseek-ai/dsh-*` packages, Radar keeps each package as an independent state record but combines the same project's notices into one Agent analysis. You get one coherent upgrade question without losing the exact package evidence needed for later resolution. The resulting conclusion is copied back to each still-current incident only after the grouped model response passes the same strict validation.
931
+ When one DSH runtime release changes `@deepseek-ai/dsh`, several `@deepseek-ai/dsh-*` packages, or Cordis packages, Radar keeps each package as an independent state record but combines the same project's notices into one Agent analysis. You get one coherent upgrade question without losing the exact package evidence needed for later resolution. The resulting conclusion is copied back to each still-current incident only after the grouped model response passes the same strict validation.
374
932
 
375
933
  Advisories, release notes, links, package names, and repository strings remain untrusted data. The generated task requires read-only analysis, project evidence, explicit uncertainty, and a fixed [result schema](schemas/analysis-result.schema.json).
376
934
 
@@ -378,20 +936,30 @@ Advisories, release notes, links, package names, and repository strings remain u
378
936
 
379
937
  - installed DSH `node_modules` graphs and npm lockfile graphs with duplicate versions and bounded dependency paths;
380
938
  - DSH shared host-runtime dependency resolution discovered from the running DSH process, with profile and `dsh-host` packages kept distinct in both graphs and alerts;
939
+ - exact `@deepseek-ai/dsh` executable-package evidence, including host-boundary OSV alerts and its own npm compatibility stream;
940
+ - one project-level alert for a shared DSH host-runtime vulnerability, retaining every affected plugin root and exact path instead of sending duplicate per-plugin notices;
381
941
  - exact-version OSV vulnerability and malicious-package matching;
942
+ - independent GitHub Advisory Database matching for exact npm versions, with GHSA/CVE alias deduplication, merged fix versions, and source-specific health;
382
943
  - npm release monitoring for plugins and DSH/Cordis packages, accepting only a candidate newer than the installed exact version (a regressed `latest` dist-tag is not a breaking update), with public GitHub Release notes attached when an exact candidate tag is available;
383
944
  - bounded transitive dependency graph checks for the earliest candidate versions, exact OSV matching for every resolved node, vulnerable path evidence, and explicit incomplete/unavailable coverage;
384
945
  - durable incident state with current-task replacement and resolution;
946
+ - a bounded transition history with a local `radar history` audit command;
385
947
  - strict DSH result writeback bound to the exact message, session, task, and event, with stale-result rejection;
386
948
  - native DSH bundle installation, startup polling, `agent/created` retry, and plugin-source attribution;
949
+ - optional provider-neutral HTTPS webhook delivery for changed events, with endpoint-safe deduplication and retry, project-specific environment routes with independent outboxes, plus direct Feishu/Lark V2 text delivery;
950
+ - delivery-only notification controls for per-project minimum vulnerability severity and timezone-aware quiet hours; critical and malicious-package alerts bypass them, while held DSH tasks and webhook events remain durable;
951
+ - read-only pnpm v6/v9 lockfile graph extraction, including project-root importers and explicit ambiguous peer references;
952
+ - static Radar inventory generation from npm or pnpm lockfiles, followed by the same exact-version OSV check used by the DSH monitor;
387
953
  - automatic selection of the only DSH profile with third-party bundles, plus a network-free `radar status` snapshot;
388
954
  - commit-friendly `init` output that records the project workspace as `.` by default;
389
955
  - a reusable GitHub Action that turns the reviewed graph into a two-step, frozen CI gate;
956
+ - a concise escaped GitHub Job Summary alongside the Action's raw JSON result;
390
957
  - an opt-in GitHub Action DSH load matrix for exact published plugin versions and exact DSH versions;
391
958
  - a real DSH plugin consumer smoke that runs the published Action against 18 exact package versions;
392
959
  - an actionable, network-free `radar status` summary with exact active paths, candidate signals, and next steps;
393
960
  - a network-free `doctor` command that checks local DSH registration, overlay/config alignment, state readability, and dependency coverage;
394
961
  - compatibility signals for Node.js, peers, exports, entrypoints, bundle paths, dependencies, and version boundaries;
962
+ - top-level remediation evidence that compares active vulnerability ids and aliases with complete candidate graphs, identifying the first non-blocked plugin candidate that removes all checked paths without calling it safe;
395
963
  - an opt-in CI gate for confirmed/strong (`breaking`) or all (`any`) active compatibility changes;
396
964
  - an offline `benchmark compatibility` command that locks the deterministic rule and gate behavior into six reviewable contracts;
397
965
  - disposable `probe dsh-load` and `probe dsh-matrix` commands that check one exact DSH version or a bounded exact-version matrix against one bundle and return `compatible`, `incompatible`, or `unknown`;
@@ -404,20 +972,25 @@ pnpm dlx --package=upstream-radar@latest upstream-radar scan /path/to/dsh-plugin
404
972
  pnpm dlx --package=upstream-radar@latest upstream-radar inspect npm:dsh-cloudflare-browser-run@0.1.1 --deep
405
973
  ```
406
974
 
975
+ The default text gate exits `2` for `review` or `block`, which is useful when every uncertainty needs a human decision. If CI should fail only on hard blocks while still printing review evidence, add `--fail-on block`.
976
+
407
977
  ## Current boundaries
408
978
 
409
- - `init` discovers the only DSH profile with third-party bundles when `--profile` is omitted; multiple candidates still require an explicit profile. By default it follows the installed DSH `node_modules` tree, so pnpm overrides and local resolution choices are included. `--dsh-patch <path>` writes an explicit DSH overlay so first startup needs no environment variables and preserves an explicitly selected registry. A native pnpm lockfile parser for pre-install/CI inspection is still deferred.
410
- - A graph with unresolved required dependency declarations is marked as incomplete coverage; optional packages that are not installed for the current platform remain visible but do not create a false required-dependency alert. Missing `@deepseek-ai/dsh-*` and Cordis peers are called out separately as unobserved DSH host dependencies because Radar cannot query a version it was never shown.
979
+ - `init` discovers the only DSH profile with third-party bundles when `--profile` is omitted; multiple candidates still require an explicit profile. By default it follows the installed DSH `node_modules` tree, so pnpm overrides and local resolution choices are included. `--dsh-patch <path>` writes an explicit DSH overlay so first startup needs no environment variables and preserves an explicitly selected registry. `graph npm-lock` and `graph pnpm-lock` are separate pre-install/CI collectors; they do not themselves query OSV or create a Radar config.
980
+ - `init --pnpm-lock <path>` or `init --npm-lock <path>` creates a static config without a DSH profile; it reads `package.json` beside the lockfile unless `--root <name>@<version>` is supplied. Follow it with `radar check` or `radar watch` to query OSV. It does not itself start DSH or deliver Agent tasks.
981
+ - If the plugin root is not published to the selected npm registry, a registry `404` skips only that package's release comparison; exact lockfile dependencies and published DSH host packages are still checked. Registry outages, timeouts, malformed responses, and OSV failures remain operational errors.
982
+ - A graph with unresolved required dependency declarations is marked as incomplete coverage; optional packages that are not installed for the current platform remain visible but do not create a false required-dependency alert. Missing `@deepseek-ai/dsh`, `@deepseek-ai/dsh-*`, and Cordis peers are called out separately as unobserved DSH host dependencies because Radar cannot query a version it was never shown. If the DSH executable package itself cannot be read from the active host plane, the core-runtime boundary remains unknown rather than being guessed.
411
983
  - Candidate upgrade graphs are resolved only for a bounded earliest prefix. A candidate with an incomplete or unavailable graph is not recommended; later unqueried candidates remain visibly unchecked. Pass `--no-deep-candidates` to opt out of this extra registry work.
984
+ - When an active vulnerability belongs to an installed plugin, candidate remediation is narrower than an upgrade recommendation: `removed` means the complete checked candidate graph has no matching OSV finding, `still-affected` means a matching path remains, and `unknown` means the graph/source is incomplete, truncated, unavailable, or the path comes from the shared DSH host runtime. A remediation candidate still needs DSH project analysis.
412
985
  - Compatibility CI gating is opt-in: `--fail-on-compatibility breaking` fails on confirmed or strong incompatibility signals, while `any` fails on every active compatibility event; neither setting claims that a candidate is safe.
413
986
  - `probe dsh-load` is intentionally narrower than a security scan: a successful load proves only that the selected DSH profile accepted the bundle configuration. It does not execute plugin actions, compare capabilities, or grant admission to an unreviewed package.
414
987
  - `probe dsh-matrix` is intentionally sequential and bounded to eight versions. An incomplete matrix is not green: `unknown` propagates to the aggregate result until every selected DSH version has a reliable load result.
415
988
  - `radar check/watch --frozen` intentionally uses the graph committed in the config for CI; it does not prove that the installed DSH profile has not changed. Without `--frozen`, native DSH and CLI polling refresh the selected profile first.
416
- - `radar status` is a local snapshot only: it does not refresh OSV/npm/GitHub data, and it cannot prove that a source is current until a check has completed. It does show whether a captured DSH host plane came from the running process or a profile fallback. Its next steps are guidance, not an automatic upgrade or safety decision.
989
+ - `radar status` is a local snapshot only: it does not refresh OSV, GitHub Advisory, npm, or GitHub Release data, and it cannot prove that a source is current until a check has completed. It does show whether a captured DSH host plane came from the running process or a profile fallback. Its next steps are guidance, not an automatic upgrade or safety decision.
417
990
  - `doctor` checks local wiring only; it cannot prove that a running DSH process has delivered a task to a model or that upstream feeds are current.
418
- - npm lock graphs are supported; pnpm and Yarn graph adapters are not implemented.
419
- - OSV, npm `latest`, and public GitHub Release notes are live sources; changelog, comparison-diff, and migration-guide ingestion are deferred.
420
- - A failed OSV check preserves confirmed matches and returns a visible source warning; source health is durable and routed through DSH after three consecutive failures, while source-claim conflict handling and external health destinations are not implemented yet.
991
+ - npm and pnpm lock graphs are supported; Yarn graph extraction is not implemented.
992
+ - OSV, GitHub Advisory Database, npm `latest`, and public GitHub Release notes are live sources; changelog, comparison-diff, and migration-guide ingestion are deferred.
993
+ - A failed advisory-source check preserves confirmed matches and returns a visible source warning; each source's health is durable and routed through DSH after three consecutive failures. When OSV and GitHub Advisory Database disagree about severity or fixed versions, the event keeps both source-labeled claims and makes the conflict explicit instead of silently choosing one. Changed events can be sent to a provider-neutral HTTPS endpoint or directly to a Feishu/Lark V2 bot; there is no separate hosted alerting service.
421
994
  - `radar watch` is a CLI monitoring fallback; it does not deliver tasks into DSH by itself.
422
995
  - Delivery uses one root Agent as the simple default; when several roots exist, it requires an exact project-workspace match and leaves ambiguous tasks queued instead of guessing.
423
996
  - DSH result writeback accepts only the exact six-field JSON contract from the matching model session; it does not infer conclusions from ordinary chat or tool output. The result is advisory and never changes deterministic incident state.