Signet — Ecosystem & Distro Support Matrix
What Signet discovers, how it resolves each to source, and what that means for the read path (coverage) and the audit queue. Generated as part of the 24-hour breadth plan (breadth-24h-plan.md).
Discovery watchers
Each watcher is off by default; the discovery Spring profile (application-discovery.properties) turns them all on at once. Cursors persist in feed_cursors; releases land in package_releases.
| Ecosystem | Feed / mechanism | Cursor | Resolver path |
|---|---|---|---|
| npm | registry _changes + latest fetch | seq | deps.dev → version-doc gitHead (VERIFIED) → tag peel fallback |
| PyPI | RSS recent updates | — (dedup) | deps.dev → tag peel |
| crates.io | /summary just_updated | — (dedup) | deps.dev → crate .cargo_vcs_info.json (VERIFIED) → tag peel fallback |
| Go | index.golang.org/index | timestamp | deps.dev → tag peel |
| RubyGems | activity/just_updated.json | — (dedup) | deps.dev → tag peel |
| Maven Central | solr /select, oldest-first from cursor (head-seeded) | epoch-ms | deps.dev → tag peel |
| NuGet | catalog change-log walk | ISO commit | deps.dev → tag peel |
| Debian/Ubuntu (deb) | Sources index (RFC822, gz) | skip-offset (wraps at end) | metadata upstream inference |
| CentOS/RHEL/Rocky/Fedora (rpm) | repomd.xml → primary.xml | skip-offset (wraps at end) | metadata upstream inference |
Resolution confidence ladder
(ecosystem, name, version) → (repo, commit) grades, from package_source_resolutions.confidence:
| Confidence | Meaning | Subject-eligible? | Produced by |
|---|---|---|---|
VERIFIED | publisher asserts the exact commit | yes | cargo — .cargo_vcs_info.json git.sha1 inside the immutable published crate; npm — the per-version registry doc's gitHead (recorded by npm CLI at publish). No tag inference; refresh re-reads the registry, and a changed sha on an immutable version is flagged as registry tampering |
ATTESTED_TAG | we peeled a host tag on the declared repo | yes | pypi/go/rubygems/maven/nuget; npm/cargo when the publish carried no commit record |
HEURISTIC | inferred upstream repo + a tag we peeled | yes | distro upstream inference (deb/rpm) |
NONE | no commit mapping | no | artifact-only / unresolved |
Subject-eligible = can become an audit_targets row and carry attestations. Only a real commit qualifies — see the guardrail below.
The distro guardrail (deb/rpm)
Distro packages wrap patched upstream tarballs and frequently have no clean commit. The decisive rules (DistroUpstreamResolver), never relaxed:
- Infer upstream when possible. From the discovered metadata (
Homepage,
Vcs-Git, rpm <url>), if a github.com/gitlab.com project is identifiable, strip the distro version (epoch:, -revision, +dfsg/+ds) and peel the upstream tag. A hit → a **real upstream commit graded HEURISTIC** (the shipped source may differ from the tagged tree, so never ATTESTED/VERIFIED).
- Otherwise, advisory artifact. Record the tarball/package sha256 as a
resolved-artifact row. It is never a subject, never enters the lease queue, carries no coverage or attestations. Surfaced on the portal Discovery page as an advisory count.
- Otherwise, unresolved (
UPSTREAM_UNKNOWN).
We do not synthesize a commit, do not loosen StatementValidator, and do not touch the conformance vectors — artifact-only-as-subject would be a spec-wide, Rust↔Java-conformance-breaking schema change (product decision, still "no").
Source hosts for tag peeling
GitTagResolver is host-aware:
- GitHub —
git/ref/tagspeel (annotated → tag object → commit). - GitLab —
repository/tags(nested-group paths; self-hosted via configurable API
base + PRIVATE-TOKEN).
- Other hosts (Bitbucket, salsa.debian.org packaging repos, …) →
TAG_NOT_FOUND; the
distro path then falls back to advisory artifact.
A host 403/429 raises HostRateLimitedException so the jobs runtime retries with backoff — it is never cached as a false "unresolved".
Read-path / coverage semantics
- Coverage and attestations attach only to subject-eligible
(repo, commit)pairs. ATTESTED_TAGandHEURISTICmappings are surfaced but should be down-weighted
relative to VERIFIED by consumers — the released artifact may diverge from the tagged tree, and tags mutate (the TTL-refresh producer re-peels and flags mutation).
- Mutation incidents bind (v1.205–v1.224): every refresh-caught mapping change
is persisted (resolution_mutations) and reaches all consumer surfaces — the /v1/disputes incident feed, the package dossier, signet ops triggers (machine-checked PD-2/PD-3 revisit condition, windowed to recent evidence), both CI gates (signet gate and signet deps fail on verified-grade tampering, warn on repoints, both with SARIF), the README badge (tampering detected outranks every state), the catalog chip (tampered), and mutation.recorded webhooks push incidents to package subscribers. AI verdicts computed pre-tampering are marked stale on every surface, the release rejoins the sweep at the head of the queue for a re-read, tampered releases dominate the human audit-target queue (the priority model's incident term outranks advisories + reach + posture combined), and mutations.jsonl in the signed dumps keeps incidents portable (kind-aware mirror verify). One deliberate asymmetry: mirror sync federates signed records only — a sync-built replica verifies the incidents partition but does not import it (a registry records what its own refresh witnesses), so it reports fewer trust incidents than its upstream until then; the CLI prints this, and a dump restore keeps them.
- Artifact-only distro rows never contribute to coverage.
Monorepo #subpath sources (PD-9)
A package's coverage scope is its subtree within the repo when a subpath is known; NULL = whole repo. Per-ecosystem population state:
| Ecosystem | Subpath source | Status |
|---|---|---|
| npm | repository.directory from the registry package doc | populated (normalized at resolve; guardrail: PackageReleaseSubpathTest) |
| Cargo | .cargo_vcs_info.json path_in_vcs inside the published .crate (written by cargo publish — authoritative) | populated (bounded tarball scan at resolve; live-verified against crates.io) |
| Maven | unique repo-tree directory whose leaf equals the artifactId and holds a pom.xml (GitHub trees API; ambiguous/truncated → whole repo) | populated (heuristic; rate limits defer per PD-4) |
| Go | module path suffix vs the repo slug (…/repo/service/s3 → service/s3; a trailing /vN is version, not directory) | populated (derived at resolve, no network; explicit metadata wins) |
| PyPI | …/tree/<ref>/<path> link in the version's project_urls (e.g. langchain-core → libs/core) — GitHub + GitLab URL forms | populated where publishers link it (widespread in monorepos); else whole repo |
| RubyGems | same tree-URL convention on source_code_uri (e.g. actionpack → actionpack) | populated where published; live-verified against rubygems.org |
| NuGet | unique repo-tree directory whose leaf equals the package id and holds <id>.csproj | populated (heuristic; the <id>.csproj marker keeps prefixed siblings distinct) |
| deb / rpm | n/a — distro rows are advisory-only | — |
Packages without a subpath keep the pre-PD-9 whole-repo semantics, which errs toward showing repo-level coverage — honest for single-package repos (the vast majority), and the dossier labels monorepo scoping explicitly where it applies.