oglyad
repository metadata and health checker for Terra.
"огляд" is Ukrainian for "review" or "inspection".
git clone https://github.com/terrapkg/packages ../packages
oglyad -m ../packages # warnings and errors
oglyad -m ../packages -v # plus info findings
oglyad -m ../packages -u # plus upstream version drift
oglyad -m ../packages --fedora # plus overlap with Fedora
oglyad -m ../packages --only drift --format json
from a checkout, cabal run oglyad -- <args> and cabal test. it needs
rpmspec (rpm-build), git, and the zstd, gzip and xz commands.
upstream lookups use GITHUB_TOKEN, or gh auth token when that's unset.
output and exit codes
--format picks text (default), markdown (made for GitHub job
summaries) or json. the json is a versioned envelope:
{"schema": 1, "oglyad": "0.1.0.0", "generated": "...", "monorepo_head": "...",
"summary": {"error": 0, "warn": 0, "info": 0, "ignored": 0},
"findings": [{"check": "...", "package": "...", "path": "...", "severity": "...", "message": "..."}],
"ignored": [...]}
schema changes on any breaking change to the json.
| exit |
meaning |
| 0 |
ran, nothing at or above --fail-on (default error, or warn, info, never) |
| 1 |
ran, and found something at or above --fail-on |
| 2 |
couldn't run: bad flags, unreadable ignore file, not a monorepo |
ignoring known findings
some findings are intentional, like a bootstrap package sharing its real
package's name. there are two ways to silence one, and ignored findings
don't count toward the exit code.
an ignore file, passed with --ignore-file. every entry needs a reason, and
names one check plus a monorepo path or a package name:
{
"ignore": [
{"check": "duplicate-name", "path": "anda/langs/zig/master-bootstrap",
"reason": "bootstrap build of zig-master"}
]
}
or a label in the package's own anda.hcl:
labels {
oglyad_ignore = "duplicate-name, stale"
}
file entries that no longer match anything are reported as unused-ignore,
so exceptions don't outlive their reason. --no-ignores shows everything.
ignores.json is the file the nightly report uses.
checks
| check |
severity |
what it means |
build-lag |
error / warn / info |
never published, merged but not built, or the repo is newer than your checkout |
drift |
warn |
upstream released something newer than the spec (-u) |
duplicate-name |
warn |
two directories build the same package name into the same repo, so they overwrite each other |
stale |
info, warn with -u |
last build is older than --stale-days, warn if upstream also moved on |
upstream-error |
warn |
the upstream from update.rhai 404s or fails, so anda update is failing too |
fedora-overlap |
warn |
Fedora ships the same source package (--fedora). subrepos are exempt |
no-autoupdate |
info |
no update.rhai |
unparsed-rhai |
info |
update.rhai does something oglyad can't follow, like custom scraping |
collect |
error |
rpmspec failed, a repo couldn't be fetched, or an upstream rate-limited us (warn) |
unused-ignore |
info / warn |
an ignore entry matches nothing, or an oglyad_ignore label names an unknown check |
how it works
┌──────────── IO shell (Collect) ──┐ ┌──── pure core ────┐ ┌─ IO ─┐
monorepo: rpmspec, git ──┐
Terra repodata + subrepos├─► Snapshot ───► [Check] ─► [Finding] ─► Report
Fedora repodata ─────────┤
upstream APIs ───────────┘
checks are Snapshot -> Package -> [Finding] and never do IO, so each one is
tested with a fake Snapshot in test/Spec.hs.
- specs are evaluated with
rpmspec
- update.rhai is lexed and pattern-matched, not run. it follows
let
bindings into rpm.version(...) / rpm.global("ver", ...) and recognizes
the andax helpers (gh, pypi, crates, gitlab, ...)
- upstream lookups hit the same endpoints anda does, so oglyad sees what
anda update would
- versions compare with a port of rpm's
rpmvercmp, plus some forgiveness
for respelled tags (v1.2, 1.2-rc1 vs 1.2~rc1, 2026-07-27 vs 20260727)
caching
everything lives in ~/.cache/oglyad.
| file |
reused while |
specs.fcNN.json |
the package dir's files, the dist tag, and rpm's whole macro state (rpm --showrc) are unchanged |
commits.json |
HEAD hasn't moved |
repodata-*.json |
the repo's primary.xml checksum is unchanged |
upstream.json |
younger than --cache-hours (default 6), successes only. entries unused for 30 days are dropped |
a run with warm caches takes about 0.4s. a cold run takes about 5s, almost
all of it in rpmspec. --fedora adds about 8s and peaks around 200MB of
memory the first time, since Fedora's repodata is 260MB decompressed. after
that it's cached until Fedora publishes again.
security
rpmspec runs the shell code in specs (%(...), %{lua:...}), so running
oglyad on a checkout means running that checkout's code as you. child
processes get an allowlisted environment, so GITHUB_TOKEN and other
secrets in oglyad's environment never reach them, but code running as your
user can still read your files.
don't run oglyad on untrusted code, like an unmerged pull request, with
credentials around. the nightly report only checks out terrapkg/packages'
default branch, and its token is read-only and expires with the job.
the GitHub token is only ever sent to api.github.com, and is dropped if a
request gets redirected anywhere else.
nightly report
report.yml runs every night in Terra's
builder image (ghcr.io/terrapkg/builder:f45), so specs evaluate with the
same macros Terra builds with. it builds oglyad-git with anda, checks
terrapkg/packages' default branch with -u --fedora, writes the markdown
report to the run's summary, and keeps the markdown and json for 30 days.
caches carry over between nights.
packaging
built the same way as anda itself. anda.hcl has two projects:
| project |
spec |
builds |
oglyad |
oglyad.spec |
the v<version> release tag |
oglyad-git |
oglyad-git.spec |
the checked-out commit, versioned <version>^<date>.<commit> |
anda build oglyad-git --package rpm -c terra-45-x86_64
both build in mock, offline, against Fedora's Haskell libraries with
%ghc_bin_build, run the test suite in %check, and ship one self-contained
binary. package-git.yml builds
oglyad-git on x86_64 and aarch64 for every push to main and every pull
request that touches the code.
releasing
- bump
version in oglyad.cabal and oglyad.spec, and add a
CHANGELOG.md entry
- push a
v<version> tag, e.g. v0.1.0.0
hackage.yml checks the tag matches the
cabal version, builds and tests the sdist, and publishes it to Hackage. the
same tag is what oglyad.spec downloads. to preview a release first, run the
workflow by hand, which uploads a Hackage package candidate instead.
it needs a HACKAGE_TOKEN secret (an API token from your Hackage account) in
a GitHub environment named hackage.
code is formatted with ormolu 0.9.0.0, and
CI fails on unformatted files. install the matching version from its
release page, then:
ormolu --mode inplace $(git ls-files '*.hs')
license
GPL-3.0-or-later. see LICENSE.