Packaging & Distribution
12. Packaging & Distribution¶
UAAPS Registry¶
UAAPS supports two registry types that share the same package format (.aam archives) but differ in transport:
| Type | When to use |
|---|---|
| Local filesystem | Offline use, air-gapped environments, CI mirrors, monorepos |
| HTTP remote | Public registry, private org registry, hosted SaaS |
The aam CLI auto-detects the registry type from the URL scheme:
# HTTP remote registry
aam install @myorg/code-review --registry https://aamregistry.io
# Local filesystem registry
aam install @myorg/code-review --registry file:///opt/aam-registry
# Default registry (HTTP, configured in ~/.aam/config.yaml)
aam install @myorg/code-review
Registry configuration is managed in ~/.aam/config.yaml or .aam/config.yaml:
registries:
default: https://aamregistry.io # planned public registry — see §12.6
sources:
- name: myorg-remote
url: https://pkg.myorg.internal/aam
auth: token # see §12.6 for auth types
- name: myorg-local
url: file:///opt/aam-registry
Other distribution channels (Git marketplace, npm bridge, direct install, project-bundled) are supported by the
aamCLI — seeaam_cli_new.md.
12.1 Archive Distribution Format¶
Packages are distributed as .aam archives (gzipped tar), providing a binary-safe, single-file distribution unit.
Archive constraints:
- Maximum size: 50 MB
- MUST contain package.agent.json at root
- No symlinks outside the package directory
- No absolute paths
- SHA-256 checksum stored in package.agent.lock post-install
12.2 Scoped Package Names¶
Following npm convention, packages support a @scope/name format for namespacing under an organization or author:
| Format | Example | Use Case |
|---|---|---|
| Unscoped | code-review |
Community / public packages |
| Scoped | @myorg/code-review |
Organization packages |
Name rules:
- Unscoped: [a-z0-9-], max 64 chars
- Scope: @[a-z0-9][a-z0-9_-]{0,63}
- Full scoped name: max 130 chars
Filesystem mapping — the @scope/name format is converted using double-hyphen:
| Package Name | Filesystem Name |
|---|---|
code-review |
code-review |
@myorg/code-review |
myorg--code-review |
The -- separator is reversible and unambiguous because valid name segments cannot start with hyphens.
12.3 Dist-Tags¶
Dist-tags are named aliases for package versions, enabling installs like aam install @org/agent@stable or enterprise gates like bank-approved.
Default Tags¶
| Tag | Behavior |
|---|---|
latest |
Automatically set to the newest published version |
stable |
Opt-in; set manually via aam dist-tag add |
Custom Tags¶
Organizations can define arbitrary tags (e.g., staging, bank-approved, qa-passed).
Tag rules: - Lowercase alphanumeric + hyphens only - Max 32 characters - Cannot be a valid SemVer string (prevents ambiguity)
Manifest Declaration (optional)¶
// package.agent.json
{
"name": "@myorg/agent",
"version": "1.2.0",
"dist-tags": {
"stable": "1.1.0", // Maintained by publisher
"latest": "1.2.0"
}
}
12.4 Portable Bundles¶
A portable bundle is a self-contained, pre-compiled archive for a specific target platform. It contains artifacts already transformed to the platform's native format — no install-time resolution needed.
Use case: Distributing packages via Slack, email, or air-gapped environments without a registry.
aam pkg build --target cursor
# → dist/my-package-1.0.0-cursor.bundle.aam
aam pkg build --target copilot
# → dist/my-package-1.0.0-copilot.bundle.aam
aam pkg build --target all
# → one bundle per configured platform
Bundle structure (tar.gz internally):
my-package-1.0.0-cursor.bundle.aam
├── bundle.json # Bundle manifest
├── .cursor/
│ ├── skills/... # Pre-compiled platform artifacts
│ ├── rules/...
│ └── commands/...
└── package.agent.json # Original manifest for reference
bundle.json schema:
{
"format": "uaaps-bundle",
"version": "1.0",
"package": "@author/my-agent",
"package_version": "1.2.0",
"target": "cursor",
"built_at": "2026-02-19T14:30:00Z",
"checksum": "sha256:...",
"artifacts": [
{ "type": "skill", "name": "my-skill", "path": ".cursor/skills/author--my-skill/" },
{ "type": "agent", "name": "my-agent", "path": ".cursor/rules/agent-author--my-agent.mdc" }
]
}
Installing from a bundle:
aam install ./dist/my-package-1.0.0-cursor.bundle.aam
# Deploys immediately — no dependency resolution needed
12.5 Local Filesystem Registry¶
A local filesystem registry is a directory tree on disk following a defined layout. No server process is required. It is suitable for offline installs, CI artifact mirrors, and air-gapped enterprise environments.
Directory Layout¶
<registry-root>/
├── index.json # Full package index (all packages + versions)
├── packages/
│ ├── code-review/ # Unscoped package
│ │ ├── meta.json # Package metadata (all versions)
│ │ └── versions/
│ │ ├── 1.0.0.aam
│ │ ├── 1.0.0.aam.sha256 # Detached SHA-256 checksum
│ │ ├── 1.1.0.aam
│ │ └── 1.1.0.aam.sha256
│ └── myorg--code-review/ # Scoped package (@myorg/code-review)
│ ├── meta.json
│ └── versions/
│ ├── 2.0.0.aam
│ └── 2.0.0.aam.sha256
└── dist-tags.json # Global dist-tag → version map
index.json¶
A flat list of all packages in the registry. Implementations MUST regenerate this file after every publish or unpublish operation.
{
"formatVersion": 1,
"updatedAt": "2026-02-22T14:00:00Z",
"packages": [
{ "name": "code-review", "latest": "1.1.0", "versions": ["1.0.0", "1.1.0"] },
{ "name": "@myorg/code-review", "latest": "2.0.0", "versions": ["2.0.0"] }
]
}
packages/<name>/meta.json¶
Per-package metadata covering all published versions.
{
"name": "@myorg/code-review",
"versions": {
"2.0.0": {
"version": "2.0.0",
"description": "Code review skills for myorg",
"author": "myorg",
"publishedAt": "2026-02-20T10:00:00Z",
"integrity": "sha256-abc123...",
"tarball": "versions/2.0.0.aam"
}
},
"dist-tags": {
"latest": "2.0.0",
"stable": "2.0.0"
}
}
dist-tags.json¶
Registry-wide dist-tag snapshot for fast tag resolution without reading every meta.json.
{
"code-review": { "latest": "1.1.0", "stable": "1.0.0" },
"@myorg/code-review": { "latest": "2.0.0" }
}
Filesystem Registry Rules¶
| Rule | Requirement |
|---|---|
Package directory name MUST use the scope--name mapping (§12.2) |
MUST |
Every .aam file MUST have a sibling .aam.sha256 file |
MUST |
index.json MUST be regenerated atomically after each mutation |
MUST |
| Symlinks outside the registry root are forbidden | MUST NOT |
| The registry root MAY be read-only (install-only mirror) | MAY |
CLI Commands for Local Registries¶
# Initialise a new local registry
aam registry init file:///opt/aam-registry
# Publish a package to a local registry
aam pkg publish --registry file:///opt/aam-registry
# Rebuild index.json after manual archive placement
aam registry reindex file:///opt/aam-registry
# List all packages in a local registry
aam registry ls file:///opt/aam-registry
12.6 HTTP Registry Protocol¶
Status: Draft — Endpoint signatures and auth model are defined below. Full request/response schemas, pagination rules, rate-limit headers, and error codes will be detailed in a dedicated Registry Protocol document in a future spec revision. The endpoint list is preliminary and subject to change.
An HTTP registry is an HTTPS server implementing the UAAPS Registry Protocol. The planned official public registry is https://aamregistry.io (under development). Organizations MAY self-host a private registry.
Base URL¶
All endpoints are relative to the registry base URL. Implementations MUST serve the API over HTTPS. Plain HTTP MUST NOT be used for registries handling private packages or authentication tokens.
Endpoint Index¶
| Method | Path | Purpose | Auth required |
|---|---|---|---|
GET |
/ |
Registry metadata & capabilities | No |
GET |
/packages |
List all packages (paginated) | No (public) / Yes (private) |
GET |
/packages/:name |
Package metadata (all versions) | No (public) / Yes (private) |
GET |
/packages/:name/:version |
Single version metadata | No (public) / Yes (private) |
GET |
/packages/:name/:version/tarball |
Download .aam archive |
No (public) / Yes (private) |
GET |
/packages/:name/:version/signature |
Fetch signature bundle | No |
GET |
/packages/:name/dist-tags |
List dist-tags for package | No |
PUT |
/packages/:name/dist-tags/:tag |
Set a dist-tag | Yes |
DELETE |
/packages/:name/dist-tags/:tag |
Remove a dist-tag | Yes |
POST |
/packages |
Publish a new package version | Yes |
DELETE |
/packages/:name/:version |
Unpublish a version | Yes |
GET |
/approvals |
List pending approval requests | Yes |
POST |
/approvals/:id/approve |
Approve a publish request | Yes |
POST |
/approvals/:id/reject |
Reject a publish request | Yes |
Authentication¶
HTTP registries MAY require authentication. The aam CLI supports the following auth types, configured per registry in ~/.aam/config.yaml:
| Auth type | Config value | Transport |
|---|---|---|
| No auth (public) | auth: none |
— |
| Static token | auth: token |
Authorization: Bearer <token> header |
| OIDC / Sigstore keyless | auth: oidc |
Short-lived token via OIDC provider |
| Basic (legacy, not recommended) | auth: basic |
Authorization: Basic <base64> header |
# ~/.aam/config.yaml
registries:
sources:
- name: myorg
url: https://pkg.myorg.internal/aam
auth: token
token: "${MYORG_AAM_TOKEN}" # resolved from environment variable
Tokens MUST be stored in environment variables or a secrets manager. Tokens MUST NOT be committed to version control. The aam login command handles interactive token acquisition and secure local storage.
aam login --registry https://pkg.myorg.internal/aam # interactive login
aam logout --registry https://pkg.myorg.internal/aam # remove stored credential
aam whoami --registry https://pkg.myorg.internal/aam # show current identity
Error Response Format¶
All error responses MUST use application/json with this structure:
{
"error": {
"code": "PACKAGE_NOT_FOUND",
"message": "Package @myorg/code-review@3.0.0 does not exist.",
"docs": "https://aamregistry.io/errors/PACKAGE_NOT_FOUND"
}
}
Standard Status Codes¶
| Code | Meaning |
|---|---|
200 |
Success |
201 |
Published successfully |
400 |
Malformed request |
401 |
Authentication required |
403 |
Insufficient permissions |
404 |
Package or version not found |
409 |
Version already exists (publish conflict) |
422 |
Validation failed (manifest schema error) |
429 |
Rate limit exceeded |
503 |
Registry temporarily unavailable |
Full pagination headers, rate-limit headers, conditional request support (
ETag,If-None-Match), and approval workflow request/response bodies will be defined in the Registry Protocol document.
Structured Error Code Constants¶
Registries MUST use the following machine-readable error codes in the error.code field of error responses. Clients SHOULD use these codes for programmatic error handling rather than relying on HTTP status codes alone.
| Code | HTTP Status | Description |
|---|---|---|
PACKAGE_NOT_FOUND |
404 |
Package name does not exist |
VERSION_NOT_FOUND |
404 |
Specific version does not exist |
VERSION_CONFLICT |
409 |
Version already published |
MANIFEST_INVALID |
422 |
Manifest failed schema validation |
SIGNATURE_INVALID |
403 |
Package signature verification failed |
TAG_NOT_FOUND |
404 |
Dist-tag does not exist |
UNAUTHORIZED |
401 |
Authentication required |
FORBIDDEN |
403 |
Insufficient permissions |
RATE_LIMITED |
429 |
Rate limit exceeded |
REGISTRY_UNAVAILABLE |
503 |
Registry temporarily unavailable |
PACKAGE_YANKED |
410 |
Version is yanked (not available for new resolution) |
PACKAGE_DEPRECATED |
200 |
Version is deprecated (returned alongside valid response) |
Content Negotiation¶
Clients SHOULD send an Accept header specifying the versioned media type:
Registries MUST also accept application/json as a fallback for clients that do not specify the versioned type. The response Content-Type MUST match the negotiated media type. If the client requests a media type the registry does not support, the registry MUST respond with 406 Not Acceptable.
Pagination¶
List endpoints (e.g., GET /packages, GET /approvals) MUST support cursor-based pagination.
Query parameters:
| Parameter | Default | Max | Description |
|---|---|---|---|
cursor |
(none) | — | Opaque token returned by the previous response |
limit |
50 |
200 |
Maximum number of items per page |
Response headers:
| Header | Description |
|---|---|
Link: <url>; rel="next" |
URL for the next page of results |
X-Total-Count |
Total number of items matching the query |
When no more results exist, the registry MUST omit the Link header with rel="next". Clients MUST NOT assume a stable ordering across pages unless a sort parameter is provided.
Additional Endpoints¶
The following endpoints extend the base endpoint index (§12.6) with search, deprecation, and yank operations.
| Method | Path | Purpose | Auth required |
|---|---|---|---|
GET |
/packages?q=<query>&category=<cat>&sort=<field> |
Search packages | No (public) / Yes (private) |
POST |
/packages/:name/:version/deprecate |
Deprecate a version | Yes |
DELETE |
/packages/:name/:version/deprecate |
Remove deprecation | Yes |
POST |
/packages/:name/:version/yank |
Yank a version | Yes |
DELETE |
/packages/:name/:version/yank |
Undo yank | Yes |
The POST /packages/:name/:version/deprecate endpoint accepts a JSON body:
The replacement field is OPTIONAL and, when provided, MUST be a valid package specifier. The message field is REQUIRED and MUST NOT exceed 280 characters.
12.7 Package Lifecycle¶
Published packages transition through a defined set of states that control visibility and resolution behavior. UAAPS follows an immutability-first model: once an archive is published, it is permanent.
State Machine¶
deprecate yank
┌───────────┐ ──────────► ┌──────────────┐
│ Published │ │ Deprecated │
└─────┬─────┘ ◄────────── └──────────────┘
│ undeprecate
│
│ yank undo yank
├──────────────────► ┌──────────────┐
│ │ Yanked │
│ ◄──────────────── └──────────────┘
- Published → Deprecated: The
deprecatecommand marks a version as deprecated. Deprecated versions remain fully functional but emit warnings during resolution. - Published → Yanked: The
yankcommand soft-deletes a version. Yanked versions are hidden from search and new resolution but remain downloadable for existing lock files. - There is no hard unpublish. Once published, the archive is permanent.
Behavior by State¶
| State | Appears in search | Resolved by aam install? |
Resolved by --frozen? |
Downloadable? |
|---|---|---|---|---|
| Published | Yes | Yes | Yes | Yes |
| Deprecated | Yes (with warning) | Yes (with warning + replacement message) | Yes | Yes |
| Yanked | No | No | Yes (lock file integrity preserved) | Yes (if in lock file) |
CLI Commands¶
# Deprecate a version with a message
aam deprecate @myorg/agent@1.0.0 "Use @myorg/agent@2.0.0 instead"
# Yank a version (soft-delete)
aam yank @myorg/agent@1.0.0
# Reverse a yank
aam yank --undo @myorg/agent@1.0.0
Manifest Metadata for Deprecation¶
Publishers MAY declare deprecation metadata directly in package.agent.json. When present, the registry SHOULD surface this information in search results and install warnings.
// package.agent.json
{
"deprecated": {
"message": "Use @myorg/agent@2.0.0 instead",
"replacement": "@myorg/agent@^2.0.0"
}
}
The message field is REQUIRED when deprecated is present. The replacement field is OPTIONAL and MUST be a valid package specifier when provided.
Immutability Guarantee¶
Unlike npm's
unpublish, UAAPS follows Cargo's immutability model: published archives are permanent. This guarantees that existing lock files always resolve successfully, preventing supply-chain breakage.