feat/declarative-app-dx #337

Merged
johnride merged 20 commits from feat/declarative-app-dx into master 2026-06-22 23:07:43 +00:00

20 Commits

Author SHA1 Message Date
0bcfe7941e refactor(app): make the application layer topology-generic
All checks were successful
Run Check Script / check (pull_request) Successful in 2m33s
HarmonyApp<T> and Capability<T> are now generic over the target Topology
instead of hardwired to K8sAnywhereTopology. Each concrete capability is
implemented only for topologies that can host its Scores — Postgres for
T: K8sclient+HelmCommand, Monitoring for T: HelmCommand+TenantManager,
ZitadelAuth for T: K8sclient — so .with(X) is a compile error on a topology
that can't run X (the framework's compile-time-capability thesis, now applied
to the app layer). ComposeDeploy<T> and the deploy/ship verbs carry T through;
status/logs stay generic. The CLI front-end (app_main) pins K8sAnywhereTopology;
a different front-end can drive another topology. Example main.rs is unchanged
— T infers from app_main. 29+1 harmony_app, 11 harmony_cli tests green.
2026-06-22 19:02:11 -04:00
b2c3c01081 refactor(app): promote compose deploy stack into harmony_app; example is just main.rs
Move the compose importer, chart generator, ComposeAppScore, publish bricks,
and the ComposeDeploy builder out of the example into harmony_app (they needed
harmony_app::Profile/Capability, so harmony core would be circular — harmony_app
is the home, per decision A). The example is now a 23-line main.rs + app/ +
contexts; folk imports from harmony_app too. Tests + the helm lint/template
integration test moved along. 29 lib + 1 integration test green.
2026-06-22 18:17:39 -04:00
5235e46d1e fix(dx): --config for the contexts file; rewrite stale example README
- app_main gains -C/--config <FILE> to point at the contexts file from any
  cwd (the 'no .harmony/contexts.toml found' trap); error now names the flag.
- examples/compose_java_react/README rewritten to the declarative ComposeDeploy
  + verb/--context surface; drops removed flags (--host/--storage-class/
  --published/--yes/compose-publish) and documents .with(...) capabilities.
  (Both flagged by the fresh-eyes DX reviews.)
2026-06-22 16:46:41 -04:00
32c6777d57 feat(app): ZitadelAuth capability — PKCE OIDC login, validated live
- ZitadelClientIdExportScore (harmony): reads a provisioned app's client_id
  from the Zitadel cache and writes it to a ConfigMap — the Zitadel→app
  bridge, by reference (client_id is non-secret under PKCE).
- ZitadelAuth capability (harmony_app): .with(ZitadelAuth::oidc(issuer)
  .project(..).redirect(..)) provisions a PKCE app (USER_AGENT/auth NONE) via
  ZitadelSetupScore + the export, and injects OIDC_ISSUER (plain) +
  OIDC_CLIENT_ID (configMapKeyRef). Keeps .secret(...) for other config.
- Validated end-to-end against a live local Zitadel + k3d (gated #[ignore]
  test): PKCE app created in Zitadel; client_id mirrored into the ConfigMap.
2026-06-22 16:28:49 -04:00
f244983b40 feat(zitadel): create OIDC PKCE (web/SPA) apps, not just DeviceCode
Adds ZitadelAppType::WebPkce { redirect_uris, post_logout_redirect_uris } —
appType=USER_AGENT, authMethod=NONE (Authorization Code + PKCE, no client
secret) — for React/SPA frontends. create/update refactored over shared
create_oidc_app/update_oidc_config helpers (DRY). The PKCE body was validated
against a live local Zitadel (Management API returned appId + clientId).
2026-06-22 15:46:58 -04:00
4a9bee73ad docs+deprecate: Application Capabilities guide; deprecate ApplicationFeature model
- docs/guides/application-capabilities.md: the .with(...) menu, by-reference
  wiring, and a recipe for writing a capability; disambiguates the overloaded
  'capability' (topology vs application) flagged in DX review; wired into the book.
- Deprecate ApplicationScore, features::Monitoring, features::PackagingDeployment
  (+ doc-deprecate the ApplicationFeature trait) in favour of harmony_app +
  ComposeDeploy/.with(...) (ADR-026). Legacy examples now warn; harmony lib is
  warning-clean (internal self-refs allowed).
