Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
bdd6c45
fix(core): make the JSON scan total and stop it splicing escaped values
justin13888 Aug 31, 2026
1b13d61
fix(core): stop version comparison reporting wrong answers as confide…
justin13888 Aug 31, 2026
cc9df2b
fix(core): bound the graph walk so an unbounded tree cannot hang or o…
justin13888 Aug 31, 2026
1a05147
feat(core): read the dependency tables the parsers were silently skip…
justin13888 Aug 31, 2026
7ad8ea2
fix(fetch): stop the cache answering questions it was never asked
justin13888 Aug 31, 2026
61dc6e2
fix(cli): fail the gate the run could not answer instead of passing it
justin13888 Aug 31, 2026
58b9ce4
fix(report): close the policy gates that passed what they exist to catch
justin13888 Aug 31, 2026
0248ef8
fix(report): emit SARIF artifact URIs a consumer can actually resolve
justin13888 Aug 31, 2026
45a9742
fix(cli): stop the config layer silently disarming the gate it was given
justin13888 Aug 31, 2026
b7c8cea
fix(cli): make `fix` safe to point at a repository
justin13888 Aug 31, 2026
bbd1c53
fix(fetch): survive a rate limit instead of quietly not auditing the …
justin13888 Aug 31, 2026
8e25de7
docs: make the documentation describe the product that exists
justin13888 Aug 31, 2026
1abef2e
fix: reconcile the stabilization fixes with the JVM ecosystem
justin13888 Sep 1, 2026
787480d
fix(report): decide a SARIF artifact URI the same way on every platform
justin13888 Sep 1, 2026
35250b2
fix(core): read a pnpm override key from its last arrow segment
justin13888 Sep 1, 2026
cb5e959
fix(fix): leave an override's forced version alone
justin13888 Sep 1, 2026
24d9100
fix(core): tell a failed constraint translation from an absent one
justin13888 Sep 1, 2026
7a28567
fix(core): read an npm `$name` override as a reference, not a constraint
justin13888 Sep 1, 2026
1e7b115
fix(cli): gate on the run that failed, not on the package that 404s
justin13888 Sep 1, 2026
d5cc3a2
fix(cli): keep `[policy]` a known key in a build that cannot enforce it
justin13888 Sep 1, 2026
42f8ec7
fix(fetch): scope a JVM mirror's answers to their own cache key
justin13888 Sep 1, 2026
54accaa
fix(cli): carry the tree truncation flag into JSON and DOT
justin13888 Sep 1, 2026
34a5b92
fix(report): give UNC, verbatim, and drive-relative prefixes a URI sp…
justin13888 Sep 1, 2026
bded641
fix(fetch): read a Go proxy 410 as "no such module"
justin13888 Sep 1, 2026
ed0b20b
fix(fetch): tell an empty version list from a missing artifact
justin13888 Sep 1, 2026
906aee2
fix(core): bound an override key's parent split to the name
justin13888 Sep 1, 2026
6ab2ecf
fix(core): read a `$` naming nothing as a dangling reference
justin13888 Sep 1, 2026
dfb08a8
fix(core): read a bare PEP 440 `*` as any version
justin13888 Sep 1, 2026
f9e033a
fix(cli): gate on a 404, not on every error a run produces
justin13888 Sep 1, 2026
6c83fb7
fix(cli): say what a run could not evaluate, and keep `-q` quiet
justin13888 Sep 1, 2026
d1af1f9
fix(cli): unwrap the CVSS-policy error message
justin13888 Sep 1, 2026
930e6fe
fix(cli): give a dangling override reference its own source token
justin13888 Sep 1, 2026
e6cbc98
fix(cli): annotate a dependency whose version could not be read
justin13888 Sep 1, 2026
c50ba74
fix(core): read a constraint dialect this tool lacks as undetermined
justin13888 Sep 6, 2026
7ad166a
fix(core): resolve a `$` override's referent the way the referent res…
justin13888 Sep 6, 2026
2b93279
fix(cli): refuse a vulnerability gate that has no scan to answer it
justin13888 Sep 6, 2026
36f6679
docs(report): describe `major_distance` as it actually behaves
justin13888 Sep 6, 2026
8b7559e
docs: say what exit 2 now means, and count the ecosystems that ship
justin13888 Sep 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/actions/dependable-check/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A composite action that installs the released `dependable` binary and runs

