# metin-release-cli plan

Plan for a release orchestration toolchain that stays outside NewERP and lets
the ERP module act only as the control plane.

## Goal

Build two components:

- `metin-release-cli` as the real operator and automation entry point
- `metin-release-mcp` as a thin MCP wrapper over the CLI

The ERP module should not own manifest generation, signing, blob publishing, or
launcher packaging logic. It should call the CLI through a stable contract and
store release metadata, status, logs, and audit trail.

## Why this split

Current release concerns already live across multiple repos and runtimes:

- `metin-launcher` consumes signed `manifest.json` + blob storage
- `m2dev-client` already has `make-manifest.py` and `sign-manifest.py`
- `m2pack-secure` owns `.m2p` build, verify, diff, and runtime-key export
- launcher self-update currently depends on Velopack packaging, which is still
  Windows-host constrained

Putting that logic into NewERP would duplicate the release path and make the ERP
module too heavy. The CLI should own the workflow. ERP should orchestrate it.

## Design principles

- CLI first. Every real operation must be runnable without MCP.
- JSON first. Every non-interactive CLI command must support machine-readable
  output.
- Thin wrapper. MCP must call CLI commands, parse JSON, and return it. No
  duplicated business logic.
- Explicit state. Releases move through named stages and emit durable logs.
- Secret isolation. Signing keys and content keys stay on the release machine or
  in a secret store, never in ERP DB.
- Linux-first. Manifest build, signing, blob publish, and `.m2p` validation must
  run on Linux. Windows-only launcher packaging remains a separate step.

## Scope

`metin-release-cli` should cover:

- create and validate a release workspace
- build or import a release manifest
- sign the manifest
- diff against the currently published release
- upload missing blobs to the update storage
- archive historical manifests
- promote a release to current
- verify the public endpoint after publish
- optionally record release metadata in ERP through HTTP API
- optionally manage `.m2p` release artifacts and runtime-key export
- optionally run launcher publish as a separate command

`metin-release-mcp` should cover:

- expose the same operations as MCP tools
- map tool input to CLI flags
- read CLI JSON output
- pass through logs, status, and errors in structured form

## Non-goals

- No manifest generation inside ERP PHP code
- No signing inside ERP
- No heavy artifact proxying through ERP uploads for multi-GB releases
- No duplicated publish logic in MCP
- No attempt to hide Windows constraints for Velopack launcher packaging

## Canonical release path

For the current launcher contract, the canonical asset release path is:

1. obtain a prepared client root on Linux
2. generate `manifest.json`
3. sign `manifest.json`
4. compute which content-addressed blobs are missing remotely
5. upload missing blobs
6. upload archived manifest under `manifests/<version>.json`
7. upload archived signature under `manifests/<version>.json.sig`
8. switch top-level `manifest.json` and `manifest.json.sig`
9. verify the public endpoint
10. report final release metadata to ERP

For launcher self-update, the path is separate:

1. build win-x64 launcher package on Windows-capable host
2. publish Velopack feed into `/launcher/`
3. optionally annotate the same release in ERP

## Proposed architecture

### 1. metin-release-cli

Suggested implementation language:

- Python is the pragmatic default because the current manifest tooling already
  exists in Python
- shell scripts may wrap platform-specific steps, but the primary interface
  should be one CLI executable with stable subcommands

Suggested internal modules:

- `workspace`
  - resolve source roots, temp dirs, release output dirs
- `manifest`
  - call or absorb `make-manifest.py`
- `signing`
  - call or absorb `sign-manifest.py`
- `storage`
  - remote existence checks
  - blob upload
  - manifest archive upload
  - current manifest promotion
- `verify`
  - verify local artifacts
  - verify public HTTP endpoint
- `erp`
  - create/update release records in ERP
- `m2pack`
  - optional integration with `m2pack-secure`
- `launcher`
  - optional launcher publish integration

### 2. metin-release-mcp

Suggested shape:

- one MCP server process
- each tool maps 1:1 to one CLI subcommand
- wrapper only:
  - validates tool input
  - spawns CLI
  - parses JSON stdout
  - returns structured result
  - forwards stderr as diagnostic text

No release decision logic should live here.

## Release state model

The CLI should emit explicit states the ERP module can mirror:

- `draft`
- `scanning`
- `manifest_built`
- `signed`
- `uploading_blobs`
- `uploaded`
- `promoting`
- `published`
- `verifying`
- `verified`
- `failed`
- `rolled_back`

Each state transition should include:

- `release_id` if known in ERP
- `version`
- `started_at`
- `finished_at`
- `duration_ms`
- `step`
- `status`
- `message`
- `log_path` if available

## CLI command set

Suggested commands:

### Release lifecycle

- `metin-release release init`
  - create local workspace metadata for a new version
- `metin-release release inspect`
  - inspect source root, file counts, launcher presence, total bytes
- `metin-release release build-manifest`
  - generate canonical `manifest.json`
- `metin-release release sign`
  - sign `manifest.json`
- `metin-release release diff-remote`
  - compare manifest blobs against remote storage
- `metin-release release upload-blobs`
  - upload only missing blobs
- `metin-release release upload-manifest-archive`
  - upload `manifests/<version>.json` and `.sig`
- `metin-release release promote`
  - switch current `manifest.json` and `.sig`
- `metin-release release verify-public`
  - fetch public manifest, signature, and optional blob samples
- `metin-release release publish`
  - composite command executing the full asset publish flow
- `metin-release release rollback`
  - promote a historical manifest back to current

### ERP sync

- `metin-release erp create-release`
- `metin-release erp update-release`
- `metin-release erp append-log`
- `metin-release erp mark-status`

