From 0526ac2ef9a433b6699d981465861f82cde757a5 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 14 Apr 2026 18:30:43 +0200 Subject: [PATCH] docs: add metin release cli plan --- docs/metin-release-cli-plan.md | 466 +++++++++++++++++++++++++++++++++ 1 file changed, 466 insertions(+) create mode 100644 docs/metin-release-cli-plan.md diff --git a/docs/metin-release-cli-plan.md b/docs/metin-release-cli-plan.md new file mode 100644 index 0000000..f7d0b3b --- /dev/null +++ b/docs/metin-release-cli-plan.md @@ -0,0 +1,466 @@ +# 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/.json` +7. upload archived signature under `manifests/.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/.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//` +- `/var/www/updates.jakubkadlec.dev/manifests/.json` +- `/var/www/updates.jakubkadlec.dev/manifests/.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.