```yaml
- uses: actions/checkout@v4
- uses: getkono/dependable/.github/actions/dependable-check@v0.1.4
- uses: getkono/dependable/.github/actions/dependable-check@v0.1.3
with:
fail-on: vulnerable
```
Expand Down Expand Up @@ -37,7 +37,7 @@ single valid document while the annotations still reach the pull request.
| `fail-on` | `vulnerable` | `none`, `outdated`, `vulnerable`, or `any`. |
| `format` | `table` | `table`, `json`, `text`, or `sarif` — what goes to stdout. |
| `annotations` | `auto` | `auto`, `always`, or `never`. `never` also turns off the job summary. |
| `version` | `latest` | A release tag such as `v0.1.4`, or `latest`. |
| `version` | `latest` | A release tag such as `v0.1.3`, or `latest`. |
| `args` | `''` | Extra arguments appended to `dependable check` verbatim. |

**`fail-on` defaults to `vulnerable`, deviating from the CLI's `none`.** This is
Expand All @@ -59,7 +59,7 @@ Counts are deliberately not outputs: they would duplicate the JSON schema and
drift from it.

```yaml
- uses: getkono/dependable/.github/actions/dependable-check@v0.1.4
- uses: getkono/dependable/.github/actions/dependable-check@v0.1.3
id: deps
continue-on-error: true
with:
Expand All @@ -83,7 +83,7 @@ Releases are tagged `v{version}` only — there is **no floating `v1` tag** —
pin a full tag:

```yaml
uses: getkono/dependable/.github/actions/dependable-check@v0.1.4
uses: getkono/dependable/.github/actions/dependable-check@v0.1.3
```

`version: latest` resolves the newest release at run time; pinning `version:` to
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/dependable-check/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ inputs:
default: 'auto'
version:
description: >-
Release tag to install, such as v0.1.4, or latest. Pinning a tag skips the
Release tag to install, such as v0.1.3, or latest. Pinning a tag skips the
releases API call, so a workflow with no contents read permission must pin.
required: false
default: 'latest'
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/live.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# The `#[ignore]`d network tests, on a schedule.
#
# `mise run test` is hermetic by design, so the entire network-facing surface —
# the crates.io sparse index, npm, PyPI, Packagist, pub.dev, NuGet, Hex, JSR and
# OSV — is never exercised by CI. Those are also the least-covered modules in the
# workspace, and the ones whose correctness depends on a schema this repository
# does not control: when a registry changes a response shape, nothing here notices
# until a user reports a wrong answer.
#
# Deliberately not part of the PR gate. These tests fail for reasons a contributor
# cannot fix — an outage, a rate limit — and a gate that cries wolf gets ignored,
# including on the day it is right.
name: Live

on:
schedule:
# 05:00 UTC daily, off the top of the hour to avoid the cron stampede.
- cron: "17 5 * * *"
workflow_dispatch:

env:
CARGO_TERM_COLOR: always

jobs:
live:
name: Live registry + OSV smoke tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Materialize Rust toolchain
run: rustup show active-toolchain || rustup show
- uses: Swatinem/rust-cache@v2
- uses: jdx/mise-action@v2
- name: Run the live tests
run: mise run test:live
14 changes: 12 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# dependable

Open-source CLI + Rust library for checking dependency versions and known
vulnerabilities. V1 scope is **Rust / Crates.io only**; see [`docs/SCOPE.md`](docs/SCOPE.md)
for what is deferred and why.
vulnerabilities. Eleven ecosystems ship — Rust, npm, PyPI, Go, Deno/JSR, pnpm,
Packagist, pub.dev, NuGet, Hex and Kotlin/Java on Maven Central — with Rust, npm and
Python marked stable and the rest experimental. The table in
[`README.md`](README.md) is authoritative for what ships and at what maturity; see
[`docs/SCOPE.md`](docs/SCOPE.md) for what is deferred and why.

## Workspace

