# CI Artifact Catalog Contract

This document is the machine-readable contract for AI coding assistants and CI pipelines that publish build artifacts to the Niusys artifact catalog. It describes metadata registration and release publication only. Upload files to the configured object store before registering their versioned URLs here.

## Base URL and Safety

```text
https://api.niusys.com
```

All `/api/ci/**` endpoints below are public and writable. They are best-effort release metadata, not authorization, audit, provenance, or credential storage. Never submit secrets, private keys, passwords, API tokens, or unredacted credentials. A failed catalog request must emit a `WARNING` to stderr and must not fail the CI build.

Responses may be returned directly or inside a shared API envelope. When reading an identifier, accept either `.buildId` or `.data.buildId`, and either `.releaseId` or `.data.releaseId`.

## CI Lifecycle

1. Create or retrieve an idempotent build with `POST /api/ci/builds`; save `buildId`.
2. Upload versioned files to object storage, then register each file with `PUT /api/ci/builds/{buildId}/artifacts`.
3. Complete the build with `POST /api/ci/builds/{buildId}/complete`.
4. For successful builds, create a draft release with `POST /api/ci/releases`; save `releaseId`.
5. Publish the release with `POST /api/ci/releases/{releaseId}/publish` after all required package components are registered.

The initial build request is idempotent for the same normalized `applicationSlug`, `ciProvider`, and `ciBuildNumber`. Published builds cannot be changed. Re-publishing a previously published release refreshes its `publishedAt` order and can be used for channel rollback.

## Write API

Every request body is JSON and uses `Content-Type: application/json`.

### Create a build

`POST https://api.niusys.com/api/ci/builds`

```json
{
  "applicationSlug": "tmaker",
  "applicationDisplayName": "TMaker",
  "productLineSlug": "aicms",
  "productLineDisplayName": "AI CMS",
  "ciProvider": "drone",
  "ciBuildNumber": "1842",
  "version": "1.0.0.1842",
  "repositoryUrl": "https://git.example.com/org/tmaker",
  "sourceRevision": "0123456789abcdef",
  "startedAt": "2026-09-11T08:00:00Z"
}
```

Required fields are `applicationSlug`, `ciProvider`, `ciBuildNumber`, and `version`. `applicationDisplayName`, product-line fields, repository URL, source revision, and start time are optional. A new application with omitted product-line fields is categorized as `uncategorized / 未分类`; an existing application's explicit product line is preserved when later requests omit the fields. Save `buildId` from the response.

### Register an artifact

`PUT https://api.niusys.com/api/ci/builds/{buildId}/artifacts`

```json
{
  "artifactKey": "windows-x64-zip",
  "component": "tmaker",
  "artifactType": "package",
  "operatingSystem": "windows",
  "architecture": "x64",
  "packageFormat": "zip",
  "displayName": "TMaker for Windows x64",
  "url": "https://downloads.example.com/tmaker/1.0.0.1842/tmaker-windows-x64.zip"
}
```

`artifactKey`, `component`, `artifactType`, and `url` are required. `artifactType` must be `package` or `install_script`. An artifact key is unique within a build and is replaced by a later request with the same key while the build is unpublished. URLs must point to versioned objects; do not use `_latest` links.

### Complete a build

`POST https://api.niusys.com/api/ci/builds/{buildId}/complete`

```json
{
  "succeeded": true,
  "finishedAt": "2026-09-11T08:10:00Z"
}
```

For a failed build, set `succeeded` to `false` and provide a non-empty `failureSummary`. Failed builds cannot create releases.

### Create a release

`POST https://api.niusys.com/api/ci/releases`

```json
{
  "buildId": 12345,
  "channel": "stable",
  "releaseNotesUrl": "https://docs.example.com/tmaker/releases/1.0.0.1842"
}
```

`buildId` and `channel` are required. Save `releaseId` from the response. Repeating the request for the same build and channel returns the existing release.

### Publish a release

`POST https://api.niusys.com/api/ci/releases/{releaseId}/publish`

```json
{
  "requiredComponents": ["tmaker"]
}
```

`requiredComponents` is optional. When supplied, every listed component must have a registered `package` artifact or publication is rejected.

## Non-blocking Shell Pattern

Use a guarded wrapper for each write. Stop later catalog operations after the first failure, but allow the rest of the CI pipeline to continue.

```bash
catalog_active=1
catalog_warn() {
  echo "WARNING: artifact catalog submission failed: $1; continuing CI." >&2
  catalog_active=0
}

catalog_submit() {
  label=$1
  shift
  if "$@"; then
    return 0
  fi
  status=$?
  catalog_warn "${label} (exit=${status})"
  return 0
}
```

Do not use a bare failing `curl`, `exit 1`, retries after a failed catalog write, or `|| true`. Read `buildId` and `releaseId` only after their corresponding request succeeds.

## Public Read API and Workbench

Only published releases are visible through the read API:

- `GET https://api.niusys.com/api/apps`
- `GET https://api.niusys.com/api/apps/{applicationSlug}/channels`
- `GET https://api.niusys.com/api/apps/{applicationSlug}/releases/latest?channel=stable`
- `GET https://api.niusys.com/api/apps/{applicationSlug}/releases/{version}?channel=stable`
- `GET https://api.niusys.com/api/apps/{applicationSlug}/releases?channel=stable&page=1&pageSize=20`
- `GET https://api.niusys.com/api/releases?page=1&pageSize=50`
- `GET https://api.niusys.com/api/catalog`
- `GET https://api.niusys.com/api/apps/{applicationSlug}/components/{component}/releases?operatingSystem=windows&page=1&pageSize=20`

Use `unlabeled=true` instead of `operatingSystem` to request artifacts without an operating-system label. The catalog workbench is `https://api.niusys.com/artifact-catalog.html`; it only shows published releases and never calls object storage.

Use the optional `productLineSlug` query parameter to filter applications or cross-application releases, for example `GET /api/apps?productLineSlug=uncategorized` and `GET /api/releases?productLineSlug=uncategorized&page=1&pageSize=50`. Omitting the parameter keeps the existing unfiltered behavior. New applications without product-line metadata use `uncategorized / 未分类`; an existing application's explicit product line is preserved when later CI requests omit the field.

For Drone build context and variables, use the separate metadata contract at `https://api.niusys.com/api/ci/help`.
