RFD0022 - Riot Package Registry
- Feature Name:
riot_package_registry - Start Date:
2026-03-27 - Status:
implemented
Summary
Section titled “Summary”Riot’s package registry lives in services/api.pkgs.ml and is deployed at
https://api.pkgs.ml.
It is the single control-plane service for:
- explicit authenticated package publication
- package-name claims
- immutable published release records
- synchronous sparse-index updates
- synchronous search updates
- derived web-view generation for
pkgs.ml
The final model is intentionally explicit:
riot publishis the only way to claim a public package nameriot add <package-name>installs from the sparse indexriot addnever implicitly publishes or claims names
Motivation
Section titled “Motivation”The design pressure was to make named-package installs feel fast like Bun or Cargo while keeping publication explicit and deterministic.
The registry therefore focuses on:
- authenticated artifact publication
- stable named install targets
- immutable release artifacts
- a sparse install index served from the API layer
The registry deliberately does not own:
- dependency solving
- lockfile generation
- workspace resolution
- compatibility selection
- build execution
Those belong in riot.
Final user model
Section titled “Final user model”Named publishes
Section titled “Named publishes”riot publish
This asks the registry to:
- accept a package-root
tar.gzartifact upload - validate that the package is publishable
- authenticate the publisher
- claim the package name if allowed
- create the immutable published release record
- store the immutable manifest and source artifact in R2
- synchronously update the sparse index
- synchronously update search and derived web views
- emit lifecycle events
After a successful publish, the package is immediately available through:
GET /v1/search- the sparse index under
cdn.pkgs.ml/index/v1/... - artifact downloads under
cdn.pkgs.ml/... pkgs.ml
Named installs
Section titled “Named installs”riot add minttea
This does not go through GitHub. It should eventually use:
GET /v1/searchonly for discovery UX, if needed- the sparse index config at
cdn.pkgs.ml/index/v1/config.json - the package shard document at
cdn.pkgs.ml/index/v1/... - the immutable artifact URL referenced by the chosen release
Authentication model
Section titled “Authentication model”The current implemented auth model is:
- GitHub OAuth for user identity
- session cookies for
pkgs.ml - API tokens for
riot publish - temporary
ROOT_AUTH_TOKENsupport for operator and e2e flows
Users authenticate through:
GET /v1/auth/github/startGET /v1/auth/github/callback
Users can then create publish tokens through:
GET /v1/me/tokensPOST /v1/me/tokensDELETE /v1/me/tokens/<token-id>
Tokens are currently scoped to:
publish
Package-name and release rules
Section titled “Package-name and release rules”The implemented publish rules are:
- package names are globally unique
- a package name may only be claimed by one account owner
- versions must be valid semver
- versions are immutable
The version immutability rule is important:
- same package name + same version + same artifact digest: publish short-circuits as an idempotent success
- same package name + same version + different artifact digest: publish fails with conflict
This means Riot does not support overwriting an already-published version.
Publish validation rules
Section titled “Publish validation rules”The registry currently enforces:
package.public = truepackage.nameexistspackage.versionis semver-validpackage.descriptionexistspackage.licenseexists and is SPDX-compatible- semver dependencies must already exist in the registry
- git dependencies are allowed if the git reference parses
The current exception is compiler-shipped OCaml libraries. These are treated as built-in dependencies and do not require publication:
stdlibunixdynlink
Workspace publish ordering is intentionally not solved in the registry.
That orchestration belongs in riot publish.
API surface
Section titled “API surface”The main registry endpoints are:
GET /POST /v1/publishGET /v1/search?q=<query>GET /v1/events?limit=<count>&after=<event-id>GET /v1/packages/<package-name>/events?version=<version>&limit=<count>GET /v1/views/packages/<package-name>/overviewGET /v1/views/packages/<package-name>/relationsGET /v1/views/recent/packagesGET /v1/views/popular/packagesGET /v1/views/categoriesGET /v1/views/owners/<github-login>/packages
The stable API surface is api.pkgs.ml/v1/... for control-plane routes, while
the sparse index and immutable artifacts are served from cdn.pkgs.ml.
Storage model
Section titled “Storage model”The implementation is database-first for control-plane data and R2-first for heavy immutable artifacts.
Cloudflare D1 stores:
- users
- sessions
- oauth state
- api tokens
- package claims
- published releases
- request-driven registry events
- search rows and FTS tables
- derived web-view documents
Cloudflare R2 stores:
- immutable published source tarballs
- immutable publication manifests
- sparse package-index files
- cached user avatars
- exported D1 backups
Events
Section titled “Events”The registry records lifecycle events in D1 and exposes them through
/v1/events.
The currently emitted package lifecycle events are:
package.submittedpackage.verifiedpackage.publishedpackage.searchablepackage.indexed
These events drive:
- the public activity page
- debugging of publish pipelines
- future downstream systems such as docs, security checks, or analytics
Relationship to riot add and riot publish
Section titled “Relationship to riot add and riot publish”This RFD captures the registry contract that riot should build against.
riot add needs two modes:
- source mode via
resolve - named-package mode via the sparse index
riot publish needs:
- Git-aware package locator detection
- auth token support
- workspace publish ordering
- clear handling of immutable version conflicts
Those client-side concerns are intentionally deferred to a later Riot package management RFD.