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

8.5 KiB

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 — 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 buildharmony app publish --context myapp-prodharmony 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):

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].