# Требования к публикации приложений в Argus

Этот документ фиксирует текущие требования публикации на основе фактического кода `Argus` и Android-клиента `Argus Store` в этом репозитории. Он разделяет:

1. жёсткие требования, которые реально проверяет сервер;
2. дополнительные требования, без которых приложение не будет нормально устанавливаться через `Argus Store`;
3. условия, при которых `Argus Store` сможет предлагать автообновления.

Если цель публикации именно в Android-клиент `Argus Store`, нужно одновременно соблюдать и серверные требования, и Android-специфичные требования из этого документа.

Источники: `scripts/publish-release.ps1:2-139`, `Program.cs:179-239`, `Program.cs:335-661`, `Contracts/ArgusDtos.cs:3-96`, `Infrastructure/SlugUtility.cs:7-39`, `Infrastructure/IdentifierUtility.cs:7-38`, `Infrastructure/SemanticVersionUtility.cs:5-240`, `Infrastructure/PackageStorageService.cs:8-97`, `android/app/src/main/java/xyz/kusoft/argusstore/StoreModels.kt:12-110`, `android/app/src/main/java/xyz/kusoft/argusstore/StoreCatalogLoader.kt:7-34`, `android/app/src/main/java/xyz/kusoft/argusstore/PackageInstallerManager.kt:15-128`, `android/app/src/main/java/xyz/kusoft/argusstore/PackageStateResolver.kt:11-252`, `README.md:13-18`, `README.md:74-113`.

## 1. Что ИИ обязан подготовить перед публикацией

Перед публикацией ИИ должен выдать:

- путь к одному готовому release-файлу;
- `slug`;
- `name`;
- `summary`;
- `description`;
- `version`;
- `channel`;
- `platform`;
- `packageKind`;
- `notes` при наличии;
- `repositoryUrl` при наличии;
- `homepageUrl` при наличии;
- `androidPackageName` для Android-релизов;
- `androidVersionCode` для Android-релизов;
- решение, будет ли приложение публичным (`isListed=true`) или скрытым (`isListed=false`).

Причина: именно эти поля использует текущий publish-скрипт и серверные DTO.  
Источник: `scripts/publish-release.ps1:2-46`, `Contracts/ArgusDtos.cs:62-94`.

## 2. Жёсткие серверные требования

### 2.1. Требования к `slug`

- `slug` обязателен.
- Разрешены только строчные латинские буквы, цифры и дефисы.
- Максимальная длина `slug` — `100` символов.
- Значение должно уже быть нормализовано; сервер не принимает произвольный mixed-case `slug`.

Фактическое правило валидации:

- `SlugUtility.Normalize(...)` переводит строку в lowercase, заменяет любые неалфавитно-цифровые символы на `-`, схлопывает повторные дефисы и обрезает дефисы по краям.
- `SlugUtility.IsValid(...)` принимает только строки, которые уже совпадают со своим нормализованным вариантом и имеют длину до `100`.

Источник: `Infrastructure/SlugUtility.cs:7-39`, `Program.cs:341-345`, `Program.cs:393-397`.

### 2.2. Обязательные поля метаданных приложения

Сервер отклоняет публикацию метаданных, если отсутствует хотя бы одно из полей:

- `name`
- `summary`
- `description`

Пустые строки и строки из одних пробелов считаются отсутствующими.

Источник: `Program.cs:372-379`, `Program.cs:582-607`.

### 2.3. `androidPackageName`

- Для не-Android публикаций поле опционально.
- Для Android-публикаций поле обязательно на уровне карточки приложения до загрузки релиза.
- Формат должен быть похож на Java package name.

Фактические правила проверки:

- минимум 2 сегмента, разделённых точками;
- строка не может начинаться или заканчиваться точкой;
- каждый сегмент должен начинаться с буквы или `_`;
- в сегментах допустимы только буквы, цифры и `_`.

Примеры подходящего формата:

- `com.example.app`
- `xyz.kusoft.aletheia`

Примеры, которые сервер отвергнет:

- `App`
- `.com.example`
- `com.example.`
- `com.example.my-app`

Источник: `Program.cs:423-429`, `Program.cs:601-605`, `Program.cs:639-656`.

### 2.4. Обязательные поля релиза

Сервер отклоняет загрузку релиза, если отсутствует хотя бы одно из полей:

- `version`
- `package` (сам файл)

Пустой файл тоже отклоняется.

Источник: `Program.cs:401-405`, `Program.cs:616-624`.

### 2.5. Когда `androidVersionCode` обязателен

`androidVersionCode` обязателен, если сервер считает релиз Android-артефактом. Сейчас это происходит, когда выполняется хотя бы одно условие:

- `platform` содержит подстроку `android`, или
- `packageKind` равен `apk`, `xapk` или `apks`.

Дополнительные правила:

- если `androidVersionCode` передан, он должен быть положительным целым числом;
- для Android-артефактов `androidVersionCode` не может быть `null`.

Источник: `Program.cs:418-420`, `Program.cs:423-429`, `Program.cs:613-633`, `Program.cs:659-661`.

### 2.6. Ограничение на дубликаты релизов