### m2pack integration

- `metin-release m2pack build`
- `metin-release m2pack verify`
- `metin-release m2pack diff`
- `metin-release m2pack export-runtime-key`

### Launcher integration

- `metin-release launcher publish`
  - separate path because of Windows packaging constraints

## JSON contract

Every automation-facing command should support:

```text
--json
```

Success output shape:

```json
{
  "ok": true,
  "command": "release publish",
  "version": "2026.04.14-1",
  "status": "published",
  "artifacts": {
    "manifest_path": "/abs/path/manifest.json",
    "signature_path": "/abs/path/manifest.json.sig"
  },
  "stats": {
    "file_count": 123,
    "blob_count": 9,
    "uploaded_blob_count": 3,
    "uploaded_bytes": 1048576
  },
  "remote": {
    "manifest_url": "https://updates.jakubkadlec.dev/manifest.json"
  },
  "erp": {
    "release_id": 42
  }
}
```

Failure output shape:

```json
{
  "ok": false,
  "command": "release publish",
  "version": "2026.04.14-1",
  "status": "failed",
  "error": {
    "code": "blob_upload_failed",
    "message": "Failed to upload one or more blobs"
  }
}
```

Exit code rules:

- `0` success
- `1` operator or validation error
- `2` remote or network error
- `3` signing or integrity error
- `4` ERP sync error

## ERP integration contract

The ERP module should integrate with the CLI at the orchestration layer only.

Recommended boundary:

- ERP creates a draft release record
- ERP stores operator intent, notes, target channel, and visibility
- ERP triggers the CLI through a worker, SSH command, queue job, or agent
- CLI performs release operations
- CLI pushes structured status updates back to ERP
- ERP renders the timeline, logs, artifacts, and rollback actions

Recommended ERP-owned data:

- release version
- notes
- previous release reference
- publish status
- operator identity
- started/finished timestamps
- manifest metadata
- remote verification status
- launcher publish status
- pointers to logs and artifacts

CLI-owned data:

- actual manifest generation
- signing
- file hashing
- blob deduplication
- upload semantics
- runtime-key export
- launcher packaging invocation

## Remote storage contract

For the current launcher path, the CLI should treat this as canonical:

- `/var/www/updates.jakubkadlec.dev/files/<hash-prefix>/<sha256>`
- `/var/www/updates.jakubkadlec.dev/manifests/<version>.json`
- `/var/www/updates.jakubkadlec.dev/manifests/<version>.json.sig`
- `/var/www/updates.jakubkadlec.dev/manifest.json`
- `/var/www/updates.jakubkadlec.dev/manifest.json.sig`
- `/var/www/updates.jakubkadlec.dev/launcher/*`

Promotion safety rule:

- upload immutable blobs first
- upload archived versioned manifest second
- replace current manifest last

## Secrets and security

- launcher manifest signing key remains outside ERP
- `.m2p` master keys remain outside ERP
- CLI reads secrets from:
  - fixed secure file paths
  - environment variables
  - secret manager integration later
- CLI must redact secrets from logs and JSON output

Never return:

- raw private keys
- runtime master keys
- decrypted secret material

## Linux and Wine posture

Supported on Linux now:

- client tree scan
- manifest build
- manifest signing
- blob upload and promotion
- `.m2p` build and verification
- Wine-based smoke validation if needed

Not fully Linux-native yet:

- Velopack packaging for win-x64 launcher release

Implication:

- asset release commands should be Linux-first
- launcher publish should remain an explicit separate command and may require a
  Windows runner or Windows host until the toolchain changes

## MCP tool set

Suggested MCP tools:

- `release_init`
- `release_inspect`
- `release_build_manifest`
- `release_sign`
- `release_diff_remote`
- `release_upload_blobs`
- `release_promote`
- `release_verify_public`
- `release_publish`
- `release_rollback`
- `erp_create_release`
- `erp_update_release`
- `m2pack_build`
- `m2pack_verify`
- `m2pack_diff`
- `m2pack_export_runtime_key`
- `launcher_publish`

Each tool should document the exact CLI command it runs.

## Recommended implementation phases

### Phase 1. Asset release CLI

Build first:

- `release inspect`
- `release build-manifest`
- `release sign`
- `release diff-remote`
- `release upload-blobs`
- `release promote`
- `release verify-public`
- `release publish`

This is the minimum path ERP needs for the current launcher contract.

### Phase 2. ERP sync

Build second:

- `erp create-release`
- `erp update-release`
- CLI status callbacks or polling contract

This lets the ERP module focus on UI, permissions, and audit trail.

### Phase 3. MCP wrapper

Build third:

- expose the Phase 1 and 2 CLI commands as MCP tools
- no new logic

### Phase 4. m2pack path

Build fourth:

- `m2pack build`
- `m2pack verify`
- `m2pack diff`
- `m2pack export-runtime-key`

Only after the signed-manifest release path is stable.

### Phase 5. Launcher release path

Build fifth:

- `launcher publish`
- Windows-capable execution environment
- optional ERP annotation

## Open decisions

- Whether the CLI should directly push status to ERP or ERP should poll CLI job
  results
- Whether release workspaces are local-only or persisted on a shared release
  host
- Whether `.m2p` artifacts become part of the same release object now or later
- Whether launcher release should be represented as:
  - separate release type
  - or a child job under the same release

## Recommendation

Start with:

- one Linux-first `metin-release-cli`
- one thin `metin-release-mcp`
- ERP module consuming only CLI results and statuses

Do not start by embedding release logic in ERP. That will create a second
source of truth and make later `.m2p` and launcher evolution harder.
