Commands Reference

Complete reference for every Aveloxis CLI command.


aveloxis serve

Starts the long-running scheduler that continuously collects repos from the priority queue, plus a web monitoring dashboard.

aveloxis serve [flags]

Flags

Flag

Type

Default

Description

--monitor

string

"127.0.0.1:5555"

Address for the monitoring dashboard (the REST API is a separate process — see aveloxis api). Always started with serve.

--workers

integer

config collection.workers (default 12); flag overrides

Number of concurrent collection workers. Each worker claims one repo at a time from the queue. The cobra flag default is 1, but when the flag is not explicitly set, serve uses collection.workers from aveloxis.json.

--augur-keys

boolean

false

Also load API keys from Augur’s augur_operations.worker_oauth table.

Behavior

  • Uses the staged collection pipeline (API -> staging -> processing -> facade -> commit resolution -> analysis).

  • The queue is Postgres-backed (aveloxis_ops.collection_queue) and uses SELECT ... FOR UPDATE SKIP LOCKED for atomic job claiming.

  • Safe to stop and restart at any time. On shutdown (Ctrl-C / SIGTERM), in-flight API calls and statements are cancelled at once; workers get up to collection.shutdown_grace_seconds (default 10) to unwind (a completion reached at the cancel is stamped on a retry bounded by half the grace, at most 5 s), then queue locks are released and staging data is preserved.

  • On startup, reclaims a previous serve’s locks at once (except a running heal’s parked rows), resumes collection, and drains any leftover staged data from an interrupted run in the background.

  • Stale locks from crashed instances are recovered after 1 hour by a running instance, or at once by the next serve start.

  • Multiple instances can share the same queue for horizontal scaling.

Periodic tasks

The scheduler also runs these background tasks:

Task

Interval

Description

Org refresh

Every 4 hours

Re-fetches org membership lists

Contributor breadth

Every 15 minutes (config breadth_interval_minutes)

Discovers cross-repo contributor activity via GitHub Events API

Materialized view rebuild

Weekly (Saturday)

Pauses collection, refreshes the 20 8Knot materialized views, resumes

Supply-chain view refresh

Every collection.supply_chain_refresh_hours (default 24)

Refreshes the two Aveloxis-owned supply-chain views CONCURRENTLY; seconds, collection never pauses

Stale lock recovery

Every 5 minutes

Re-queues jobs locked for more than 1 hour

Scancode worker tuning

aveloxis serve also runs a decoupled ScancodeWorker pool (v0.21.0+) for per-file license + copyright scanning. The pool’s behavior is tuned via the collection block of aveloxis.json — no CLI flag. The most operationally relevant knobs:

aveloxis.json field

Default

What to change it for

scancode_workers

2

More concurrent scancode invocations (cap by CPU cores).

scancode_max_in_memory

5000

Raise on RAM-rich hosts (e.g. 50000 on a host with hundreds of GB) so scancode keeps more per-file scan state in memory instead of spilling to a tempfile. Linux-kernel / chromium-class monorepos benefit most. Memory cost is roughly scancode_workers × --processes × scancode_max_in_memory × per-file working set, so account for the multiplier when sizing. v0.25.2+.

scancode_run_timeout_hours

2

Raise on fleets skewed toward big repos so the per-job timeout starts higher (default is adaptive — every wall-clock timeout doubles the next attempt’s budget up to scancode_run_timeout_cap_hours).

scancode_cadence_days

180

Lower for testing or when source-license churn is unusually high.

See configuration.md -> scancode worker for the full per-field reference and the v0.21.x → v0.25.x context.

Examples

# Start with defaults (collection.workers, default 12; dashboard on 127.0.0.1:5555)
aveloxis serve

# Start with 4 workers and a custom dashboard address (8082 is the web GUI's port)
aveloxis serve --workers 4 --monitor 127.0.0.1:5556

# Start using Augur's API keys
aveloxis serve --workers 4 --augur-keys

aveloxis web

Starts the web GUI for OAuth-based group management. Users log in via GitHub or GitLab, create groups, and add repositories or entire organizations for collection.

aveloxis web

Configuration

The web command has no CLI flags. All settings come from the web section of aveloxis.json:

Config field

Type

Default

Description

web.addr

string

":8082"

Listen address for the web server.

web.base_url

string

"http://localhost:8082"

External URL used to construct OAuth callback URLs.

web.session_secret

string

""

Reserved; not read today (sessions are random in-process tokens). Set a random string anyway.

web.dev_mode

bool

false

Local HTTP development: cookies without Secure, loopback confirmation links. Never in production.

web.api_internal_url

string

"http://127.0.0.1:8383"

