Files
harmony/docs/guides/application-cli.md
Sylvain Tremblay 51c9499c55 docs(adr): add ADR-025 application lifecycle CLI
Define the dev-facing CLI contract: `harmony <scope> <verb>` grammar,
mandatory explicit context (no default), declarative/operational verb
split, three config homes, per-env profile tag, and the machine/agent
contract. Add the living CLI use-case & command guide and register it in
the mdbook nav. Mark ADR-012 superseded by 025.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 06:06:59 -04:00

183 lines
8.5 KiB
Markdown

# Harmony Application CLI — Use Cases & Commands
> **Status: design target (proposed).** Today `harmony_cli` runs Scores
> via flags; none of the verbs below are implemented yet. This is the
> living reference we are building toward. The *decisions and rationale*
> live in [ADR-025](../adr/025-application-lifecycle-cli.md) — read that
> for the "why"; this doc is the "what" and "how".
## Mental model
Four ideas carry the whole CLI:
- **`harmony <scope> <verb>`.** A small fixed set of scope nouns —
**`app`** (developer lifecycle), **`tenant`** (tenant-admin),
**`cluster`** (cluster-admin), **`context`** (everyone). What you *can*
run is decided server-side by your context's identity, not by the
command existing.
- **Implicit app, explicit context.** The app is *this project's app*
(inferred — you never name it). The **target is always explicit**:
`--context <name>` or `HARMONY_CONTEXT`. **There is no default context;
omitting it is a hard error.** Even local k3d is `--context local`.
- **Declarative vs operational.** Verbs that change desired state go
through the project's typed Scores and re-converge. Verbs that only
read or are ephemeral talk to the cluster directly and never mutate
desired state. You never imperatively edit live state — you edit a
Score and redeploy.
- **Config has three homes, none of them a config file.** Behavior →
typed Scores (in git). Target + credentials → the context. Secrets →
OpenBao.
### Contexts & profiles
A **context** is `{ cluster (endpoint + CA), tenant, credential source /
identity, profile }`. It carries **no role** — authorization is enforced
server-side from the identity's token (Zitadel → OpenBao → RBAC), never a
client-side setting. Contexts are defined out-of-band (user/CI config),
not in the project.
```
harmony context list # what can I target?
harmony context show myapp-prod # cluster, tenant, profile, identity (+ role, derived from token)
```
The **profile** (`local | staging | prod`) is a **structured field** the
context carries — *not* its name. A Score branches on `ctx.profile()` to
compute per-environment values (`prod` → 3 replicas + managed Postgres;
`local` → 1 replica + sqlite), and **never parses the context name**. The
name (`myapp-prod`) is a free human handle for picking `--context`; the
profile field is the authoritative value, so two differently-named contexts
can share `profile = Prod` (and `myapp-prod` vs `otherapp-prod` are two
different contexts at the same profile). See ADR-025 §7.
Credentials ride on the context and degrade: **local** uses the ambient
k3d/kubeconfig; **remote** mints a short-lived, namespace-scoped token via
Zitadel→OpenBao. The CLI holds nothing standing (ADR-025 §10).
---
## `app` — the developer lifecycle
The verbs, by class:
| Verb | Class | Context? | What it does |
|---|---|---|---|
| `check` | declarative | no | Compile + type-check the Scores. The only pre-deploy validation today (no live diff yet). |
| `build` | declarative | no | Build a digest-pinned OCI image; prints the digest. Environment-agnostic. |
| `publish` | declarative | **yes** | Push the image to the registry (remote) **or** `k3d image import` (local). Topology-specific. |
| `deploy` | declarative | **yes** | Converge the Scores against the context, pinned to `--image <digest>`. **Does not build.** Returns only after smoke-test. |
| `ship` | declarative | **yes** | `build` + `publish` + `deploy`, threading the digest. The everyday verb. |
| `run` | declarative | **yes** | Run a one-off Score-driven Job (migration, task). |
| `logs` | operational | **yes** | Stream/tail logs. Deep search lives in the observability tool. |
| `status` | operational | **yes** | Health, replicas, last deploy, recent events. |
| `exec` | operational | **yes** | Shell/exec into a running container. |
| `forward` | operational | **yes** | Port-forward a service to localhost. |
| `restart` | operational | **yes** | Rollout-restart a workload (pods return identical; no state change). |
| `describe` | operational | **yes** | Resource detail + events for diagnosis. |
| `history` | operational | **yes** | Deploy history — what shipped, when, which digest. |
### Develop (inner loop)
| I want to… | Command |
|---|---|
| Check my Scores compile/type-check | `harmony app check` |
| Build the image to test it builds | `harmony app build` |
| Deploy to local k3d and test | `harmony app ship --context local` |
| See it running locally | `harmony app status --context local` · `harmony app logs --context local -f` |
### Ship to production
| I want to… | Command |
|---|---|
| Build + publish + deploy to prod | `harmony app ship --context myapp-prod` |
| Deploy an already-built image | `harmony app deploy --context myapp-prod --image <digest>` |
| Roll **forward** to a prior good build (recovery) | `harmony app deploy --context myapp-prod --image <prior-digest>` |
| Build / publish as separate CI stages | `harmony app build``harmony app publish --context myapp-prod``harmony app deploy …` |
There is no rollback and no staging (the roll-forward-only model): you roll forward by
deploying a known-good digest.
### Operate (day-2)
| I want to… | Command |
|---|---|
| Tail logs | `harmony app logs --context myapp-prod -f` |
| Check health / what's deployed | `harmony app status --context myapp-prod` |
| Shell into the app | `harmony app exec --context myapp-prod -- /bin/sh` |
| Reach a service locally | `harmony app forward --context myapp-prod 8080:80` |
| Restart the app | `harmony app restart --context myapp-prod` |
| Run a DB migration | `harmony app run --context myapp-prod -- migrate` |
| Change replicas / env / resources | *edit the Score, then* `harmony app ship --context myapp-prod` |
> Changing replicas/env/config is **not** an imperative command — it's a
> Score edit + redeploy. An out-of-band `kubectl scale` survives only
> until the next deploy, then is reconciled away (ADR-025 §5, §8).
### Debug / diagnose
| I want to… | Command |
|---|---|
| Understand why a deploy failed | read the `deploy` output (convergence errors, rustc-style) |
| See why a workload is unhealthy | `harmony app describe --context myapp-prod` · `harmony app logs …` |
| See what shipped recently | `harmony app history --context myapp-prod` |
| Deep log search / traces / metrics | open the observability tool (HyperDX/SigNoz/Grafana) — the CLI deep-links |
---
## `tenant` — tenant administration (tenant-admin)
> Mostly **manual today** (ADR-025 Out of scope: step-0 provisioning).
> Listed as the target surface; authorization is by the context's role.
| I want to… | Command (planned) |
|---|---|
| List apps / environments in my tenant | `harmony tenant list --context my-tenant` |
| Create an environment (namespace — billed) | `harmony tenant env create <name> --context my-tenant` |
| Add / remove a developer | `harmony tenant member add <user> --context my-tenant` |
| See usage vs quota | `harmony tenant usage --context my-tenant` (or the dashboard) |
| Rotate an app's deploy key | `harmony tenant key rotate <app> --context my-tenant` |
---
## `cluster` — cluster administration (platform operator)
> **Future / manual today.** Same noun-by-scope, authorization by role
> (only a cluster-admin context may run these).
| I want to… | Command (planned) |
|---|---|
| Provision a tenant | `harmony tenant create <name> --context <cluster-admin-ctx>` |
| Cluster-wide status | `harmony cluster status --context <cluster-admin-ctx>` |
| Manage platform operators | `harmony cluster operator …` |
---
## Machine & agent usage
Every verb is built for CI and autonomous agents — the same surface,
strict mode (ADR-025 §9):
- `--json` → a frozen, versioned schema on **stdout**; logs/progress on
**stderr**.
- Non-interactive when stdin isn't a TTY — no hidden prompts.
- Idempotent verbs; `Outcome` (SUCCESS/NOOP/FAILURE/RUNNING/BLOCKED)
mapped to **exit codes** so CI/agents branch without scraping text.
- A future agent skill / MCP surface is *derived from* this reference,
not authored separately.
Example CI release (digest pinned end-to-end):
```sh
DIGEST=$(harmony app publish --context myapp-prod --json | jq -r .image)
harmony app deploy --context myapp-prod --image "$DIGEST" --json
```
---
## Status legend
Nothing here is implemented yet. As verbs land, tag them:
**`[impl]`** implemented · **`[wip]`** in progress · **`[planned]`** ·
**`[future]`** out of v1 scope (per ADR-025). Until the first verbs
ship, treat the whole document as `[planned]`.