2026-06-22 15:38:35 -04:00
51b6e02a5a feat(app): Monitoring capability — namespace-scoped downtime alert
.with(Monitoring::new().alert(receiver)) composes HelmPrometheusAlertingScore
with a kube-state-metrics deployment-availability alert
(kube_deployment_status_replicas_available{namespace} == 0, 5m) — no app-side
/metrics or ServiceMonitor needed. Composition mirrors examples/monitoring
(proven pattern); rule helper unit-tested. Live alert-firing validation needs
kube-prometheus on the cluster. Addresses the downtime-rule intent (task #4).
2026-06-22 15:01:25 -04:00
cdf0cdad40 refactor(app): promote Capability + Postgres into harmony_app (framework menu)
Capabilities were example-local (a discoverability trapdoor per review). Move
the Capability trait, AppRef, and the Postgres capability into
harmony_app::capabilities so .with(...) is a real, framework-level menu;
ComposeDeploy now consumes the framework trait. Pure capability tests live in
harmony_app; ComposeDeploy keeps the integration test. No behavior change.
Foundation for the Monitoring + ZitadelAuth capabilities (folk prod).
2026-06-22 14:50:50 -04:00
716fb58c39 feat(examples/compose): .with(Capability) — composable add-ons, reference-wired
A Capability contributes Scores + injects env wired by k8s reference (service
DNS / secretKeyRef), so .with(Postgres::managed()) deploys a CNPG cluster and
wires DATABASE_URL from its generated <app>-db-app secret — the password never
passes through Harmony, so the chart stays publishable. No change to the
Score/Interpret/Outcome contract (typed outputs deferred until a non-reference
value needs wiring). TDD: capability tests; verified end-to-end on k3d
(cluster healthy, DATABASE_URL referenced, app pods ready).
2026-06-22 06:21:31 -04:00
e9539571d8 feat(examples/compose): declarative ComposeDeploy authoring (manifest-style DX)
Declare an app fluently — from_dir().expose().secret() — and it implements
HarmonyApp, so app_main gives ship/deploy/status/logs over any context. The
example's main is now ~20 lines of pure declaration; the flag soup is gone.
TDD: 4 tests pin the spec→Score derivation (local renders/RWO/HTTP, prod
publishes/replicated/TLS, defaults, secrets). Verified end-to-end on k3d.
2026-06-22 05:46:21 -04:00
67c11682bf fix(cli): rename --version to --tag; drop ADR jargon from help/errors; simplify deploy
--version no longer hijacked (it prints version again); the release tag is
--tag. User-facing help/errors no longer cite ADRs. deploy() clones scores
before register instead of reading them back through a lock.
2026-06-22 05:24:24 -04:00
d5a778a761 feat(app): in-repo context store + OpenBao-brokered cluster access
Contexts are defined in .harmony/contexts.toml (found by walking up from cwd),
so collaborators and CI need zero local setup. A context owns cluster access:
k3d (local) or a kubeconfig brokered from OpenBao via harmony_config
(Zitadel-authenticated, the CI/prod path) as a ClusterAccess secret — separate
from the app's own secrets. --context required (ADR-026 §1).
2026-06-22 05:04:42 -04:00
7af6540a3a refactor(examples/compose): use harmony_app::Profile; DeployConfig::for_profile
Profile tag is now the shared harmony_app one; the k8s-knob mapping (storage/
replicas/RWX vs RWO/TLS) lives in DeployConfig::for_profile + cluster_issuer_for.
2026-06-22 04:45:16 -04:00
c5280dbf04 feat(cli): app_main — verb front-end (harmony app <verb>) over harmony_app
Parses argv, resolves a context (required, no default per ADR-026 §1), calls
the harmony_app verbs, renders. A per-app binary becomes: fn main() {
app_main(MyApp) }.
2026-06-22 04:45:16 -04:00
7af236c74a feat(app): harmony_app — UI-agnostic application lifecycle layer
The home the modules::application feature was missing (ADR-026): a HarmonyApp
describes identity + how to build its Scores per context; ship/deploy/status/
logs are verbs returning structured results, never printed. CLI/TUI/web are
front-ends over these verbs — no UI coupling. AppContext is the selected
target+profile; converges the same Scores wherever it points.
2026-06-22 04:45:16 -04:00
0f6ef8731f feat(topology): env-free kubeconfig config + pod log capability
K8sAnywhereConfig::kubeconfig(path, ctx) lets programmatic callers (the app
layer, TUI, web) target a cluster without mutating process env. K8sClient
gains pod_logs for operational read verbs.
2026-06-22 04:45:16 -04:00
87509ca99a docs(adr): add ADR-027 multi-tenant cloud identity
One shared Zitadel project as universal audience; tenant scoping via the
groups claim; per-tenant onboarding is provisioning, not app/auth changes.
Index 026 + 027.
2026-06-22 04:28:02 -04:00
cde433005b refactor(examples/compose): Profile-driven config, publish bricks, app-secret via Score
- Profile {Local,Prod} supplies storage/replicas/rolling/TLS + publish target
- publish split into build_images/push_to_registry/import_to_k3d behind PublishTarget
- app_secrets applied inside ComposeAppScore (envFrom), no handrolled manifest (ADR-023)
- PVC access mode derived from update strategy (RWX rolling, RWO recreate) — k3d-deployable
- PublicEndpoint::from_compose dedups port extraction
2026-06-22 04:28:02 -04:00
a6985ece81 feat(config): add TomlFileSource for offline/local config
Human-friendly sibling of LocalFileSource: reads <base>/<key>.toml, so
multi-line values (kubeconfigs, PEM) need no escaping. Wired via an explicit
ConfigClient::new chain; cleartext on disk, local/offline use only.
2026-06-22 04:28:01 -04:00
56c1b76373 chore(workspace): make private crates standalone, drop private_repos/* glob
Globbing private_repos/* into the public workspace leaked gitignored crate
names into the committed Cargo.lock. Each private crate is now its own
standalone workspace; the public root no longer references them.
2026-06-22 04:27:40 -04:00