Сервер не разрешает второй релиз с тем же набором:

- `slug`
- `version`
- `channel`
- `platform`

`packageKind` в этом конфликте не участвует. Это означает, что нельзя загрузить два разных файла с одинаковыми `version + channel + platform` для одного и того же приложения.

Если нужны разные ABI-сборки одной версии, они должны отличаться именно значением `platform`.

Источник: `Program.cs:432-444`.

### 2.7. Публичность приложения

- В publish-скрипте приложение по умолчанию публикуется как публичное (`isListed=true`).
- Если использовать ключ `-Unlisted`, сервер сохранит `isListed=false`.
- Публичные API `/api/apps`, `/api/apps/{slug}` и `/api/apps/{slug}/manifest` возвращают только `IsListed` приложения.

Практический вывод: если приложение должно появиться в `Argus Store`, его нельзя публиковать как `Unlisted`.

Источник: `scripts/publish-release.ps1:30`, `scripts/publish-release.ps1:59-67`, `Program.cs:179-239`, `Program.cs:378`.

## 3. Что нужно именно для Android-клиента Argus Store

Ниже не только серверные требования, но и дополнительные условия текущего Android-клиента. Без них релиз может загрузиться на сервер, но приложение либо не появится в клиенте, либо будет помечено как `Unsupported`, либо не сможет установиться.

Источник: `android/app/src/main/java/xyz/kusoft/argusstore/StoreCatalogLoader.kt:7-34`, `StoreModels.kt:12-110`, `PackageInstallerManager.kt:15-128`, `PackageStateResolver.kt:11-129`.

### 3.1. Для установки через Argus Store нужен именно `apk`

Текущий Android-клиент считает установимыми только релизы, у которых:

- `packageKind == "apk"`

`xapk` и `apks` сейчас сервер распознаёт как Android-артефакты для валидации, но Android-клиент их не выбирает как installable release.

Практическое требование: если приложение должно устанавливаться через `Argus Store`, публикуй обычный `.apk` и ставь `packageKind=apk`.

Источник: `Program.cs:659-661`, `android/app/src/main/java/xyz/kusoft/argusstore/StoreModels.kt:27-29`, `StoreModels.kt:72-82`.

### 3.2. Для Android-клиента `androidPackageName` должен быть заполнен

Если у карточки приложения отсутствует `androidPackageName`, клиент помечает приложение как неподдерживаемое для установки.

Источник: `android/app/src/main/java/xyz/kusoft/argusstore/PackageStateResolver.kt:12-21`.

### 3.3. `androidVersionCode` релиза должен совпадать с APK

Перед отправкой APK в `PackageInstaller` клиент читает скачанный APK и сверяет:

- `archiveInfo.packageName == androidPackageName`
- `archive versionCode == androidVersionCode`

Если хотя бы одна проверка не проходит, установка отклоняется.

Практическое требование: публикуемый `androidPackageName` должен точно совпадать с `applicationId`/package name внутри APK, а `androidVersionCode` — точно с `versionCode` внутри APK.

Источник: `android/app/src/main/java/xyz/kusoft/argusstore/PackageInstallerManager.kt:24-55`.

### 3.4. Требования к `platform` для подбора совместимого APK

Клиент выбирает релиз как совместимый, если:

- `packageKind == "apk"`, и
- `platform` соответствует одному из распознаваемых вариантов.

Релиз считается универсальным, если `platform` равен:

- `android`
- `android-universal`
- `android-generic`
- `generic`

Для ABI-специфичных APK клиент ищет в `platform` одну из подстрок, связанных с ABI устройства:

- `arm64`
- `arm64-v8a`
- `android-arm64`
- `android-arm64-v8a`
- `armv7`
- `armeabi-v7a`
- `android-armv7`
- `android-armeabi-v7a`
- `x86_64`
- `android-x86_64`
- `x86`
- `android-x86`

Рекомендация для предсказуемости публикации:

- universal APK: использовать `platform=android-universal`;
- ARM64 APK: использовать `platform=android-arm64`;
- ARMv7 APK: использовать `platform=android-armv7`;
- x86_64 APK: использовать `platform=android-x86_64`;
- x86 APK: использовать `platform=android-x86`.

Первые два блока выше — фактическое поведение клиента. Последний блок — рекомендация для единообразия, а не серверная проверка.

Источник: `android/app/src/main/java/xyz/kusoft/argusstore/StoreModels.kt:72-110`.

### 3.5. Как клиент выбирает предпочитаемый APK

Среди совместимых installable APK клиент выбирает релиз по такому порядку:

1. больший `androidVersionCode`;
2. более поздний `publishedAt`;
3. большее строковое `version`.

Практический вывод: для Android-обновлений нельзя полагаться только на красивый `version`; нужно монотонно увеличивать `androidVersionCode`.

Источник: `android/app/src/main/java/xyz/kusoft/argusstore/StoreModels.kt:72-82`, `PackageStateResolver.kt:44-59`.

## 4. Что нужно для корректной работы обновлений

### 4.1. Для обычного обнаружения обновлений

Чтобы клиент видел, что доступно обновление, нужны одновременно:

- установленный пакет с тем же `androidPackageName`;
- опубликованный совместимый APK;
- у релиза должен быть заполнен `androidVersionCode`;
- новый `androidVersionCode` должен быть больше установленного на устройстве.

Источник: `android/app/src/main/java/xyz/kusoft/argusstore/PackageStateResolver.kt:33-59`.

### 4.2. Для best-effort автообновлений без ручного подтверждения

Текущий клиент считает автообновление потенциально возможным только если одновременно выполняются все условия:

- приложение уже установлено;
- опубликованный APK имеет `androidVersionCode`;
- в настройках Android разрешены установки из `Argus Store`;
- устройство работает минимум на Android 12 / API 31;
- установленное приложение было установлено именно `Argus Store`;
- `targetSdkVersion` установленного приложения не ниже минимального значения для текущего API устройства.

Минимальные `targetSdkVersion`, которые сейчас проверяет клиент:

- API 31-32: `29`
- API 33: `30`
- API 34: `31`
- API 35: `33`
- API 36 и выше: `34`

Важно:

- эти условия не гарантируют полностью бесшумное обновление на любом устройстве;
- это только текущая эвристика клиента, при которой он считает no-touch update допустимым к попытке.

Источник: `android/app/src/main/java/xyz/kusoft/argusstore/PackageStateResolver.kt:76-129`, `PackageStateResolver.kt:244-252`.

## 5. Рекомендации по версии и артефакту

### 5.1. `version`

Сервер выбирает `latest` релиз по semantic version precedence, если обе сравниваемые версии парсятся как semver. Если хотя бы одна версия не парсится, сравнение откатывается к обычному case-insensitive строковому сравнению.

Практическая рекомендация: использовать semver-совместимые версии:

- `1.0.0`
- `1.2.3`
- `1.2.3-beta.1`
- `v1.2.3`

Это рекомендация для предсказуемого выбора `latest`, а не отдельная серверная валидация.

Источник: `Infrastructure/SemanticVersionUtility.cs:5-240`, `Program.cs:563-567`, `README.md:20`.

### 5.2. Расширение файла

Сервер сохраняет любой непустой загруженный файл и вычисляет для него:

- `SHA-256`
- размер в байтах
- исходное имя файла
- content type

Но текущий publish-скрипт автоматически выставляет специальный MIME type только для файлов с расширением `.apk`. Для остальных расширений он ставит `application/octet-stream`.

Практическое требование для Android-публикаций: использовать реальный `.apk` файл.

Источник: `Infrastructure/PackageStorageService.cs:8-97`, `scripts/publish-release.ps1:111-121`.

## 6. Шаблон brief'а для другого ИИ

Ниже шаблон, который можно отдавать ИИ как входное задание. Он построен только из полей и ограничений, которые реально используются текущим `Argus`.

```yaml
argusPublication:
  target: "Argus"
  mustBeVisibleInArgusStore: true

  app:
    slug: "<lowercase-slug-with-hyphens-only>"
    name: "<public app name>"
    summary: "<short one-line summary>"
    description: "<full description>"
    repositoryUrl: "<optional https url or null>"
    homepageUrl: "<optional https url or null>"
    isListed: true

  android:
    androidPackageName: "<java.package.name>"
    versionName: "<human-readable version, ideally semver>"
    versionCode: <positive integer>
    targetSdkVersion: <integer>
    abiKind: "<universal|arm64|armv7|x86_64|x86>"

  release:
    version: "<same semantic release label as versionName, if applicable>"
    channel: "stable"
    platform: "<android-universal|android-arm64|android-armv7|android-x86_64|android-x86>"
    packageKind: "apk"
    notes: "<optional release notes or null>"
    packagePath: "<absolute or repo-relative path to final .apk>"

  mandatoryChecks:
    - "APK exists and is non-empty."
    - "APK package name exactly matches androidPackageName."
    - "APK versionCode exactly matches androidVersionCode."
    - "If the app should appear in Argus Store, isListed must be true."
    - "No existing Argus release uses the same slug + version + channel + platform."
    - "If this is an update, androidVersionCode is greater than the already published Android version code."
```

## 7. Минимальная команда публикации

Текущий стандартный путь публикации в этом репозитории — `scripts/publish-release.ps1`, который:

1. логинится в admin API;
2. делает `PUT /api/admin/apps/{slug}`;
3. делает `POST /api/admin/apps/{slug}/releases`.

Минимальный пример для Android APK:

```powershell
.\scripts\publish-release.ps1 `
  -BaseUrl "https://argus.kusoft.xyz" `
  -Username "<admin>" `
  -Password "<password>" `
  -Slug "<slug>" `
  -Name "<name>" `
  -Summary "<summary>" `
  -Description "<description>" `
  -AndroidPackageName "<java.package.name>" `
  -Version "<version>" `
  -Channel "stable" `
  -Platform "android-universal" `
  -PackageKind "apk" `
  -AndroidVersionCode <positive integer> `
  -PackagePath "<path-to.apk>"
```

Источник: `scripts/publish-release.ps1:53-130`, `README.md:74-90`.