Where web proxies /api/*; must follow api.addr.

web.spa_url

string

""

The origin of a separate front end whose ?next= the login flow may honor; empty otherwise.

web.auto_approve_add_limit

int

0

Ordinary users’ repository batches up to this size are approved automatically; 0 means every addition waits for an admin; organizations always do.

web.github_client_id

string

""

GitHub OAuth app client ID.

web.github_client_secret

string

""

GitHub OAuth app client secret.

web.gitlab_client_id

string

""

GitLab OAuth app client ID (Application ID).

web.gitlab_client_secret

string

""

GitLab OAuth app client secret.

web.gitlab_base_url

string

"https://gitlab.com"

GitLab instance URL for self-hosted instances.

OAuth app setup

Behavior

  • Serves the web GUI on the configured listen address.

  • Users authenticate via OAuth, then create groups and add repos or orgs through the browser.

  • Breadcrumb navigation shows Home / {Group Name} for easy navigation.

  • Group detail pages display repos 25 per page with pagination controls. A case-insensitive search box filters by repo name, owner, or URL.

  • Repos added through the web GUI are inserted into the same collection queue used by aveloxis add-repo.

  • When a user adds a GitHub org or GitLab group, a user_org_requests row is created. A background task scans tracked orgs every 4 hours to discover and queue new repos.

  • Sessions are stored in-memory with a 24-hour expiry. Restarting the process clears all sessions.

  • Runs as a separate process from aveloxis serve. Both share the database.

Examples

# Start the web GUI (uses settings from aveloxis.json)
aveloxis web

# Then open http://localhost:8082 in your browser

See the Web GUI guide for detailed setup instructions.


aveloxis api

Starts the REST API server on 127.0.0.1:8383 unless configured otherwise. Serves repo statistics, weekly time series, dependency licenses, scancode results, SBOM downloads, repo search, and the Augur-compatible metric endpoints. The web GUI’s charts and the comparison page load their data from this process.

aveloxis api                        # 127.0.0.1:8383, or api.addr from the config
aveloxis api --addr 0.0.0.0:9383    # override for this invocation

Flag

Type

Default

Description

--addr

string

(from config)

Listen address for the API server. Overrides api.addr; when neither is set the default is 127.0.0.1:8383.

The listen address is normally set with api.addr in aveloxis.json (v0.28.19), because aveloxis start api spawns the process with only --config — a flag-only setting could not reach a backgrounded API, and two instances on one host collided on 8383. The default is loopback on purpose; see Reaching the API from another host before binding a routable address, and remember that web.api_internal_url has to follow the API when it moves.

Run alongside aveloxis serve and aveloxis web — aveloxis start all starts all three. See the REST API guide for the endpoint reference.


aveloxis scancode-worker

Runs ONLY the ScancodeWorker pool against the configured database (v0.27.6) — the dedicated-scancode-host deployment, following the aveloxis api single-purpose-process precedent. Scancode is the one subsystem whose resource profile (multi-GB shallow clones, CPU-pinned Python subprocesses, 24-hour worst-case wall clocks) is nothing like the API-bound main pipeline; moving it to an adjacent machine isolates its disk/CPU blast radius while the shared tables (FOR UPDATE SKIP LOCKED claims + the scancode_locked_host column) provide all the coordination needed.

aveloxis scancode-worker -c /etc/aveloxis/aveloxis.json
  • The primary server opts out by setting "scancode_workers": 0 (an EXPLICIT 0 — an absent key keeps the default of 2) in its own aveloxis.json.

  • No API keys required — scancode clones anonymously; this command starts without any worker_oauth rows (unlike aveloxis serve).

  • Does NOT run schema migrations (the v0.21.5 contract: only serve and migrate do). It checks the schema version at startup and logs an ERROR pointing at aveloxis migrate when the DB is behind.

  • Writes its PID to ~/.aveloxis/aveloxis-scancode-worker.pid, and is managed like the other components: aveloxis start scancode-worker / aveloxis stop scancode-worker (v0.29.4). It is never part of all (a primary that still runs its in-serve pool would double up).

  • All collection.scancode_* knobs apply (workers, cadence, clone dir, adaptive timeouts, ignore globs, timeout-cap strikes).

  • Do not run aveloxis serve on the dedicated host. serve is the full scheduler — it migrates and collects — whatever knobs the config carries. Since v0.29.4 the incident’s shape (a binary AHEAD of the schema stamp) is refused twice: the start serve deploy gate refuses when the stamp is behind the binary (no migration of that binary has completed here — the ladder’s step 2: the release checklist’s migrate, aveloxis migrate --skip-views on the standard ladder), and serve’s own startup migration refuses to run a full pass while another aveloxis-serve is connected. Since 2026-09-11 a same-version start serve is refused too: serve will not start a second full scheduler against a database that already has one, because the two compete for the same queue and API keys. If you genuinely want two, say so explicitly with aveloxis serve --allow-second-serve. stop scopes its backend check to this host.

Full recipe — Postgres remote access, minimal config template, systemd unit, libmagic version-lock — in the dedicated scancode host guide.


aveloxis test-mail

Sends a test email through the configured Gmail SMTP settings (v0.20.14). Validates the mail block of aveloxis.json first (fail-fast on syntactic mistakes like a bare domain in gmail_user or a non-App-Password), then actually exercises the credentials.

aveloxis test-mail you@example.org

Use at deploy time so the first user signup isn’t the first SMTP attempt.


aveloxis collect

One-shot collection of specific repos without the scheduler. Uses the direct collection pipeline (bypasses staging, writes directly to relational tables). Best for testing or collecting a small number of repos.

aveloxis collect [flags] <url> [<url> ...]

Flags

Flag

Type

Default

Description

--full

boolean

false

Full historical collection. Ignores the days_until_recollect window and fetches all data from the beginning.

--augur-keys

boolean

false

Also load API keys from Augur’s augur_operations.worker_oauth table.

Examples

# Incremental collection (only new data since last run)
aveloxis collect https://github.com/chaoss/augur

# Full historical collection
aveloxis collect --full https://github.com/chaoss/augur

# Multiple repos, mixed platforms
aveloxis collect \
  https://github.com/torvalds/linux \
  https://gitlab.com/fdroid/fdroidclient

aveloxis add-repo

Adds repositories to the collection queue. Platform is auto-detected from the URL.

aveloxis add-repo [flags] <url> [<url> ...]

Flags

Flag

Type

Default

Description

--priority

integer

100

Queue priority. Lower numbers are collected first.

--from-augur

boolean

false

Import all repos from augur_data.repo. Each URL is verified via HTTP HEAD – dead repos are skipped.

URL formats

# Single GitHub repo
aveloxis add-repo https://github.com/chaoss/augur

# Single GitLab repo (including nested subgroups)
aveloxis add-repo https://gitlab.com/group/subgroup/project

# GitHub organization (adds all repos in the org)
aveloxis add-repo https://github.com/chaoss

# Multiple repos at once
aveloxis add-repo \
  https://github.com/torvalds/linux \
  https://github.com/chaoss/grimoirelab

# High priority
aveloxis add-repo --priority 10 https://github.com/kubernetes/kubernetes

# Import from Augur
aveloxis add-repo --from-augur

Platform detection

  • URLs containing github.com are treated as GitHub

  • URLs containing gitlab in the hostname are treated as GitLab

  • Hostnames listed in gitlab.gitlab_hosts in the config are treated as GitLab


aveloxis add-key

Stores API keys in the database for use during collection.

aveloxis add-key [flags] [<token>]

Flags

Flag

Type

Default

Description

--platform

string

(required unless --from-augur)

Platform for the key: github or gitlab.

--from-augur

boolean

false

Bulk import all keys from augur_operations.worker_oauth. Duplicates are skipped.

Examples

# Store a GitHub token
aveloxis add-key ghp_your_github_token --platform github

# Store a GitLab token
aveloxis add-key glpat-your_gitlab_token --platform gitlab

# Bulk import from Augur
aveloxis add-key --from-augur

aveloxis prioritize

Pushes a repository to the front of the collection queue.

aveloxis prioritize <url>

Sets the repo’s priority to 0 and due time to now. The scheduler will collect it next.

aveloxis prioritize https://github.com/chaoss/augur

Also available via the REST API:

curl -X POST http://localhost:5555/api/prioritize/42

Where 42 is the repo’s repo_id.


aveloxis recollect

Flags one or more repositories for a full (since=zero) re-collection on their next scheduler cycle.

aveloxis recollect <url>...

Sets the force_full_collect flag on each named repo’s aveloxis_ops.collection_queue row. When the scheduler dequeues a flagged repo, determineSince() returns zero time, triggering a full re-collection regardless of last_collected. The flag clears itself on the next successful completion.

aveloxis recollect https://github.com/chaoss/augur
aveloxis recollect https://github.com/a/b https://github.com/c/d     # multiple

Use this command:

  • After a bugfix or schema change that invalidates previously-collected data for a specific repo.

  • To manually force a refresh when you suspect the incremental-since window missed something.

  • In combination with aveloxis prioritize <url> to start the full re-collection immediately rather than at the repo’s normal due time.

Automatic triggering (v0.18.24+)

The scheduler also sets this flag automatically when a collection ends with an error that indicates incomplete PR child data — specifically the GraphQL PR batch error classes (stream CANCEL mid-body, “Timeout on validation of query”, or retry exhaustion). The next cycle then backfills whatever the failed batch missed. Operators see a force_full_recollect set WARN log line when auto-flagging fires.

See the troubleshooting guide for details on the error classes that trigger auto-flagging.


aveloxis migrate

Creates or updates the database schema. Without --skip-views it also drops and re-creates every materialized view from its definition, which is the only command that applies a changed view definition; with --skip-views the views are left as they are.

aveloxis migrate

Creates 149 tables across three PostgreSQL schemas, plus 20 8Knot materialized views when collection.materialized_views is enabled (the default) and, always, the two supply-chain views:

  • aveloxis_data (103 tables + 22 materialized views) – all collected data

  • aveloxis_ops (42 tables) – operational state

  • aveloxis_scan (4 tables) – scancode per-file license/copyright results

Also performs a data cleanup pass that nullifies garbage timestamps (year < 1970) across all tables, preventing BC-era dates from poisoning queries.

Safe to run repeatedly. All DDL uses CREATE ... IF NOT EXISTS and inserts use ON CONFLICT DO NOTHING. Does not touch Augur schemas if sharing a database.

Upgrading an existing deployment across many releases? migrate covers every schema change and one-shot SQL backfill, but a separate set of operator-run heal commands (API-calling or long-running repairs) is deliberately not a migration — see Upgrading for the ordered list with the release that introduced each.

The completed-backfill ledger (v0.28.4)

Expensive one-shot data steps — keyset backfills, history rotations, dedups, the timestamp cleanup — record their completion in aveloxis_ops.migration_ledger and are skipped on every later migrate. Before the ledger, every version bump re-walked all of them as no-ops (~1.5–2.5 hours on a fleet-scale database). DDL steps (tables, columns, indexes, views) are deliberately not ledgered: an explicit aveloxis migrate still heals hand-dropped objects on every run.

A step is recorded only after it completes with zero errors — a failed step re-runs on the next migrate automatically. To force a specific step to re-run (for example after hand-repairing data it targets), delete its ledger row and migrate again:

-- See what has been recorded:
SELECT step_label, completed_at, tool_version FROM aveloxis_ops.migration_ledger ORDER BY completed_at;

-- Replay one step:
DELETE FROM aveloxis_ops.migration_ledger WHERE step_label = 'v0.27.104 backfill pull_requests.meta_head_id/meta_base_id';

then run aveloxis migrate --skip-views. Every ledgered step is idempotent, so replays are always safe.


aveloxis backfill-identities

One-off repair for missing identity attribution (v0.26.5 — see summary/identity-attribution-audit-2026-07-09.md). The 2026-07-09 audit found cntrb_id 0% populated on issue_assignees, pull_request_assignees, pull_request_reviewers, and pull_request_meta, and issues.closed_by_id at 0.015% — while the raw identity material (platform user ids) was stored at ~100%.

aveloxis backfill-identities --dry-run          # candidate counts, no writes
aveloxis backfill-identities                    # all phases
aveloxis backfill-identities --phase 1          # assignment joins + pr_meta owners + pr_repo owners (SQL only)
aveloxis backfill-identities --phase 2          # closed_by from issue events (SQL only)
aveloxis backfill-identities --phase 3          # closed_by GraphQL timeline sweep (needs API keys)

Flags: --dry-run, --batch-size (primary-key window width per UPDATE batch, default 100000; on fleet-scale tables use 1000000), --limit (cap per phase, 0 = unbounded), --phase (all|1|2|3), --sweep-batch (issues per GraphQL query in phase 3, default 100).

Phases 1–2 are pure SQL derivation (no API calls; measured production coverage 99.87–99.98% for assignments). Since v0.27.104, phase 1 also runs the pull_request_repo.pr_cntrb_id owner pass — keyset windows over pr_repo_id on a table that is ~41.2M rows on the production fleet, so expect phase 1’s wall time to be dominated by it on large installations (use --batch-size 1000000 there). Like the other phases it is resumable and idempotent: interrupted runs re-walk windows cheaply because filled rows (pr_cntrb_id IS NOT NULL) drop out of the candidate set. Owner logins that match zero contributors (deleted forks) or MORE THAN ONE active contributor (ambiguous — v0.27.123) stay NULL; no fabricated attributions. Both owner passes are GitHub-only — GitLab rows heal forward via ID-qualified owner refs on re-collection. Phase 3 fetches closers the history-capped events feed cannot reach, via per-issue timelineItems(itemTypes:[CLOSED_EVENT]) batched ~100 issues per GraphQL query (~3 points each). Run phase 2 after the v0.26.3 event-healing cohort completes for best coverage; all phases are idempotent and resumable. Rows whose identity genuinely isn’t derivable stay NULL — no data is fabricated.

aveloxis dedup-repos

Merges case-variant duplicate repositories (v0.25.32). GitHub and GitLab treat owner/repo URLs case-insensitively, so github.com/azure/azure-sdk-tools and github.com/Azure/azure-sdk-tools are the same repository — but fleets that predate v0.25.32 could accumulate both as separate rows, each collected in full (doubled API budget, storage, and double-counted analytics).

aveloxis dedup-repos --dry-run      # show the plan (first 20 pairs)
aveloxis dedup-repos --limit 50     # canary batch
aveloxis dedup-repos                # full run — re-run until "0 pairs"

Flags

Flag

Default

Purpose

--dry-run

false

Print the merge plan without writing. Shows winner/loser URLs + repo_ids, per-side last-collected dates, and whether either side is mid-collection.

--batch-size

50

Pairs merged per batch. Each pair is one heavy transaction (repoints + child-data deletes).

--limit

0

Cap total pairs merged this run (0 = no cap).

What each merge does

Per pair, winner = the oldest repo_id, in one transaction:

  1. Repoints user_repos to the winner — every user group that referenced either variant keeps the repo and immediately sees the winner’s collected data.

  2. Repoints shared-copy rows (messages, email_message, commit_comment_ref, foundation_membership) — these tables are globally unique, so the pair shares one copy. (contributor_repo is deliberately untouched: it is the breadth worker’s observational record of contributors’ GitHub-wide activity, keyed by the numeric gh_repo_id, not a catalog reference.)

  3. Deletes the loser’s duplicated child data (issues, PRs, commits, releases, dependency scans, …) leaves-first, then the loser row itself. Nothing is lost: both sides collected the same repository.

  4. Enqueues the winner if it has never been collected.

Pairs with either side status='collecting' are left out of each batch’s window so they cannot stall the run (a mid-collection pair at the head of the alphabetical order used to occupy a slot every round); the end-of-run summary reports how many groups remain — re-run once those jobs finish. The command is idempotent; merged pairs drop out of the candidate set.

Precondition

The merge refuses to start until the v0.28.18 migrate has built the email_message FK indexes (idx_email_message_repo_id / idx_email_message_signaled_repo_id) — without them every pair would sequential-scan that table on the repoints and again at commit. Run the pending deploy steps first (aveloxis deploy-checklist --pending prints them and names the migrate); the refusal names the missing index. The precondition checks the indexes, not the schema stamp, so a fleet that built them in an earlier release passes it on a binary that is not yet deployed: keeping that order is up to you.

After the run

Once the pending deploy steps are done (so the views already carry the release’s definitions):

aveloxis migrate --skip-views   # builds uq_repos_repo_git_ci — the permanent
                                # DB-level backstop (skipped with a WARN while
                                # duplicates remain)
aveloxis refresh-views          # matviews stop double-counting immediately

Generic-git repos (platform 3) are never touched: unknown hosts may legitimately be case-sensitive, so their URLs stay byte-exact.


aveloxis migrate-cntrb-ids

One-shot opt-in data migration (v0.22.2): rewrites contributors whose cntrb_id is a legacy random UUID to the deterministic PlatformUUID(platform_id, platform_user_id) form, cascading through all child tables via the v0.22.1 ON UPDATE CASCADE FKs.

aveloxis migrate-cntrb-ids --dry-run     # show the plan
aveloxis migrate-cntrb-ids               # execute (batched transactions)
aveloxis migrate-cntrb-ids --limit 10000 # incremental run

Flag

Default

Description

--dry-run

false

Show the plan and a sample without writing.

--batch-size

5000

Rows per transaction (use 500 on fleet-scale DBs).

--limit

0

Cap rows migrated this run (0 = no cap).

--skip-precheck

false

DANGEROUS: skip the ON UPDATE CASCADE precheck.

Run aveloxis refresh-views afterwards. Collision pairs are left for merge-cntrb-collisions.


aveloxis merge-cntrb-collisions

Soft-merges the rename-collision pairs migrate-cntrb-ids skips (v0.22.3): same platform user under two rows. Moves identities to the winner, merges non-empty fields, inserts an alias, marks the loser cntrb_deleted = 1 (row stays in place — no FK rewrites).

aveloxis merge-cntrb-collisions --dry-run
aveloxis merge-cntrb-collisions

Flag

Default

Description

--dry-run

false

Show the merge plan without writing.

--batch-size

500

Pairs merged per batch (one transaction each).

--limit

0

Cap total pairs merged this run.


aveloxis heal-collection-gaps

aveloxis heal-collection-gaps --dry-run          # list candidates + gap sizes
aveloxis heal-collection-gaps --repo-id 49290    # heal one repo
aveloxis heal-collection-gaps --workers 4        # the fleet pass

The isolated healer (v0.27.140) for issues/PRs lost to the pre-v0.27.139 incremental blind-window bug. Finds repos whose metadata issue/PR counts exceed the stored counts and runs a threshold-0 gap fill on each: list every issue/PR number from the API, diff against the database, fetch ONLY the missing items (with children and comments), stage, process.

Targeted by construction — only count-gap candidates are visited (~5% of a typical fleet), never a 100% rescan. A healed repository keeps its place in the recollection cycle (v0.29.68: the heal’s release leaves due_at alone; before, every healed repository became due at once), and a serve started while a heal runs leaves the heal’s parked repositories to it (its heartbeat keeps them fresh; only a heal whose heartbeat stopped for longer than the stale-lock window is reclaimed). The candidate query is the resume state: healed repos drop out, so the workflow is re-running until “0 candidates”. Safe beside a running serve: each repo is drain-locked for the duration of its heal, and repos mid-collection are skipped (a rerun catches them). Neither the lock nor the heal touches last_collected. While a repo is locked the monitors count it under Parked (drain / heal). Ctrl-C (or SIGTERM) stops the run cleanly, releases every repo it still holds, and exits nonzero (v0.29.65). The log line names the after_repo_id of the interrupted page’s start, so --after-repo-id revisits that page; a plain rerun works too.

Flags: --dry-run (list the candidates and their gap sizes, healing nothing; with --repo-id, print that repo and whether a real run could lock it — before v0.29.65 that combination actually healed the repo), --limit N, --workers N (default 4), --repo-id N, --after-repo-id N (keyset resume), --all (sweep every collected repo — the completeness mode for repos whose stored-but-deleted rows numerically hide the gap; not recommended for routine use). Exits nonzero when any repo’s heal failed, and with --repo-id also when the repo could not be locked: the message says whether it is being collected, is parked by a staging drain or another heal run, has no queue row (gone or dequeued, so nothing will heal it), became queued again in the meantime (rerun now), or whether its queue status could not be read.

Sizing, measured on a ~140K-repo fleet (2026-08-23): 6,809 candidates / 279,100 items took ~65 hours at --workers 4 (the largest single repo filled 24,845 items); reruns then converged in minutes. Expect a small residual candidate set that heals with filled=0 on every rerun — repos whose forge metadata count exceeds what the forge’s own listing returns (transferred or hidden items); that is the floor, not unfinished work.

Run it on a binary at v0.27.139 or later — earlier binaries re-open the blind window on the next routine cycle. Typical ordering after an upgrade across the v0.27.13x train: aveloxis migrate first, then this command, then aveloxis refresh-views once the heal settles.

aveloxis refresh-views

Manually refreshes the materialized views this database has — both sets, or one.

aveloxis refresh-views                     # both sets (--set all)
aveloxis refresh-views --set 8knot         # the 20 views in matviews.sql (8Knot / analytics)
aveloxis refresh-views --set supply-chain  # explorer_package_exposure + explorer_package_advisory (seconds)
aveloxis refresh-views --aggregates        # + the dm_repo_* / dm_repo_group_* aggregate tables

Uses REFRESH MATERIALIZED VIEW CONCURRENTLY where unique indexes exist, so reads are not blocked during the refresh. A set the database does not have is reported and skipped. aveloxis serve refreshes the 8Knot set weekly (default Saturday; collection.matview_rebuild_day, which can also disable it) and the supply-chain pair every collection.supply_chain_refresh_hours (default 24; 0 disables the schedule). A refresh keeps each view’s definition: a release that changes an 8Knot view needs a plain aveloxis migrate, which re-creates that set; every migrate re-creates the supply-chain pair.

--aggregates (v0.28.18) additionally runs the dm_ aggregate pass after the views — the same per-repo loop the weekly rebuild runs unless collection.matview_rebuild_skip_dm_aggregates is set. It is off by default because that pass runs for hours to days at fleet scale; with the skip knob on, this flag is the only way the dm_ tables update. The pass holds a database advisory lock for its whole duration — if the weekly scheduler rebuild (or another --aggregates run) is already in it, the command exits nonzero with another dm_ aggregate rebuild is already running instead of interleaving two DELETE+INSERT passes over tables that have no unique key.

Both halves keep going past a failing view or repo but exit nonzero when any failed (v0.28.18): the error names the first ten failures with the full count, the rest of the pass’s work stands, and a view failure does not skip the aggregate pass. Scripts wrapping the command will see exits they never saw before — that is the point; re-run after fixing the named view or repo.


aveloxis sbom

Generates a CycloneDX or SPDX Software Bill of Materials from the dependency data collected for a repository.

aveloxis sbom 42 --format cyclonedx -o sbom.json

Flag

Default

Description

--format

cyclonedx

Output format: cyclonedx or spdx.

--output, -o

stdout

Write to a file instead of stdout.

--store

false

Also store the SBOM in repo_sbom_scans.

The repo must have been collected with dependency analysis (automatic under aveloxis serve). SBOMs are also downloadable from the web GUI and the REST API.


aveloxis install-tools

Installs optional analysis tools.

aveloxis install-tools

Installs three external analysis tools: scc (per-file code complexity; requires Go), scorecard (OpenSSF security posture; release tarball), and scancode (per-file license/copyright scanning; requires Python 3.10+ and pipx — also injects the typecode-libmagic plugin). If the Python prerequisite is missing, scancode installation fails and per-file license scanning never runs; see aveloxis upgrade-tools for updating installed tools.

If scc is not installed, the code complexity phase is silently skipped during collection.

Exits non-zero if any tool fails to install or installs off PATH (the same LookPath serve’s phases use), so aveloxis install-tools && aveloxis start all stops before a start without the tool (v0.29.68). Both install-tools and upgrade-tools are non-interactive: each tool runs in its own process group with pip and git prompts disabled (PIP_NO_INPUT, GIT_TERMINAL_PROMPT=0; a Homebrew formula install never prompts), so credentials must come from configuration or a keyring and a pip or git credential prompt fails at once (any other prompt, such as an ssh host-key or passphrase question when git fetches over ssh, is not answered and ends at the bound); each tool is bounded by the same limit as the monthly update check, and Ctrl-C kills the tool in flight and ends the walk. upgrade-tools and the monthly update check count a scorecard upgrade as failed when an older scorecard earlier on PATH would still be the one serve runs, and name both paths (v0.29.69).


aveloxis upgrade-tools

Upgrades the external analysis tools in place (v0.23.6). Unlike install-tools (which short-circuits when a tool exists), this re-runs the install path to pull updates: go install ...@latest for scc, a fresh release tarball for scorecard, and pipx upgrade for scancode (preserving venv customizations) plus a typecode-libmagic re-injection.

aveloxis upgrade-tools

Exits non-zero if any tool fails to upgrade, so it can run from a maintenance cron.


aveloxis start

Launches aveloxis components as detached background processes with log output redirected to files in ~/.aveloxis/.

aveloxis start serve            # scheduler + monitor        → ~/.aveloxis/aveloxis.log
aveloxis start web              # web GUI                    → ~/.aveloxis/web.log
aveloxis start api              # REST API                   → ~/.aveloxis/api.log
aveloxis start scancode-worker  # dedicated scancode worker  → ~/.aveloxis/scancode-worker.log
aveloxis start all              # serve + web + api (never the scancode worker)

PID files are written to ~/.aveloxis/aveloxis-{serve,web,api,scancode-worker}.pid. If a component is already running, the command reports it and skips the launch. scancode-worker (v0.29.4) is the dedicated-host process from Dedicated Scancode Host; all deliberately excludes it.

The exit status is the contract for scripts: a component that could not be started — a pidfile that cannot be read (the command refuses rather than risk a second scheduler on the host), a log file that cannot be opened, a failed exec — makes the command exit nonzero, naming each failure, after every requested component has been attempted. start all therefore still tries web and api beside a refused serve, and says so. Since v0.29.68 start writes the child’s pidfile at once (the “already running” guard reads that file, so a second start during the child’s startup is refused), then waits for the child’s readiness signal on an inherited pipe and reports a child that exited first. web and api signal readiness once the schema gate has passed AND the port is bound (a port in use is a refusal the process exits on); the scancode worker once the schema gate has passed; serve once its keys are loaded and its startup migration is done. web, api and the scancode worker refuse to start while the schema stamp is behind their binary, so a --skip-deploy-check start on an un-migrated fleet reports them as exited; start them again after the migrate. A child still starting after 30 seconds (a long migration for serve) is reported as started. An already-running component is a no-op and exits 0.

Log files are opened in append mode — existing content is preserved across restarts.

Note: start does NOT survive a reboot. For production hosts that should come back automatically after a restart, run the three processes under systemd instead — see Running Aveloxis as a Service. Pick one manager per host: once the systemd units own the processes, aveloxis start/stop must not be used there.


aveloxis stop

Gracefully stops background aveloxis processes.

aveloxis stop serve            # stop only the scheduler
aveloxis stop web              # stop only the web GUI
aveloxis stop api              # stop only the REST API
aveloxis stop scancode-worker  # stop the dedicated scancode worker
aveloxis stop all              # stop serve + web + api (never the scancode worker)
aveloxis stop                  # (no args) same as 'all'

Sends SIGTERM to the specified component(s) using PID files in ~/.aveloxis/. For serve: in-flight API calls and statements are cancelled at once; workers get up to collection.shutdown_grace_seconds (default 10) to unwind (a completion reached at the cancel is stamped on a retry bounded by half the grace, at most 5 s), then queue locks are released; staging data is preserved. For web and api: the listener closes, in-flight requests get 10 s to finish, then the pool closes. The scancode worker has its own bounds (collection.scancode_shutdown_grace_minutes; see Graceful shutdown). PID files are removed after a successful stop or when they are stale (process no longer running); a file the command could not read, whose process it could not signal, or that it could not remove (the message carries the error) is left in place for you to inspect; a stale file another start replaced meanwhile is left to that start. stop all names a scancode worker it left running.

Nothing to stop is exit 0 — stop is idempotent. A process that was found but could not be signaled (typically operation not permitted on a process another user started) is a failure: its pidfile is left in place, every other requested component is still stopped, and the command exits nonzero naming the one it could not. A pidfile that exists but cannot be read — unreadable, or carrying something that is not a PID — is the third arm: it is not evidence that the component is stopped, so the command still tries the pgrep fallback — which normally finds a component that start launched, so you will see Stopped serve (PID N) AND a nonzero exit reading serve stopped, but: pidfile left in place …. The file is neither stale nor live, so it is left for you to inspect and delete by hand; the next start refuses on it until you do. If pgrep finds nothing either, the exit is still nonzero and reads serve: pidfile left in place … — nothing was stopped, and nothing could be confirmed. A pgrep that itself fails (not installed, or any exit other than its documented “no match”) is likewise not evidence of absence: the command exits nonzero reading serve: pgrep for serve: ….

After SIGTERM the command watches pg_stat_activity for the component’s backends and, past the shutdown budget, prints the persistent PIDs with a pg_terminate_backend recipe. Since v0.29.4 that recipe is offered for this host’s backends only — the ones whose client address is this session’s own, or local to the database host (a unix socket, or loopback in either address family). Every other backend carrying the same tag falls into one of two reported arms, is never waited on, and is never offered for termination:

  • Another client address — normally another machine running this component against the same database (the primary’s serve seen from a dedicated scancode host, or the reverse). The check knows the address, not the machine, so a serve on THIS host that dialed a different DSN address reads the same way.

  • Address not visible — a backend of a database role whose privileges this one does not hold. pg_stat_activity shows a session’s client_addr only to roles that HOLD that session’s role’s privileges and to roles that hold pg_read_all_stats’s; everyone else reads NULL, the same NULL a unix socket shows. stop prints a no-verdict note for these — neither this host’s nor another’s.

To get a verdict on the second arm, run stop as that role, or grant this one the stats privileges:

GRANT pg_read_all_stats TO <role>;

Holding is the test, not membership: a NOINHERIT role, or one granted WITH INHERIT FALSE, is a member of pg_read_all_stats with none of its privileges and still reads NULL. On PostgreSQL 16 and later a grant’s inherit option is fixed at GRANT time, so if the grant already exists and does not inherit, re-grant it with GRANT pg_read_all_stats TO <role> WITH INHERIT TRUE. Check with SELECT pg_has_role(current_user, 'pg_read_all_stats', 'USAGE').

The check needs the config (-c) to reach the database; without it the command says the check was skipped.

How a backend is placed on a host

Each component tags its connection pool with an application_name that carries the component and this machine’s hostname, separated by @:

aveloxis-serve@kate
aveloxis-scancode-worker@runner-01
aveloxis-web@kate
aveloxis-api@kate

stop matches on the part before the @ (the component) and compares the part after it (the host) against its own machine.

The rule in one sentence: a marker can only separate hosts, never merge them. The client address decides, and the marker can only veto it. A backend is placed on this host when its client address is this host’s and its marker does not contradict that — so a backend that reaches the database from a different address is never this host’s, whatever its marker says, and two backends sharing an address but carrying different markers are correctly separated. Either marker being absent means the marker abstains and the address rule alone decides.

The composition is deliberately one-directional: it never places a backend here that the client-address rule alone would have placed elsewhere. That is what keeps marker quality out of the safety argument — os.Hostname() is not unique across machines (two container hosts running the same compose file report the same hostname), so a marker collision degrades to the address rule rather than to a pg_terminate_backend recipe for a machine you are not on.

Your own pg_stat_activity queries should match with LIKE 'aveloxis-%' (which still works unchanged) or with split_part(application_name, '@', 1) = 'aveloxis-serve'. Matching the full tag by equality will find only the backends of one specific host.

A host whose kernel will not report a hostname tags un-suffixed (aveloxis-serve) and falls back to the client-address rule.

Warning

Behind a transaction pooler (pgbouncer in transaction or statement mode, pgcat, Odyssey), the client address is useless — the host marker is not. Every client reaches PostgreSQL through the pooler, so pg_stat_activity.client_addr is the pooler’s address for every backend, this host’s and every other host’s alike. The address rule alone would read them all as one host and offer a terminate recipe for another machine’s serve. The @host marker survives the collapse because PostgreSQL stores the application_name the client sent and never rewrites it.

The residual is the upgrade window. A backend tagged by a pre-v0.29.4-round-12 binary carries no marker, so it falls back to the address rule — and behind a pooler that rule reads it as this host’s. Until every host on the database runs a build with the marker, treat a “Persistent PIDs (this host)” entry on a pooled deployment as unverified and confirm with ps on each host before terminating. Once every component is upgraded, the verdict holds through a pooler.

The other residual: one host reached over two addresses reads as two. If a component was started against a DSN naming this machine’s LAN address while stop runs against a localhost DSN (or one side goes through a pooler and the other does not), the addresses differ, the address rule says “another host”, and the marker cannot overrule it. stop then reports the component’s own backends under “other hosts” and returns without waiting out their drain. From inside the database that case is indistinguishable from two machines that happen to share a hostname, and only one of the two readings can print a terminate recipe for a machine you are not on — so aveloxis takes the safe one. Run stop with the same -c config the component was started with and the addresses match.

A session-mode pooler is unaffected in principle only if it preserves the client address, which pgbouncer does not — but with markers in place that no longer matters for the host verdict.

Note

aveloxis stop also works for processes started in the foreground (e.g., aveloxis serve), because all foreground processes write PID files on startup.


aveloxis data-test

Operator-driven shadow-database verification harness for schema changes. Builds binaries from a tagged release and the local working tree, provisions two scratch databases (aveloxis_released and aveloxis_new), collects the same repo into each, and reports row-count differences. Catches data-loss regressions before they ship. Shipped in v0.22.8.

See the full guide: Schema-change verification.

Flags

  • --released-tag TAG — git tag of the released aveloxis version to compare against (e.g., 0.22.6). The tag must exist in the local clone; git fetch --tags if missing. Required.

  • --repo URL — git URL of the test repo to collect into both scratch DBs. augurlabs/augur is the canonical choice. Required.

  • --keep-dbs — retain the scratch DBs after the run. Default is to drop them. Pass when you want to inspect a failing table via psql after the report is written.

  • --work-dir PATH — where to put binaries, logs, and the report. Default is /tmp/aveloxis-data-test-<UTC-timestamp>.

Behavior

  • Builds the released binary via git worktree add (reuses local clone’s objects — no remote fetch).

  • Builds the local binary from the current working tree.

  • Connects to the configured PostgreSQL host using the operator’s aveloxis.json credentials. The user must have CREATEDB privilege because the harness creates and drops scratch databases.

  • Copies API keys from the operator’s primary aveloxis_ops.api_keys table into both scratch DBs — operator doesn’t re-paste tokens.

  • Collections run sequentially (~30 min each), not parallel, because both sides share the API key pool.

  • Exit code 0 on PASS or FLAG-only; exit code 1 on any FAIL (row loss detected) — suitable for CI gating.

Examples

# Validate the current working tree against v0.22.6 using augur
aveloxis data-test --released-tag 0.22.6 \
  --repo https://github.com/augurlabs/augur

# Keep scratch DBs for ad-hoc inspection after a FAIL
aveloxis data-test --released-tag 0.22.6 \
  --repo https://github.com/augurlabs/augur \
  --keep-dbs

# Custom work directory (useful for CI artifact retention)
aveloxis data-test --released-tag 0.22.6 \
  --repo https://github.com/augurlabs/augur \
  --work-dir /var/cache/aveloxis-data-test

The full report is written to <work-dir>/report.md. See Schema-change verification for guidance on interpreting PASS / FLAG / FAIL results.


Column-fill diff (v0.26.1)

Row counts can’t see a column the new binary stopped populating (the canonical case: issue_labels.platform_label_id is 0 on every row under the GraphQL path while row counts match exactly). After the row-count diff, data-test therefore also compares per-column FILL COUNTS — how many rows carry a meaningful value, type-aware (<> '' for text, <> 0 for numerics, IS NOT NULL otherwise) — across every column of every base table in aveloxis_data, aveloxis_ops and aveloxis_scan. The fourth schema, aveloxis_augur_data, holds only the Augur-compatibility views — derived data with no base tables to diff — so it is deliberately out of scope.

  • FAIL (exit code 1): a column populated under the released binary is completely unpopulated under the new one — a dropped mapping or renamed JSON tag.

  • FLAG: fill counts differ — review; small drift is expected since the two collections run against a live repo minutes apart.

aveloxis shadow-diff

Semantic equivalence diff of issue/PR collection between two databases (the harness behind the REST → GraphQL refactor’s per-phase validation). Compares issues, PRs, labels, assignees, reviewers, reviews, commits, files, messages, and bridge tables by platform-stable keys.

aveloxis shadow-diff --rest-dsn postgres://.../aveloxis_shadow_rest \
                     --graphql-dsn postgres://.../aveloxis_shadow_graphql

Flag

Default

Description

--rest-dsn

(required)

Postgres DSN for the baseline database.

--graphql-dsn

(required)

Postgres DSN for the comparison database.

--repo-id

0

Restrict the diff to one repo_id (0 = all).

--json

false

Machine-readable report for CI pipes.

Exit code 1 on any FAIL-level difference. For a coarser whole-schema row-count comparison, see aveloxis data-test.


aveloxis reconcile-repos

Heals the stranded-repo class: non-archived repos rows with no collection_queue row — invisible to the scheduler forever. Production audit (2026-07-21) root-caused the cohort to GitHub renames + prelim’s duplicate skip+dequeue (which dequeues without archiving), plus a smaller lost-enqueue share. Each stranded repo is classified by a LIVE redirect check:

  • dead upstream (404/410/451) → archived (the outcome prelim applies)

  • redirects to a tracked repo → dataless duplicates heal onto the winner (HealRenamedDuplicate); data-bearing duplicates consolidate through the dedup-repos per-pair machinery (repoints + leaves-first deletes)

  • redirects to an untracked URL → re-enqueued (prelim renames it in place on the next cycle — rename detection is prelim’s job)

  • alive at its own URL → re-enqueued (a lost queue row)

aveloxis reconcile-repos --dry-run   # per-repo classification, no writes
aveloxis reconcile-repos --limit 50  # canary
aveloxis reconcile-repos             # everything

The scheduler also logs a startup gauge (non-archived repos with no collection_queue row) pointing here whenever the count is non-zero. Re-run until stranded = 0; healed repos drop out of the set. Since v0.29.68 Ctrl-C or a SIGTERM (kill <pid>) cancels the in-flight probe or statement, ends the walk and prints an interruption summary (what was archived, healed, consolidated and enqueued so far, with the dry-run marker when set); the exit is nonzero, every outcome is idempotent, and a rerun walks the whole cohort again (there is no resume marker).

Both consolidation arms (the dataless heal and the per-pair merge) share dedup-repos’ precondition: the v0.28.18 migrate must have built the email_message FK indexes, or each such repo is skipped with a warning naming the migrate to run first (the other classifications — dead, re-enqueue — proceed) and the run exits nonzero at the end so a script cannot read a refused run as success. The same nonzero exit applies when a stranded repository’s stored URL carries credentials (v0.29.57): the row is not probed, is logged as an error and counted as refused; correct repo_git and re-run.

aveloxis generate-showcase

Renders the PUBLIC collection showcase pages (v0.27.77 growth plan): every admin-curated collection becomes a static, SEO-indexable HTML page under <out>/showcase/, plus a showcase index, plus the site sitemap.xml (when --gui-root is given — the generator is the single writer of the sitemap once deployed). Pages carry full meta/OG/JSON-LD and render ONLY collection metadata + cached per-repo counts — never user names, stars, or group ownership (queries run anonymously). Writes are atomic (tmp+rename) and pages whose collection was deleted or renamed are pruned.

Since v0.27.78 the collection tables order by collected issues (commit counts are bot-floodable — automation repos with millions of machine commits headlined the tables), and the top 5 non-fork repositories of each collection also get a public repo snapshot page under <out>/repos/, deduped across collections. Forks (repos.forked_from non-empty, captured by Phase 0 since v0.27.78) are skipped and the next non-fork slides into the freed slot. Each snapshot carries the cached commit/issue/PR counts, description and primary language, a static weekly-activity chart in the signed-in grammar (commits + issues + PRs-opened stacked bars, PRs-merged line overlay; trailing 12 months, baked SVG — no JS, no endpoints), every temporal CHAOSS-metric chart the signed-in repo page shows (the seven store-backed metrics plus burstiness, project velocity, and the drive-by/repeat retention cohorts — computed through the SAME functions the compare API uses), each line chart carrying the live site’s full trend grammar (dashed OLS trend, ±2σ residual tube, red breach dots, slope/R² chip — a Go port of lib/trend.js with identical formulas), the latest OpenSSF Scorecard table, and the open-vulnerability posture — with honest “not yet scanned” / “analysis pending” / “no collected activity” states instead of fabricated zeros. Collection pages highlight the featured rows and carry a sign-in reminder as its own row directly under them; everything below the cut stays forge-linked. Snapshot pages ride the same sitemap, prune pass, and privacy contract.

v0.27.80 also emits /showcase/compare.html — a fully static comparison demo of the four featured repos with the most comparable activity levels, one baked SVG chart per headline metric (commits, issues, change requests, contributors), linked from the showcase index. The slug compare is reserved (a collection literally named “Compare” gets compare-2).

aveloxis generate-showcase \
  --out /var/www/aveloxis-gui/showcase \
  --gui-root /var/www/aveloxis-gui \
  --base-url https://aveloxis.io

The whole run is bounded by --timeout (default 60m; accepts any Go duration like 90m or 2h; 0 or negative disables the deadline entirely). The pre-v0.27.88 hardcoded 10-minute budget was sized before the snapshot pages carried the full chart set — on a fleet whose featured repos include kubernetes-scale projects, the ~16 snapshot pages × ~10 analytics queries each exceed 10 minutes and the run died with a misleading per-query context deadline exceeded on whichever chart the clock expired at (aborting compare.html, the sitemap, and the prune pass with it). If a run still times out, raise --timeout — the first run on a cold Postgres cache is the slowest; subsequent hourly runs benefit from warm buffers.

Runs hourly from a systemd timer (the Aveloxis project’s own site keeps its unit with the site). Read-only on the schema; does not run migrations (v0.21.5 policy). Safe alongside an active aveloxis serve.

aveloxis adopt-forge-id

Treats a repository that was deleted and re-created on its forge under the same URL as a continuation of the one Aveloxis already tracks (v0.29.62).

aveloxis adopt-forge-id --list                         # recorded changes: pending, superseded, adopted
aveloxis adopt-forge-id --repo-id 126257 --repo-id 98226
aveloxis adopt-forge-id --repo-id 126257 --note "upstream re-created by the org"

A re-created repository gets a new numeric forge ID. The org scan notices the stored and the forge’s ID differ, logs a forge-ID mismatch ERROR, and records the change as pending; it never changes the repository row itself. --repo-id asks GitHub or GitLab for the repository’s current ID and creation date, replaces the stored ID, and records the change as adopted. It refuses, changing nothing, when the forge lookup fails, when the forge still reports the stored ID, or when the stored ID changed in the meantime. A GitLab repository is only looked up on the configured GitLab instance (project IDs are per instance, and the keys stay on that host); a generic git repository has no API to ask.

The repository page then shows: “Note: this repository changed forge identifiers on , from to . This is unusual, and may affect statistics.” The row holds data from both upstream repositories: commit history usually lines up (a re-push keeps the hashes), but issue and pull request numbers restart in the new repository. The mismatch ERROR stops once the change is adopted.

An admin can do the same from the web GUI (v0.29.63): the approvals page lists pending changes under “Re-created repositories”, each with an Adopt button, one per recorded change. The button adopts exactly that change, with the creation date the scan saw; it does not ask the forge again, because the api process holds no forge keys. The card is hidden when nothing is pending. When the scan recorded two changes for one repository and one is adopted, the other no longer starts from the stored ID: --list marks it superseded, it leaves the card, and adopting it is refused.

aveloxis backfill-repo-metadata

Operator-driven fleet sweep (v0.27.79) that refreshes repos.repo_description, primary_language, languages, repo_archived, and forked_from from the forge APIs — one FetchRepoInfo per GitHub/GitLab repo, written through the same UpdateRepoMetadata path the collector’s Phase 0 uses. Generic-git repos are skipped (no API).

Built as a launch accelerator for fork status: v0.27.78 added fork capture to Phase 0, but the per-cycle forward path takes ~6–21 days to cover the fleet. This command front-loads it — roughly an hour for a 94K-repo fleet at the default 8 workers, costing ~94K GraphQL points (about a quarter of one hour of a 73-key pool’s budget).

aveloxis backfill-repo-metadata --dry-run       # candidate count only
aveloxis backfill-repo-metadata --limit 100     # canary
aveloxis backfill-repo-metadata                 # full sweep
aveloxis backfill-repo-metadata --after-repo-id 51234   # resume

Idempotent (every write is the forge’s current truth) and resumable — progress lines and the summary print the last repo_id processed; re-run with --after-repo-id after an interruption. Repos that 404 (renamed/deleted/private) are skipped and left to prelim’s normal rename/dead-repo handling. Does not run migrations (v0.21.5 policy).

aveloxis rewalk-whitespace

One-time full-history whitespace bootstrap (v0.27.105 — the fill-audit Workstream C). commits.cmt_whitespace was never measured while SUM(cmt_whitespace) feeds six live aggregate queries; the facade now measures it per cycle with Augur-parity semantics (an added blank line counts as whitespace, not added; a whitespace-only reformat of a

8-character line counts as whitespace instead of an add/remove pair — so cmt_added/cmt_removed on walked rows become the ADJUSTED Augur numbers). The per-cycle phase is incremental past a stamped marker (repos.whitespace_head_hash); this command does the full-history walk per repo, reusing the facade’s persistent bare clones, and stamps the marker so every later cycle stays incremental.

aveloxis rewalk-whitespace --limit 5            # canary first
aveloxis rewalk-whitespace --workers 8          # fleet run
aveloxis rewalk-whitespace --repo-id 12345      # single repo
aveloxis rewalk-whitespace --after-repo-id 51234  # keyset skip-ahead

Runtime expectation (grounded 2026-08-19): git log -p measured ~209 MB/s single-threaded; the fleet’s patch volume estimates at ~5–10 TB (162.7M commits, ~146B changed lines). At 8 workers expect roughly 8–24 hours wall-clock (outer bound ~2 days if disk-bound); the per-repo progress lines and every-50-repos running totals let you extrapolate from the first hour. Safe alongside a running serve — repos mid-collection are skipped this pass, and overlapping updates are same-value idempotent. Resumable by construction: the marker IS the resume state, so a re-run skips every already-walked repo. After the fleet run, aveloxis refresh-views --aggregates (or the next scheduled aggregate rebuild) picks the corrected sums into the dm_ tables. Does not run migrations (v0.21.5 policy) — run aveloxis migrate first so the marker column exists.

aveloxis data-verify

The standing data-verification program (v0.27.43): runs the read-only invariant probe battery against the configured database and exits 1 on any FAIL — suitable for gating releases and cron runs. Probes: structural invariants (case-duplicate repos, Default-group singleton, stranded repos, stale >24h locks, cross-kind message corruption with migration-state awareness), sampled count-integrity (cached queue counts vs actual rows, gathered vs forge metadata at the gap detector’s 5% line, batch-vs-single stats agreement), and fill rates (report-only unless floors are set).

aveloxis data-verify                          # full battery, human report
aveloxis data-verify --json                   # automation form
aveloxis data-verify --sample 500             # wider drift/equality sample
aveloxis data-verify --ground-truth 10        # ALSO re-fetch live GitHub
                                              # metadata for 10 sampled repos
                                              # and compare (needs API keys)
aveloxis data-verify --min-identity-fill 95   # optional floors → WARN below

Severities: FAIL = a code-enforced invariant is broken (exit 1); WARN = known-and-healing state or operator-attention signal (pending heal worklist, stranded repos, schema behind the binary — each WARN names its fix command); OK = verified. Safe against production: every probe is read-only and bounded.

aveloxis heal-messages

Repairs message rows corrupted by the pre-v0.27.38 cross-kind ID collision (issue/PR conversation comments, inline review comments, and review bodies share overlapping GitHub ID sequences; under the old two-column arbiter the later writer silently overwrote the earlier kind’s text — 198,237 rows on the production fleet). The v0.27.38 migration captures the affected rows into aveloxis_ops.message_heal_worklist; this command consumes it.

Each pass refetches the claiming parents’ comments per-item (parent-deduplicated — API cost is per distinct issue/PR, not per collision), re-creates the correct rows under the kinded arbiter, deletes the stale cross-kind bridge links, and stamps healed_at. Failed parents leave their rows pending; re-run until “nothing pending”.

Since v0.28.1 the command loops internally in 25,000-row passes and stamps healed_at at the end of EACH pass, so an interrupted run (Ctrl-C, reboot, kill) keeps everything already healed — at most one pass of progress attribution is lost. Hand-chunking with --limit is no longer load-bearing; --limit caps the rows CONSIDERED this run (claimed rows + parent refetches — a failure-heavy canary stays a canary).

Since v0.28.8 passes walk the worklist by a strictly increasing msg_id cursor: a fully-failing batch no longer stops the run (or starves the rows behind it) — the run continues past it, failed rows stay pending, and a re-run retries them from the bottom. The command exits nonzero whenever any claimed row failed to heal, so cron/scripts notice incomplete runs.

aveloxis heal-messages --dry-run       # plan: pending rows, distinct parents
aveloxis heal-messages --limit 1000    # canary pass
aveloxis heal-messages                 # everything pending

Flags: --limit (0 = all pending), --dry-run, --augur-keys. Requires GitHub API keys (this healer refetches). Safe with serve stopped; after a fleet restart, run it once the force-full wave crests so it doesn’t compete with collection for the key pool.

aveloxis heal-libyear

One-shot healer for libyear values that could never be computed.

A dependency’s libyear is the age gap between the version in use and the latest release, so it needs both release dates. When either is missing the calculation returns 0 — which is indistinguishable from a dependency that really is on the latest release, and avg() counts a 0 while it skips a NULL. Measured on a 3.6M-row fleet in September 2026, 87% of every row reading libyear = 0 actually meant “unknown”, and the reported fleet average was less than half the honest figure.

Since v0.29.57 the collector stores NULL for those rows as each repository is re-analysed. This command corrects the rows already stored that re-analysis can never fix: a dependency declared with no pinned version has no release date to measure from, whatever the registry says later.

aveloxis heal-libyear           # dry run: report how many rows qualify
aveloxis heal-libyear --apply   # replace those numbers with NULL
aveloxis refresh-views          # make explorer_libyear_summary reflect it

The dry run is the default — unlike reconcile-repos, where --dry-run is the opt-in flag. This rewrites hundreds of thousands of rows of collected data, so the mutation is the flag and the preview is free.

Rows whose version IS pinned but whose release date was missing are deliberately left alone: v0.29.56 fixed the Go and Maven resolvers responsible for most of them, so re-analysis fills in real dates. NULLing them here would erase rows the next collection cycle is about to answer properly.

Walks keyset windows over the primary key, so it is safe alongside a running serve and a cancelled run simply resumes on the next invocation.

aveloxis heal-commit-daily

One-shot filler for aveloxis_data.repo_commit_daily, the table the repository page’s weekly commit series and top-contributors commit counts read (0.29.73).

The commits table holds one row per file per commit, and one repository’s rows lie scattered about one per page across the whole table, so the two commit answers for a large repository read millions of pages (a kernel fork: two minutes, past the front end’s budget). The facade now folds the walk it already makes of the whole default branch into one row per repository, UTC day and author email, and replaces the repository’s rows after every completed walk; the two readers sum that table when the repository’s picture is complete (repos.commit_daily_complete_at, set by a facade walk whose every commit was proven written and by this command — never by a walk that swallowed writes; 0.29.75) and the window is UTC-day aligned, and read the commits table otherwise. Author identity: the commits table’s stored id when this command carried it and its contributor is still live (not merged away; 0.29.81), else GitHub’s numeric user id a noreply address carries (it survives a rename), else the login it names (through LOWER(gh_login)), else the house email rule (the contributor’s own emails, then contributors_aliases), each only when unambiguous among live contributors.

A repository collected before 0.29.73 has no rows until its next collection, and one whose only walk since swallowed commit writes has rows but no stamp; both are listed. This command fills such repositories now, largest first, by one scan of each repository’s commit rows — the cost its first page view pays today, paid once here:

aveloxis heal-commit-daily                # dry run: how many repositories need it
aveloxis heal-commit-daily --apply        # fill them, largest first
aveloxis heal-commit-daily --apply --limit 200   # only the 200 largest

Each repository is its own transaction, so an interrupt loses at most the one in flight; rerun to finish. It can run beside a running serve: every repository’s own next collection fills it anyway, so this only brings the largest forward — and a repository whose collection finishes between this command’s listing and its turn is skipped, because that walk’s fold is the authoritative default-branch picture (a rebuild from the commits table would bring back force-pushed commits); the closing line counts them. Minutes per kernel fork, seconds for most repositories; on a large fleet run it under nohup and read its log.

aveloxis heal-vulnerabilities

One-shot healer that re-scans every repository with stored vulnerability rows. Two healing classes it covers:

  • Rows stored before v0.27.4’s two-phase OSV fetch (they carry severity=UNKNOWN and empty summaries — the querybatch endpoint only returns id stubs). The re-scan fills severity, CVSS, summary, fix versions, aliases, and references from /v1/vulns/{id}.

  • False positives from malformed dependency versions (v0.27.71 — the “6.0.3, fixed in 5.4” class: an unparseable version defeats OSV’s version matching and every advisory in the package’s history is reported). As of v0.27.72 the scan normalizes stored versions and rebuilds purls at read time, so this command heals those findings immediately after deploy — no need to wait for each repo’s analysis phase to rewrite its dependency rows.

aveloxis heal-vulnerabilities              # all repos with findings
aveloxis heal-vulnerabilities --limit 50   # bounded canary run

Idempotent (prefer-nonempty upserts never downgrade filled data) and safe alongside a running serve. Without this command the same healing happens gradually as each repo’s next scheduled collection cycle re-scans it.

aveloxis mark-gone-repos

Probes repositories that have no collection-queue row against their forge and maintains the “gone” state (v0.28.1). Background: when a tracked repository is deleted — or its whole organization goes private, as the US Department of Veterans Affairs did in mid-2026 — the URL returns 404 and prelim sidelines it permanently (archives + removes from the queue). All collected data is kept, but before v0.28.1 nothing distinguished that state from “never collected”, so the GUI showed a false “queued for first collection” banner over real data.

The probe is bidirectional and only DEFINITIVE answers decide:

  • 404/410/451 → stamp repos.repo_gone_at. The repo page then shows “This repository is no longer publicly available or traceable on GitHub” over the data we hold.

  • 200 on a previously-stamped repo → clear the stamp and re-enqueue: if the organization re-publicizes, collection resumes automatically on the next run of this command.

  • Anything else (transport errors, rate limits, 5xx, unresolvable redirects) → the repo is skipped and a re-run retries it.

  • A stored URL carrying credentials (https://user:token@…, a row written before v0.29.57 refused such URLs) → not probed, logged as an error naming the repository, counted as refused, and the command exits nonzero: a re-run cannot help until repo_git is corrected. Scripts wrapping the command will see an exit they never saw before.

aveloxis mark-gone-repos --dry-run    # list what each probe would do
aveloxis mark-gone-repos              # stamp / clear / re-enqueue
aveloxis mark-gone-repos --limit 100  # bounded canary

Idempotent and re-runnable on any cadence. Since v0.29.68 Ctrl-C or a SIGTERM (kill <pid>; aveloxis stop knows only the four long-running components) cancels the in-flight probe or statement, ends the walk and prints an interruption summary (what was probed, stamped, cleared and skipped so far); every stamp is its own statement, so what landed stays, and a rerun walks the whole cohort again — every verdict is idempotent, so nothing is lost, only time. Dataless stranded rows (no queue row, no data, no archived flag) are not candidates — there is nothing to display for them either way. New gone repos are stamped automatically by prelim at collection time; this command exists for the historical cohort and for resurrection checks.

Since v0.29.7 aveloxis serve re-checks gone repositories on its own. A 404/410/451 removes the repository’s queue row, and the scheduler only ever visits queued repositories, so prelim never probes it again; before v0.29.7 a repository made private and later public again stayed “gone” until this command ran. The scheduler’s recheck ticker now re-probes every gone-stamped repository once per collection.gone_repo_recheck_days (default 28, up to 500 per hour, never-checked rows first) with the same probe and the same verdict rule, and logs gone recheck: repository is reachable again when it resurrects one. This command remains the immediate, whole-cohort form: run it after an upgrade or when you know an organization has flipped back, and it resets the cadence clock on every repository it verifies. The probe is one unauthenticated HEAD per candidate and consumes no API-key budget: at roughly 200 ms per probe a few thousand candidates take minutes and tens of thousands take an hour or more of wall-clock, and nothing else; --limit bounds a first run. See Recurring maintenance.

aveloxis run-scorecard

Bulk remote-primary OpenSSF Scorecard pass (v0.27.5). Walks every non-archived, at-least-once-collected GitHub repo and runs scorecard in remote mode (--repo, the full ~18-check set including Code-Review, Maintained, Contributors, Branch-Protection, CI-Tests, CII-Best-Practices, Signed-Releases), oldest-scorecard-first — repos that have never been scanned come first. Exists because the per-cycle collection phase historically ran local mode (~11 checks); this command upgrades the fleet’s scorecard data without waiting for each repo’s recollect cadence.

aveloxis run-scorecard                        # the whole backlog
aveloxis run-scorecard --limit 50             # canary run
aveloxis run-scorecard --older-than 180       # only stale/never-scanned repos
aveloxis run-scorecard --workers 8            # default NumCPU/2, capped at NumCPU

Details:

  • Refuses to start while aveloxis serve is running on this host (checked via the serve pidfile + process liveness) — the pass would compete with the collection workers for the shared GitHub API budget. Stop serve first: aveloxis stop serve. It also writes its own pidfile (~/.aveloxis/aveloxis-run-scorecard.pid) so two bulk passes can’t overlap.

  • Uses the SAME invoke/persist code as the collection phase: the comma-separated multi-token GITHUB_TOKEN built from the key pool (collection.scorecard_token_count), the per-attempt wall-clock timeout (collection.scorecard_timeout_minutes, default 15), and the scorecard_mode marker on every stored row.

  • The tokens are borrowed from the pool for each repo, not once for the whole pass (v0.29.10).

  • No analysis clone exists in this pass, so there is no local backstop: a failed remote attempt is logged, counted, and skipped — the repo’s next collection cycle retries. If no GitHub token can be lent (a misconfigured empty key), the repo is skipped at once and counted as failed rather than run without a token (v0.29.10): scorecard’s remote mode without a token waits up to an hour for GitHub’s rate limit, longer than the default 15-minute timeout.

  • Progress prints every 100 repos: done/total, cumulative api_calls_used, elapsed, and ETA. api_calls_used is a sum of one-token samples (instrument_token_sample): each run’s /rate_limit used-delta on the first lent token. It misses calls scorecard makes on the other lent tokens and counts other callers’ calls on the same token, so read it as an order of magnitude, not an exact count. Runs whose sample is unknown (a probe failed, or the rate-limit window reset mid-run) are excluded and counted as unknown. Interrupting with Ctrl-C is safe; the oldest-first ordering makes a re-run resume where it left off.

  • Requires GitHub API keys (aveloxis add-key). Does not run schema migrations (v0.21.5 contract) — run aveloxis migrate first when upgrading to v0.27.5 so the scorecard_mode column exists.

aveloxis staging-stats

Read-only view of the JSONB staging table, per repo and entity type (v0.22.4): row counts, processed/unprocessed split, oldest/newest timestamps, and approximate bytes.

aveloxis staging-stats              # top 10 (repo, entity_type) cohorts
aveloxis staging-stats --top 50
aveloxis staging-stats --repo microsoft/vscode

Use it to confirm the staging_retention_hours cleanup is working and to spot repos whose staged rows aren’t being processed.


aveloxis distribution-stats

Read-only rollup of the distribution-tracking subsystem (v0.24.0): which repos publish to package registries, and which declare packaging manifests without any registry evidence.

aveloxis distribution-stats                    # fleet rollup
aveloxis distribution-stats --orphans          # manifest-without-registry list
aveloxis distribution-stats --repo owner/repo  # per-repo drill-down

Requires collection.distribution_tracking_enabled: true. See the distribution architecture doc.


Mailing-list commands

These commands register and verify the mailing-list ingestion subsystem (off by default; set collection.mailing_list_enabled = true to collect). See Mailing-list ingestion for how the subsystem works.

aveloxis load-foundation-core-repos

Loads one core/primary repository per project across the tracked open-source foundations (Apache TLPs + podlings). Idempotently registers each project’s flagship repo into a foundation group. Renamed from import-foundations (kept as a hidden alias so existing scripts keep working).

aveloxis load-foundation-core-repos
aveloxis load-foundation-core-repos --dry-run

aveloxis load-foundation-orgs

Registers each foundation’s GitHub org(s) as tracked orgs under your user, so the periodic org-refresh ticker continuously discovers new repos. Tracking the apache org pulls all ~3,000 apache/* repos (a large collection-budget commitment — surfaced in the output) so sibling repos like arrow-rs get collected and mailing-list repo signals resolve.

aveloxis load-foundation-orgs --dry-run
aveloxis load-foundation-orgs --yes

Flag

Description

--user-id

owning user (default 1)

--dry-run

print planned registrations without writing

--yes

proceed without the interactive confirmation

aveloxis load-apache-lists

For each Apache PMC, ensures a per-PMC repo_group, links the PMC’s primary repo, and registers the dev@ and users@ lists for collection. Run load-foundation-core-repos first so the primary repos exist; PMCs whose repo isn’t in the catalog are skipped.

aveloxis load-apache-lists --dry-run
aveloxis load-apache-lists

aveloxis register-mailing-list

Registers a single mailing list for collection under any archive system — used for curated, non-catalog lists like the kernel’s lore public-inbox. The list is attached to the repo’s repo_group (named after the repo, so multiple lists for one repo share a group).

aveloxis register-mailing-list \
    --system lore_public_inbox \
    --list linux-pci@vger.kernel.org \
    --repo https://github.com/torvalds/linux

Flag

Description

--system

archive system (apache_ponymail, lore_public_inbox)

--list

list address

--repo

repo URL to attach the list’s discussion to (must already exist in the catalog)

aveloxis backfill-issue-external-keys

Populates issues.external_key from bracketed [KEY-N] title prefixes (Apache Jira → GitHub issue imports). This is what lets issue_event mailing-list mail bridge to the imported issue by its Jira key.

aveloxis backfill-issue-external-keys

aveloxis mailing-list-stats

Read-only coverage rollup: registered lists, email_message counts, mirror rate, signaled-repo resolution, sender-identity resolution, and the per-class distribution. Safe to run alongside an active serve.

aveloxis mailing-list-stats

The same data is available over HTTP at GET /api/v1/mailing-list/stats (see REST API).

aveloxis verify-mailing-list

The Phase 4 branch-coverage harness. Reads the collected data and prints a PASS / EMPTY / DEFER table for every logic branch (each msg_class, both backends, each routing outcome, threading, signaled/sender resolution, external_key backfill), plus the contributor-resolution assessment.

aveloxis verify-mailing-list            # report only (exit 0)
aveloxis verify-mailing-list --strict   # exit non-zero if a required branch is empty

Flag

Description

--strict

exit non-zero when a required (mailing-list-native) branch produced zero rows

--strict gates only the mailing-list-native branches. Cross-subsystem branches (bridged-to-issue/PR, mirror-linked, sender-resolved, external_key) report as DEFER and never gate — they fill in steady-state operation once the linked repos’ GitHub data is collected and the periodic backfills run. See Mailing-list ingestion §12 for the collection-ordering caveat.


aveloxis deploy-checklist

Prints the current release’s manual deploy/heal steps (read-only). Releases that heal data a plain restart does not touch — v0.29.0 is one — register a checklist here.

aveloxis deploy-checklist
aveloxis deploy-checklist --pending         # every step THIS database still needs — the range the start gate enforces — and the migrate to run
aveloxis deploy-checklist --since 0.29.64   # every release's steps after the last acknowledged deploy, oldest first
aveloxis deploy-checklist --since 0.29.64 --inclusive   # the same, 0.29.64's own steps included

--pending reads the database’s last acknowledged deploy and schema stamp and prints the range aveloxis start serve enforces, computed by the same code: after the acknowledged release; from the stamp, inclusive, when nothing was acknowledged or the stamp is behind the acknowledgement; the binary’s own steps when neither can be used. It prints nothing pending only when the schema stamp is readable and current and this release’s steps are acknowledged (or it has none); a missing, unreadable or malformed stamp prints the steps, with a first line saying why, and so does a database with no collected data yet (the start gate does not require the steps on a fresh install). Otherwise its last line is the migrate to run. It does not take --since or --inclusive. Use it for upgrades (Upgrading).

--since is the offline form: it takes a version you name — typically the last acknowledged deploy, the highest version in aveloxis_ops.deploy_ack compared as a version — and prints every later release’s block up to this binary, oldest first, without reading the database. It is exclusive: that release’s steps already ran. When no deploy was ever acknowledged, pass the schema stamp (SELECT schema_version FROM aveloxis_ops.schema_meta, read before the migrate moves it) with --inclusive, which prints the stamp release’s own block as well: its steps were never acknowledged either (PR #218 fix review r1). --inclusive without --since is an error. Consecutive releases with identical steps print as one block labelled with the range it covers. When more than one release has steps, a header comes first: run aveloxis stop all, the migrate and aveloxis start all once, not once per block — the migrate is the strongest any block asks for (a plain aveloxis migrate if any block needs one and no later release in the range lifts it, as 0.29.61 lifts 0.29.60’s; otherwise aveloxis migrate --skip-views) — then every block’s checks, heals and audits, oldest first, then aveloxis ack-deploy, then aveloxis start all, and a step that needs the running release (such as adopt-forge-id or the approvals page) after the start. The start gate’s refusal names the same migrate for the range it prints. A --since value that is not a version (dotted non-negative numbers such as 0.29.64) is an error: the command names the flag and exits non-zero.

aveloxis start serve prints the same accumulated list, with the same header and collapsing, when the last acknowledged deploy is behind the binary: the heals of a skipped release never ran (v0.29.68). Upgrading puts both commands in order.

aveloxis ack-deploy

Records that the current binary version’s deploy/heal steps were run, so aveloxis start serve / aveloxis start all stops prompting for them. Run it AFTER completing the steps deploy-checklist --pending prints. It cannot stand in for step 2: while the schema stamp is behind the binary the start gate refuses regardless of the acknowledgement (v0.29.4) — only a completed migration of that binary (the checklist’s migrate step — aveloxis migrate --skip-views on the standard ladder, a plain aveloxis migrate for v0.29.57) moves the stamp.

aveloxis ack-deploy [--note "..."]

The start gate: on an EXISTING fleet, aveloxis start serve and aveloxis start all refuse to start (or, in an interactive terminal, prompt) when the current release has un-acknowledged deploy steps — the release’s data-side healing must not be silently skipped. Since v0.29.4 the gate also reads the schema stamp, on EVERY release and BEFORE the ledger: a stamp behind the binary proves no migration of that binary has completed here (the ladder’s step 2), so it refuses without a prompt and records nothing — an acknowledgement cannot clear it, only a completed migration of that binary (the checklist’s migrate step, which the refusal names — aveloxis migrate --skip-views on the standard ladder) can. Fresh installs, releases without deploy steps on a current stamp, and acknowledged releases whose stamp is current, start silently. When another aveloxis-serve is already connected to the database the gate prints a note (never a refusal) with the other serve’s client addresses, each carrying the code’s own verdict: an “(other address)” entry is normally the primary, so this host is running the wrong command (aveloxis start scancode-worker is the alternative); a “(this host)” entry is a serve on THIS host, either one still running or a backend of one just stopped here still draining (only aveloxis start refuses to double-start a component). The two labels report the host verdict, not a comparison of the address text alone: since v0.29.4 the verdict also consults the @host marker, so behind a pooler a backend can be tagged “(other address)” beside an address string identical to your own — the marker is what separated them. When NO “(other address)” entry appears — every listed entry is “(this host)”, or an address this database role cannot see — the note withdraws the wrong-command reading, because nothing in the listing identifies the primary: on a primary restarting beside its own draining backends that verdict would be exactly backwards. Automation can bypass with --skip-deploy-check, which prints the evidence, then the release’s deploy steps, and proceeds (serve’s own startup migration still refuses beside another serve).


aveloxis register-jira-projects

Seeds aveloxis_ops.jira_project_serve from the synthetic Jira issues the mailing-list projection already minted — one registration per distinct project key (191 on the production aveloxis DB), each mapped to its repo. Idempotent; re-running never clobbers an operator-fixed repo mapping. Makes no network calls: dead upstream keys are disabled by the collector on their first 400.

aveloxis register-jira-projects --dry-run
aveloxis register-jira-projects

Flag

Default

Description

--dry-run

off

List derivable projects without registering.

--base-url

https://issues.apache.org/jira

Jira Server base URL for the registrations.


aveloxis backfill-jira-identities

The one-shot identity + state pass over every registered Jira project: bulk-searches each registration’s Jira Server (identity and state fields; comments are the collector’s job) and writes reporter and assignee identity + authoritative issue state onto the synthetic issues through the same provider-precedence writers the collector uses. The registrations in jira_project_serve are the ONLY project source — their repo mapping and base_url are operator-correctable, and the command refuses to run before aveloxis register-jira-projects has created them. Assignee identities are banked into jira_identities alongside reporters (the username is the perishable half). A resolve/mint failure skips that issue and the command exits nonzero — rerun to retry; all writes are idempotent. The full-history pass uses the same drift-safe walk as the incremental worker (window cursor + fixed ceiling — never bare offsets over a mutable ordering), and carries NO resume marker: --limit is a canary only, and a rerun restarts from the beginning (harmless — every write is idempotent). Measured cost: ~2–3 polite hours for the full 844,401-issue ASF corpus at 1,000 issues per search call.

Run it soon regardless of whether the collector is enabled: the stable Server-era username this matches on (49.2% of issues unambiguous by login, +17.6% by display name) does not exist in Jira Cloud’s API — if the ASF instance migrates to Atlassian Cloud, the whole corpus’s usernames become unfetchable at once.

Identity policy (SR-6): unambiguous matches link to existing contributors; pure Jira-only identities (zero candidates) mint a contributor so networks include the person; ambiguous identities stay recorded in jira_identities with a NULL link.

aveloxis register-jira-projects            # once, first
aveloxis backfill-jira-identities --dry-run
aveloxis backfill-jira-identities --project KAFKA --limit 2000   # canary
aveloxis backfill-jira-identities          # the full pass

Flag

Default

Description

--dry-run

off

Report per-project issue totals without writing.

--project

""

Restrict to one project key.

--limit

0

Stop after N issues (canary).


aveloxis strip-quoted-history

Fills messages.msg_text_clean — the quote-stripped body — on mailing-list messages ingested before the ingest-time stripper existed. Measured corpus facts that motivate it: 82.5% of Apache list mail embeds the thread it replies to (64% of body characters are quotation), so raw email bodies average ~4,800 characters against GitHub comments’ ~274; stripping brings the email median to ~300. Consumers read COALESCE(msg_text_clean, msg_text) — until this walker completes, unstripped historical rows serve their raw text.

Resumable: msg_text_clean IS NULL is the resume state, so rerunning after an interruption skips completed rows automatically. After a pattern-library version bump, run with --rule-rerun to re-strip rows stamped under the older rule.

aveloxis strip-quoted-history --limit 50000   # canary
aveloxis strip-quoted-history                 # full history (~30-45 min on a 12.6M-body fleet)

Flag

Default

Description

--limit

0

Stop after N rows (canary); 0 = run to completion.

--rule-rerun

off

Re-strip rows stamped under an older rule version.

--batch

5000

Rows per read+stamp batch.


aveloxis resolve-email-identities

Resolves mailing-list sender identities in one fast pass: every retained sender_email on an unattributed mailing-list message body is re-joined against the current contributors / contributors_aliases tables (email, canonical, and alias chains — the same chain commit resolution uses), walking msg_id keyset windows from floor to ceiling. A full pass over a 12.6M-body fleet measures ~5–10 minutes.

The serve-side backfill ticker (collection.mailing_list_sender_backfill_interval_minutes, default hourly) does the same walk continuously; this command is for “converge NOW” — after aveloxis migrate, after backfill-identities, or on a freshly healed fleet. Safe beside a running serve (row-scoped UPDATEs on cntrb_id IS NULL rows, with deadlock retry). Rerun until it reports 0: unresolved rows stay pending, and an interrupted run resumes with --after-msg-id from the printed cursor.

aveloxis resolve-email-identities --dry-run   # count resolvable rows
aveloxis resolve-email-identities             # the full pass

Flag

Default

Description

--dry-run

off

Count currently-resolvable rows and exit without writing.

--after-msg-id

0

Resume cursor from an interrupted run.

--window

500000

msg_id keyset-window width per statement.


aveloxis backfill-mailing-list-projection

Projects already-ingested mailing-list issue_event mail onto issues (mailing-list Phase 5). In-place and idempotent; batched.

aveloxis backfill-mailing-list-projection --batch 500

Flag

Default

Description

--batch

500

Rows per batch.


aveloxis load-numfocus-projects

Loads the embedded NumFocus project catalog (v0.25.4): creates the “NumFocus Sponsored” / “NumFocus Affiliated” user groups and adds each project’s flagship repository.

aveloxis load-numfocus-projects --dry-run
aveloxis load-numfocus-projects
aveloxis load-numfocus-projects --detect-new   # report catalog drift vs numfocus.org

Flag

Default

Description

--user-id

1

Owner of the created groups.

--dry-run

false

Preview without writing.

--detect-new

false

Crawl numfocus.org and report projects missing from the embedded catalog.

--catalog-file

(embedded)

Override the embedded YAML catalog.


aveloxis load-numfocus-orgs

Companion to load-numfocus-projects: registers each NumFocus project’s GitHub organization for ongoing repo-discovery refresh (“NumFocus Sponsored Orgs” / “NumFocus Affiliated Orgs” groups). Same flag surface as load-numfocus-projects.

aveloxis load-numfocus-orgs --dry-run
aveloxis load-numfocus-orgs

aveloxis version

Prints the Aveloxis version.

aveloxis version

Global behavior

Config file

Every command reads the file named by -c/--config, default aveloxis.json in the current working directory. A missing file is not an error: the command logs config file not found, using defaults at WARN and runs on compiled defaults (the database augur as user augur on localhost:5432, every other key at its default) — so a migrate or add-key run from the wrong directory acts on that default database. Pass -c /path/to/aveloxis.json on every command in scripts, units and runbooks; an invalid file (bad JSON, a value refused at load) is fatal. version and install-tools read no configuration.

Exit codes

Code

Meaning

0

Success

1

General error (invalid arguments, config not found, database connection failed)

Signal handling

aveloxis serve handles the following signals:

  • SIGTERM / SIGINT (Ctrl-C) – graceful shutdown. In-flight API calls and statements are cancelled at once; workers get up to collection.shutdown_grace_seconds to unwind (a completion reached at the cancel is stamped on a retry bounded by half the grace, at most 5 s), then locks are released; staging data is preserved.

  • SIGTERM sent by aveloxis stop – same graceful shutdown.