Skip to content

Version storage

A Matrix package version is identified by (packageName, version). The artifact registry stores tarballs keyed by that pair, and the package catalog indexes metadata by it.

Two parallel stores

There are two things stored per version:

WhatWhereFormat
Tarball bytesGitea blob storagepackage-version.tgz
Discovery metadatadiscovery-index.json next to the tarballsJSON entry

These are written together by the publish pipeline but read independently:

  • npm install reads only tarballs.
  • system.packages.registry.search reads only discovery-index.json.

Tarball storage

Gitea owns the blob layer. Tarball lookup is HTTP:

GET /api/packages/<owner>/npm/<pkg>/-/<pkg>-<version>.tgz

Internally Gitea stores the tarball content-addressed under its <gitea-data>/packages/ directory. That layout is Gitea's concern, not Matrix's; Matrix only assumes the public URL surface.

Discovery-index.json shape

The companion metadata index lives at the registry root (next to the tarball storage). Its shape is:

json
{
  "packages": {
    "@open-matrix/chat": {
      "versions": {
        "0.2.3": {
          "indexedAt": "2026-04-30T12:00:00Z",
          "indexedBy": "publish-pipeline",
          "metadata": {
            "packageName": "@open-matrix/chat",
            "version": "0.2.3",
            "displayName": "Chat",
            "description": "...",
            "inferredKind": "webapp",
            "tags": [...],
            "capabilities": [...],
            "webapp": { "appName": "chat", "shells": ["platform"], ... },
            "components": [
              {
                "type": "ChatActor",
                "mount": "chat",
                "accepts": ["chat.send", "chat.thread.list"],
                "emits": ["chat.message"],
                ...
              }
            ]
          }
        },
        "0.2.4": { ... }
      }
    },
    "@open-matrix/director": { ... }
  }
}

The exact field shape is the IPackageDiscoveryIndex interface in projects/matrix-3/packages/mx-cli/src/utils/discovery-index.ts. The reader in SystemPackageRegistryActor.readLatestMetadata extracts the latest-version entry per package by sorting version keys natural-numerically:

typescript
// SystemPackageRegistryActor.ts:218-228 (paraphrased)
const latest = Object.entries(versionsRecord)
  .sort(([left], [right]) => left.localeCompare(right, undefined, { numeric: true }))
  .at(-1)?.[1];

Version sort semantics

localeCompare(..., { numeric: true }) is not semver-aware. It does right-thing comparisons on 0.2.3 vs 0.2.10 (treating numbers numerically), but it does not know that 0.2.3-rc.1 < 0.2.3. For pre-release ordering, use proper semver tooling at publish time (the publish pipeline uses semver.compare).

Note: The catalog's "latest" view is a coarse approximation. For pre-release-aware version selection, consumers should run install resolution with semver, not rely on the catalog's "latest" projection.

Storage durability

ConcernBackendNotes
Tarball bytesGitea volumeSurvives container restarts; lost if volume is destroyed. Backups are operational concern.
Discovery indexfilesystemSame volume as tarballs. Re-buildable by re-reading every tarball's matrix.json if lost.

There is no replication today. The registry is single-instance per deployment. A future revision could add S3/blob-store backing for tarballs; the catalog index can be regenerated from tarball contents.

Yanking / deletion

Gitea allows package deletion via its admin UI / API. Matrix's own tooling does not currently include a mx unpublish command. Operators can:

  1. Delete the version from Gitea.
  2. Re-run the publish pipeline's index step against the post-deletion directory to update discovery-index.json.

Status: No automated yank flow exists. This is an open operator action.

See also

Source: projects/matrix-3/packages/system-catalog/src/SystemPackageRegistryActor.ts:211-230, projects/matrix-3/packages/mx-cli/src/utils/discovery-index.ts.