Expand All @@ -12,6 +15,10 @@ for what is deferred and why.
public end-to-end entry point: the `Checker` (parse → fetch → evaluate → OSV scan)
plus async IO (crates.io sparse index, OSV client, moka cache). Depends on and
re-exports `dependable-core`, so external consumers (e.g. an IDE) need only this crate.
- **`dependable-report`** (`crates/dependable-report`) — the report and policy layer:
HTML rendering (minijinja), SARIF v2.1.0, the SPDX license evaluator, and the
`[policy]` engine. Pure like the core: it takes the finished report model and
returns bytes, so it holds no IO of its own. A library only — it ships no binary.
- **`dependable-tui`** (`crates/dependable-tui`) — the interactive terminal UI
(ratatui). Holds no IO of its own: it drives `dependable-fetch`. Its `App` state
machine is free of both IO and ratatui, which is what makes navigation, search,
Expand All @@ -21,6 +28,9 @@ for what is deferred and why.
`tree` command renders the workspace dependency graph offline via
`dependable_fetch::build_workspace_graph` (no `Checker`, no network).

Five crates, published to crates.io in dependency order:
core → report → fetch → tui → dependable.

## Quality

Validate changes before committing:
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 41 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,41 @@ skipped and transitive deps are never fetched), and the public API is
forward-compatible: enums are `#[non_exhaustive]` and the registry layer routes
per ecosystem, so future registries (npm, PyPI, Go, …) are additive.

## Exit codes

| Code | Meaning |
| --- | --- |
| `0` | The run completed and no armed gate was tripped. |
| `1` | A gate was tripped: `--fail-on` matched, or a `[policy]` rule was violated. |
| `2` | The tool could not do the job — a config it cannot read, a policy it cannot enforce, or **a gate it cannot answer**. |

That last case matters for CI. If you arm `--fail-on` (or a `[policy]` severity
rule) and the run cannot establish what the gate needs — the vulnerability scan did
not complete, or a registry never answered — `dependable` exits `2` and says so,
rather than exiting `0`. A gate that reports success on the run it could not perform
is worse than no gate at all. With no gate armed, an unreachable registry is still
reported per dependency and the run exits `0`, because nothing was promised.

A registry that *did* answer is a different thing. A package it reports as
non-existent — an unpublished internal package, one served by a registry this run
does not route to, a deleted package — is a permanent per-dependency fact, not a
failure of the run: it appears in the table and in `--format json`, the run says on
stderr how many were skipped, and the exit code is `0`. The same goes for a
dependency whose declared version this run could not read, which is reported
`undetermined`: `--fail-on vulnerable` promises something about vulnerabilities and
`--fail-on outdated` something about staleness, and neither is a promise that every
constraint was parseable. `--fail-on any` is that promise, and it fails on both.

Arming a gate the run cannot enforce is a configuration error, caught before any
network access: `--fail-on vulnerable` with vulnerability scanning switched off
(`--no-vuln`, or `[vulnerability] enabled = false`) exits `2`, as a `[policy]`
severity rule already did. Every advisory list would be empty and the gate could
never fail — a gate is either enforceable or it is a mistake.

`.dependable.toml` is validated: an unknown key or a wrong-typed value is an error,
not a silent fallback to defaults. One mistyped character used to reset
`[global] fail_on` to `none` and disarm the gate with nothing on stderr.

## Development

| Command | Description |
Expand All @@ -568,7 +603,10 @@ per ecosystem, so future registries (npm, PyPI, Go, …) are additive.
- **`dependable-fetch`** — the high-level library: `Checker` ties parsing to async
registry + OSV fetching and caching. The public end-to-end entry point for other
tools; re-exports the core types so consumers need only this crate.
- **`dependable`** — the CLI binary; a thin wrapper over `dependable-fetch`.
- **`dependable-report`** — HTML, SARIF, and the `[policy]` engine, over the finished
report model. A library only.
- **`dependable-tui`** — the interactive terminal UI (ratatui), driving `dependable-fetch`.
- **`dependable`** — the CLI binary; a thin wrapper over the crates above.

## Git Hooks

Expand All @@ -583,7 +621,7 @@ is a composite action that installs the released binary and runs the check:

```yaml
- uses: actions/checkout@v4
- uses: getkono/dependable/.github/actions/dependable-check@v0.1.4
- uses: getkono/dependable/.github/actions/dependable-check@v0.1.3
with:
fail-on: vulnerable
```
Expand All @@ -609,7 +647,7 @@ both the annotations and the job summary. Annotations go to **stderr**, so
stdout.

This repository's own GitHub Actions workflow runs format checks, linting, and
tests on pushes to `main` and on pull requests, plus a coverage job that uploads
tests on pushes to `master` and on pull requests, plus a coverage job that uploads
an `lcov.info` artifact.

## License
Expand Down
Loading
Loading