Tooling for the Schwarz Digits open-source compliance program.
This repository contains the command-line tools and GitHub Actions workflows
that we use to maintain transparency, license compliance, and security
hygiene across our open-source organizations on GitHub
(SchwarzDigits, SchwarzIT).
Early development. The first focus is a repository inventory tool that enumerates our open-source repositories and records their metadata, license status, and key compliance signals.
| Component | Description |
|---|---|
cmd/osstool |
Command-line entry point |
internal/inventory |
Repository enumeration and metadata collection |
internal/github |
GitHub API client (GraphQL-first) |
More components will be added over time.
The tool is intended to run via GitHub Actions for production runs and
locally for development. Local execution requires Go 1.23+ and a GitHub
personal access token in GITHUB_TOKEN.
make build
export GITHUB_TOKEN=ghp_xxx
# Collect repositories using the orgs listed in config/inventory.yml
./bin/osstool inventory run
# Or override the config and pass orgs on the command line
./bin/osstool inventory run --orgs SchwarzDigits,SchwarzIT
# Generate a Markdown summary from the latest output
./bin/osstool inventory report
# Generate a report that also calls out what changed since a previous
# snapshot (file or directory of per-org JSON files):
./bin/osstool inventory report \
--input ./output/latest \
--diff-from ./output/2026-04-30 \
--output ./output/summary.md
# Compare two snapshots directly and emit a Markdown diff
./bin/osstool inventory diff \
--from ./output/2026-04-30 \
--to ./output/latest \
--output ./output/diff.mdOutput layout:
output/
├── 2026-04-30/
│ ├── SchwarzDigits.json
│ └── SchwarzIT.json
└── latest/
├── SchwarzDigits.json
└── SchwarzIT.json
output/summary.md # produced by `inventory report`
The per-repo JSON schema includes a compliance_checks object (with
secrets_vuln and license sub-objects, each carrying status,
completed_at, and url) and a likely-owner hint (likely_owner,
likely_owner_source). All optional fields use omitempty, so older
snapshots that predate a field still parse cleanly; missing fields
default to zero values.
Snapshots written by earlier versions of this tool used five flat
last_compliance_run_* fields instead of compliance_checks. The
diff command silently skips per-check transitions when one side of
the comparison lacks compliance_checks — a one-time silent
boundary on the upgrade.
config/inventory.yml drives both which orgs are scanned and which
repos are excluded:
orgs:
- SchwarzDigits
- SchwarzIT
excludes:
- "*/.github" # any-org pattern
- SchwarzDigits/oss-compliance # exact org/name patternorgs are the default list scanned by osstool inventory run; the
--orgs CLI flag overrides them. excludes lists repos that aren't
meaningful subjects of compliance reporting (the central workflow
definition, data-only repos, the org .github repo). Two pattern forms
are supported: <org>/<name> for an exact match and */<name> to match
a name across any org. Comparisons are case-sensitive.
Point at a different config with --config <path>. The CLI works
without the file present — a missing config is logged and treated as
"no defaults", in which case --orgs becomes mandatory.
Excluded repos are dropped at collection time, so they never appear in the per-org JSON snapshots or the report. Snapshots taken before a repo was added to the excludes will still contain it; the diff sub-command will surface the one-time disappearance as a "Repositories removed" entry.
Every repo is classified into one of three lifecycle statuses — active (pushed in the last 12 months), stale (older), or archived — and that vocabulary is reused across the per-org table, the Compliance: Migration Priority section, and the Archive Candidates section. The report's Status definitions footer at the end of the report spells out the rules in one place; section methodology hints reference the footer rather than restating them.
Status header percentages use the active count as the denominator — stale and archived repos can't realistically be onboarded, so including them would understate adoption progress. The license-compliance bullet also surfaces the overall ratio (across all statuses), since legal obligation isn't excused by stale status.
The central Compliance workflow has three jobs:
secret-and-vuln-scan, decide-ort, and license-and-sbom. Only
the first and third are real compliance checks; decide-ort is a
routing job that decides whether ORT (the license analyzer) needs
to run for the current trigger. Because ORT skips on doc-only
pushes, reading just the workflow's top-level conclusion would
report green when no license analysis happened — masking real
violations. The inventory therefore tracks each check's most recent
meaningful run independently (skipping conclusion: skipped)
and surfaces them in separate columns of the Compliance status
table.
A check shows – never when no meaningful run has occurred within
the lookback window — distinct from ❌ failure. The lookback uses
two phases: first the latest 30 runs of the Compliance workflow
unfiltered, then (for the license check only, if still empty) one
fallback to the most recent event=schedule run. The cron is the
floor on freshness because it forces a full ORT execution every
Sunday. For newly-onboarded repos – never is normal until the
schedule fires. If a long-running repo shows – never for license,
it means the cron itself is broken or decide-ort is failing on
the scheduled trigger — investigate the workflow configuration.
Note on terminology: the License compliance bullet in the
status header tracks presence of a LICENSE file in each repo
(static metadata). The Latest license checks bullet and the
License (ORT) column in the compliance table track ORT runtime
analysis of dependency licenses (per-run signal). They share the
word "license" but answer different questions; both are surfaced
because both are required for compliance.
The "Likely owner" column attributes a repo first to a CODEOWNERS file
(suffix (CO)) and otherwise to the dominant non-bot author in the
last 100 commits on the default branch, with a fallback to commits
101-200 if the first 100 are bot-only (suffix (committer)). The
underlying LikelyOwnerSource field on the JSON snapshot is one of
codeowners, top_committer_recent, or empty (renaming the older
top_committer_90d value — readers handling old snapshots can treat
any top_committer_* value as the same source class).
| Target | Description |
|---|---|
make build |
Build the binary at ./bin/osstool |
make test |
Run the Go test suite |
make vet |
Run go vet ./... |
make lint |
Run golangci-lint run (requires golangci-lint installed locally) |
make run-inventory |
Convenience wrapper around inventory run with default orgs |
make clean |
Remove bin/ and output/ |
Contributions are welcome. Please see CONTRIBUTING.md
in our .github repository for general guidelines, including our
Contributor License Agreement.
Apache License 2.0. See LICENSE.