provisioning-code/CHANGELOG.md

28 KiB
Raw Permalink Blame History

Provisioning Repository - Changes


2026-06-08 — provisioning-desktop: NATS auto-start + configurable launchers (ADR-049)

Refines ADR-048 by extending what the shell does on the local host.

Generalized process supervision: the daemon supervisor is extracted into a process-agnostic ProcessSupervisor driven by a ProcessSpec (spawn + health + restart-backoff + graceful SIGTERM→SIGKILL, port pre-check). The daemon (HTTP /health) and a local nats-server (TCP-connect health) are two specs on one shared path; DaemonStatus/DaemonControl are kept as aliases so existing code is untouched and the backoff tests are preserved.

NATS auto-start (local-or-remote, like the daemon): a local nats-server is spawned and supervised only when nats.spawn is set AND nats.url is loopback (-a <host> -p <port> -js), started before the daemon and awaited (bounded) so the daemon and consumer bridge find it listening. The same port pre-check adopts an already-running external server instead of double-spawning. Remote nats.url stays connect-only — never spawned. Absent nats-server degrades to the REST poller, not a panic.

Configurable launchers (replaces tray actions): a unified Launcher { kind, label, target, args, params, show_output, confirm } declared per-connection and globally, merged in the tray. kind=url opens the OS browser (platform opener); kind=command runs a local program host-side via tokio::process, capturing stdout/stderr into a reusable bundled output window (dist/output.html); kind=tool invokes a daemon /api/v1 tool (the former action). Legacy [[connections.actions]] TOML migrates transparently (serde alias = "actions", defaulted kind=tool, tooltarget alias).

Security boundary: command execution is host-side only — in the Rust tray handler, sourced from the local settings.toml, and deliberately not a Tauri command — so the external /ui/ webview (no invoke) and remote connections cannot trigger host execution. Preserves the ADR-048 host-bridge boundary.

Settings form: the bundled settings window gains a Launchers editor (scope selector, kind-aware fields, JSON params validation); save_settings now carries the AppHandle and rebuilds the tray so edits appear immediately.

Verified: cargo clippy -p provisioning-desktop --all-targets -- -D warnings clean; cargo test -p provisioning-desktop → 34 passed (incl. legacy actions→launcher migration, NATS loopback-spawn gating, host/port parsing).

Also — daemon UI SPA fix (provisioning-daemon/ui/templates/base.html): the SPA router dropped per-page <head> <script src>/<link> on client-side navigation, so Cytoscape never loaded and the workspace DAG view rendered empty in the desktop webview (which is SPA-only) while a full browser load worked. _spaNavigate now loads a fetched page's <head> vendor assets (deduped by resolved URL) before running its inline scripts. Fixes the DAG view and any future {% block head %} vendor-lib page (e.g. ontology.html).

ADR-049: provisioning-desktop local-host surface: generalized process supervision (daemon + NATS) and configurable launchers (url/command/tool) (Accepted). 3 constraints — 1 Hard (desktop-command-launcher-host-side-only) + 2 Soft on centralized-vs-scripted / capability granularity (desktop-nats-spawn-loopback-only, desktop-launchers-config-driven). ontology_check.verdict = 'Safe. Engages centralized-vs-scripted (new core.ncl edge, 'Complements): local affordances beside the orchestrator path, not through it.

Files: platform/crates/provisioning-desktop/src/{supervisor,settings,connection,tray,commands,state,window,lib}.rs, dist/{output.html,settings.html}, capabilities/default.json; provisioning-daemon/ui/templates/base.html; .ontoref/adrs/adr-049-desktop-local-surface.ncl, .ontoref/ontology/core.ncl.


2026-06-08 — provisioning-desktop: Tauri native shell for the daemon

New crate platform/crates/provisioning-desktop

Decision: a Tauri 2 native shell (desktop + iOS) that wraps the daemon's existing /ui/ in a webview rather than reimplementing a native UI — the daemon's web surface stays the single source of UI truth. It is a workspace member but does not depend on provisioning-daemon: it mirrors the small DaemonEvent JSON wire enum locally and reuses only platform-nats, so a GUI app never drags in Axum/tera/provisioning-core (also sidesteps the surrealdb rustls lockfile pin).

Live status — durable JetStream consumer + REST fallback: per-device durable pull consumer (durable_name = desktop-<id>, DeliverPolicy::New on first create, RetentionPolicy::Limits so each device keeps an independent cursor, delete-on-logout) on a new DAEMON stream. The stream was added to platform-nats::ensure_provisioning_streams (provisioning.daemon.>) in the same change — this also fixed a latent bug where EventBus::publish failed its JetStream ack on every tool invocation (no stream previously covered the subject). NATS-less remotes fall back to polling GET /api/v1/state/recent (baseline-on-first-poll mirrors DeliverPolicy::New, no notification flood).

Multi-connection profiles: exactly one supervised-local (spawn + health + restart-backoff + graceful SIGTERM→SIGKILL; port pre-check per ADR-027 to avoid double-spawning a CLI-started daemon) plus N connect-only-remote; live switch tears down tasks and re-points the webview.

Auth + security: reuses the daemon matrix (solo/jwt/password) — the shell carries tokens (keyring JWT, /ui/ cookie) but never mints or decides them, honoring the ADR-014 auth boundary. Remote profiles must be https:// (loopback exempt — never crosses a network, and aligns with iOS ATS), enforced at save + activate + load-fallback. The external /ui/ window gets no invoke access; only the bundled settings window does.

Tray: Connections submenu (live switch + rebuild-on-switch), per-connection predefined Actions with confirm gating via a native OK/Cancel dialog, Workspaces (informational), daemon Start/Stop/Restart (local only), status tooltip.

iOS-ready: [lib] crate-type = ["staticlib", "cdylib", "rlib"] + thin [[bin]]; run() carries #[cfg_attr(mobile, tauri::mobile_entry_point)]; tray/settings-window are #[cfg(desktop)]; local profiles coerce to connect-only-remote on mobile. cargo tauri ios init is a local toolchain step (not run here).

Verified: cargo clippy -p provisioning-desktop --all-targets -- -D warnings clean (built on /Volumes/Devel target-dir); cargo test -p provisioning-desktop → 27 passed.

ADR-048: provisioning-desktop: a Tauri native shell wrapping the daemon /ui/, a wire-decoupled workspace member with durable-consumer status (Accepted). 5 constraints — 3 Hard (remote-https-only, external-window-no-invoke, auth-carried-never-minted) + 2 Soft on the monorepo-vs-split Spiral (wire-decoupled-from-daemon, daemon-stream-durable-per-device, direction-of-motion per ondaod). ontology_check.verdict = 'Safe. Sub-decisions folded in as constraints rather than standalone ADRs: lib/bin split is framework-mandated; HTTPS-loopback is localized validation.

Ontology: core.ncl Practice node provisioning-desktop-shell (edges to smart-interface-unification [Complements], monorepo-vs-split [Resolves], platform-shared-infra [ManifestsIn], solid-boundaries [ValidatedBy]; adrs = ["adr-048"]). State: no FSM transition — a new consumer surface, not an advance of a tracked dimension.

Files: platform/crates/provisioning-desktop/ (new crate: api, auth, commands, connection, events, nats_bridge, notify, poller, settings, state, supervisor, tray, window, lib, main + dist/ SPA + tauri.conf.json + capabilities + icons), platform/crates/platform-nats/src/streams.rs, platform/Cargo.toml, justfiles/desktop.just, .ontoref/ontology/core.ncl.


2026-05-29 — catalog management as a multi-surface capability (ADR-047)

ADR-047: Catalog management dispatched through the shared Registry

Decision: catalog management operations are provisioning-core Registry Tools dispatched exclusively through Registry::invoke, so CLI, WebUI, GraphQL, and MCP share identical semantics (no per-surface reimplementation). Per the nushell-vs-rust boundary synthesis (ADR-029) the Rust Tool owns dispatch while the Nushell resolver (contract_resolver.nu) is the tier-3 legacy closure, retired per-operation once G3 contract-test parity passes. 3 Hard constraints: catalog-management-via-registry, catalog-tools-g3-parity, catalog-tier3-nushell-closure.

Implementation — first Tool catalog_validate (read-shaped: evaluate a component manifest against the contract schema via NclCache, report its requires.contract): platform/crates/provisioning-core/src/tools/catalog.rs, registered in with_all_tools. Verified: cargo check -p provisioning-core Finished; G3 parity test catalog_tool_agrees_across_three_tiers (CLI≡HTTP≡MCP) green via a ct_catalog fixture in contract-tests.

Full rename: extension-registry → catalog-registry (no compatibility aliases)

Complete cut-over with zero aliases — every extension-registry/extension_registry/ExtensionRegistry/provisioning-extension-registry identifier is now catalog-registry/catalog_registry/CatalogRegistry/provisioning-catalog-registry.

  • Plano A (crate): package name, lib name, directory (git mv in the platform submodule), workspace member, provisioning-core path dependency, use statements.
  • Plano B (service identity): binary name, CatalogRegistry type (service.rs/lib.rs/main.rs/handlers.rs), runtime service-key strings (is_service_enabled/load_service_config_from_ncl/observability/api_catalog/user-agent), control-center ServiceType::CatalogRegistry + catalog_registry_url + check_catalog_registry, platform-config CatalogRegistryConfig, orchestrator/config_manager, core/nulib health/autostart/startup/service_check/workspace strings, docker-compose service names (multi-user/solo + examples), nginx/prometheus, justfiles, scripts, typedialog forms/scripts.
  • Files renamed on disk (12): api-catalog-catalog-registry.json, schemas/platform/catalog-registry.ncl + configs/catalog-registry.{solo,multiuser,enterprise,cicd}.ncl + defaults/catalog-registry-defaults.ncl + templates/catalog-registry-config.ncl.j2, platform/templates/catalog-registry.ncl.j2, platform/typedialog_templates/catalog-registry.ncl.j2, platform/typedialog/forms/catalog-registry-form.toml, platform/typedialog/scripts/configure-catalog-registry.sh.

Verified: cargo check -p catalog-registry -p control-center -p platform-config -p orchestrator -p provisioning-core Finished; nickel export clean for catalog-registry.{solo,cicd}.ncl, nickel typecheck clean for {multiuser,enterprise} (their export-time "missing url" is a pre-existing template-completeness issue, not the rename).

Excluded: CHANGELOG.md (historical record), platform/archive/ (archived), data/tasks/ (runtime artifacts), one draft SVG diagram label.

Standalone "extension" vocabulary → catalog (Tool API surface)

The catalog-discovery Tools are renamed extension_{list,show,search,capabilities}catalog_{list,show,search,capabilities} (structs Catalog{List,Show,Search,Capabilities}); tools/extension.rstools/catalog_components.rs; daemon UI invoke_tool call-sites updated. The Environment.extensions_root field → catalog_root, and the env var PROVISIONING_EXTENSIONS_ROOTPROVISIONING_CATALOG_PATH — unifying the catalog-root identifier across the Nushell (env.nu) and Rust (daemon) boundary, which previously diverged. Touches provisioning-core, provisioning-daemon, provisioning-tool, control-center-ui, backup-manager, platform-config, mcp-server, the daemon NCL schema, and CLI help/workspace tooling. This changes Tool names on the API surface (HTTP /api/v1/tools/catalog_*, MCP tools/call, CLI) — no aliases.

Ontology: core.ncl Practice node catalog-management-multi-surface (edges to provisioning-registry, g3-contract-invariant, catalog-contract-distribution, nushell-vs-rust-boundary).

Files: adrs/adr-047-catalog-management-multi-surface.ncl, platform/crates/provisioning-core/src/tools/catalog.rs, platform/crates/provisioning-core/src/{registry.rs,tools/mod.rs}, platform/crates/contract-tests/{src/lib.rs,tests/g3_contract.rs}, platform/crates/catalog-registry/ (renamed), platform/Cargo.toml, .ontology/core.ncl


2026-05-29 — catalog externalization + content-addressed contract (ADR-046)

Change: the catalog (providers/components/taskservs/_clusters) no longer must live under $PROVISIONING/catalog. A new PROVISIONING_CATALOG_PATH env var (→ paths.catalog config → $PROVISIONING/catalog default) sets the root; all sub-paths derive from it. Resolved in core/nulib/env.nu; accessor get-catalog-path added; the loose path join "catalog" sites (components, clusters/taskservs run, OCI signing, integration) now derive from the root. Lets a catalog live anywhere — importable/downloadable, no provisioning checkout dependency.

ADR-046: Catalog component contract distributed as content-addressed Domain artifact via OCI referrers

Decision: schemas/catalog/manifest.ncl (the component contract) is distributed as a content-addressed OCI Domain artifact, never vendored by copy. A catalog declares requires.contract = { id, version, subject } (semver intent); resolution pins it to an immutable sha256 digest in catalog.lock.ncl (DomainLockEntry shape), and all automated loading reads the digest. Version→digest discovery uses the OCI Referrers API exclusively — each version is attached as a referrer to a stable anchor digest with an org.opencontainers.image.version annotation, so no mutable tags exist in the load path. Reuses the ADR-042 Domain-artifact primitives; component-owned config schemas stay vendored as 'SchemaLibrary.

Implementation: requires.contract field + ContractRef type in schemas/catalog/manifest.ncl; resolver core/nulib/platform/oci/contract_resolver.nu (real semver matching incl. ^/~/comma ranges, referrers discovery, pull-by-digest, lock writing, anchor + per-version attach publishing) reusing domain_client.nu credential machinery; validation wiring (read-contract-decl, validate-manifest-against-contract). Automated process: core/nulib/platform/oci/contract_cli.nu + justfiles/catalog-contract.just (recipes contract-publish-anchor/-version/-resolve/-validate).

Verification: full round-trip proven end-to-end against a local zot (referrers) — publish anchor → attach v1.0.0+v1.1.0 → discover → resolver selects 1.1.0 for >=1.0.0, <2.0.0 → pull-by-digest → pulled contract.ncl typechecks → catalog.lock.ncl validates against DomainLockEntry. Production publish targets reg.librecloud.online (private — unreachable from dev sandbox).

ADR-046 extension: push-registry as a pluggable-source platform setting

Decision: the OCI push-registry target is a platform setting, not a property of the active workspace. A command resolves endpoint + credentials at call time from a pluggable source — sops | api | literal | env — and materialises them as an env setting (PROVISIONING_REGISTRY + PROVISIONING_PUSH_DOCKER_CONFIG) that the contract resolver honours before the workspace credential flow. In override mode the credential source is the authorization gate (the Age key decrypting the sops file, or the api's own auth), replacing the workspace-scope check — which is what decouples the push target from CWD. Precedence: explicit flag → registry.push.* config → workspace registry_provides fallback.

Implementation: core/nulib/platform/oci/push_registry.nu (resolve-push-registry, select-push-registry; sops/api/literal/env sources reusing the ADR-042 cabling idiom); acquire-docker-config helper in contract_resolver.nu honouring the PROVISIONING_PUSH_DOCKER_CONFIG override across all four OCI functions; select-registry subcommand + contract-use-registry recipe. ADR-046 constraint push-registry-pluggable-source (Hard). Verified: literal/env/sops resolution (real Age-key sops decrypt) + export emission + docker-config materialisation.

Catalog management as a cross-surface invariant

Catalog management is a domain capability that MUST be exposed uniformly across CLI, WebUI, GraphQL, and MCP, dispatching through provisioning-core::Registry per the G3 contract invariant (no per-surface reimplementation). The Nushell modules + just recipes are the current CLI/script surface; cross-surface parity (catalog operations as Registry Tools) is the target.

Ontology: core.ncl Practice nodes catalog-contract-distribution + push-registry-selection (9 edges); tensions monorepo-vs-split and centralized-vs-scripted updated with the ADR-046 synthesis. QA provisioning-catalog (what catalogs are, contents, distribution, cross-surface management) + mode provisioning-catalog (inspect/validate: resolve root, enumerate by kind, typecheck manifests, report contract lock).

Files: adrs/adr-046-catalog-contract-content-addressed.ncl, schemas/catalog/manifest.ncl, core/nulib/platform/oci/{contract_resolver,push_registry,contract_cli}.nu, justfiles/catalog-contract.just, core/nulib/env.nu, core/nulib/platform/config/accessor/functions.nu, .ontology/core.ncl, .ontoref/reflection/qa.ncl, .ontoref/reflection/modes/provisioning-catalog.ncl


2026-05-20 — foundation library relocation (ADR-045 revises ADR-043)

ADR-045: Foundation library relocation — single-consumer provider traits fold into provisioning

Decision: ontoref-compute and ontoref-dns relocate into provisioning/platform/crates/ as workspace members; ontoref-auth and ontoref-ui consolidate into one external foundation workspace; ADR-043's γ-posture two-consumer locked claim for DnsProvider/ComputeProvider is revised.

Why ADR-043's premise lapsed: ADR-043 declared both traits γ-posture locked on a two-consumer set (lian-build + orchestrator). lian-build subsequently issued its own ADR-009 (no-provider-knowledge, 2026-05-18): the libre-forge fleet control plane became operational, fleet-daemon took ownership of VM lifecycle, and lian-build removed all provider knowledge (deleted orchestrator_client.rs, the daemon::compute module, GET /api/providers, the ADR-005 catalog). lian-build is no longer a consumer — DnsProvider/ComputeProvider now have a single consumer, the provisioning orchestrator.

Placement rule: a library is foundation (its own repo/workspace) when plural independent consumers make sharing cheaper than duplication; it folds into a consumer's repo when a single consumer makes a standalone repo pure overhead. auth/ui (3 consumers: ontoref daemon, lian-build, mirador) stay split into foundation; compute/dns (1 consumer: orchestrator) fold into provisioning.

Unaffected: ADR-043's orchestrator-internal decisions — coredns_client.rs deletion, GET /.panel/manifest, compute_provider: Option<Arc<dyn ComputeProvider>> — and its three constraints remain in force. ADR-045 revises only the consumer-count and trait-home claims.

Files: adrs/adr-045-foundation-library-relocation.ncl


2026-05-12 — orchestrator γ-posture adoption (ontoref-foundation)

ADR-043: Orchestrator adopts DnsProvider, ComputeProvider, federation manifest

Decision: Replace coredns_client.rs with Arc<dyn DnsProvider>; add compute_provider: Option<Arc<dyn ComputeProvider>> to AppState; expose GET /.panel/manifest at src/handlers/federation.rs.

γ-posture verdicts confirmed by this ADR:

  • DnsProvider: two consumers (lian-build + orchestrator) → trait locked
  • ComputeProvider: two consumers (lian-build + orchestrator) → lifecycle surface locked; HcloudClient retained for operations outside the trait surface (volumes, floating IPs, images) per γ PARTIAL semantics

/.panel/manifest endpoint: OrchestratorManifest (panel_id, version, connectors, panels, workflows) returned without auth. Superset of the three-field projection consumed by FederationResolver — serves both machine and human consumers.

compute_provider: Option<Arc<dyn ComputeProvider>>: Populated from HetznerCloudProvider::from_env() when HCLOUD_TOKEN is present; None → degraded mode (read-only fleet ops), consistent with ADR-042's no-hard-startup-failure contract.

Files: adrs/adr-043-ontoref-foundation-orchestrator.ncl


2026-05-12 — nickel branch consolidation

ADR-025 Amendments: Eager function-body parse (2026-04-17)

Decision: Nushell parses use statements inside function bodies at module-load time. Subprocess boundary is the only true lazy-load mechanism.

Constraints added/amended:

  • bash-wrapper-has-no-runner-reference — now permits provisioning-cli.nu as transitional fallback
  • universal-fallback-is-transitional — 22 unmapped commands are explicit migration debt; must be resolved before lazy-load architecture is considered complete
  • every-registry-command-has-thin-handler — made directional (progress metric, not gate)

Rejected approach: Single-entry provisioning-cli.nu for hot paths — measured at 3.1s vs 0.080.15s for thin handlers. All 15 dispatcher wrappers fire at module-load regardless of invoked command.

Files: adrs/adr-025-unified-lazy-loading.ncl


Platform Services Documentation (2026-02-03)

All 10 platform services documented with endpoint catalogue and local setup guide.

Services:

Service Endpoints Notes
vault 8 Transit encryption, dynamic secrets
registry 6 OCI extension distribution
control-center 12 RBAC, audit, compliance
rag 5 Vector search, document ingestion
ai-service 7 Model routing, DAG execution
mcp-server 4 AI-powered config tools
daemon 3 State sync, health aggregation
orchestrator 8 Workflow execution, checkpoints
detector 4 Anomaly detection, alerts
ui 3 Web control center frontend

Added:

  • docs/src/operations/platform-services-inventory.md — 50+ endpoints
  • docs/src/operations/local-services-setup.md — build, config, troubleshooting
  • scripts/start-local-binaries.nu — dependency-ordered service startup automation

TypeDialog Migration (2026-01-09)

forminquire fully replaced by TOML-driven typedialog with TTY wrappers.

New form registry (.typedialog/):

  • core/forms/auth-login.toml
  • core/forms/mfa-enroll.toml
  • core/forms/setup-wizard.toml
  • core/forms/infrastructure/server_delete_confirm.toml
  • core/forms/infrastructure/cluster_delete_confirm.toml
  • core/forms/infrastructure/taskserv_delete_confirm.toml
  • core/forms/infrastructure/generic_delete_confirm.toml
  • platform/forms/ai-service-form.toml (with Nickel fragment composition)
  • platform/forms/control-center-form.toml
  • platform/forms/extension-registry-form.toml

Documentation: .typedialog/README.md, platform/forms/README.md, platform/forms/fragments/README.md

Architecture doc: docs/src/architecture/config-loading-architecture.md


Nushell 0.110.0 Compatibility (2026-01-21)

  • Fixed try/catch syntax across .typedialog/platform/scripts/, bootstrap/install.nu, example deploy scripts
  • Updated external.nu, paths.nu, export-toml.nu for Nu 0.110.0 API changes
  • Removed stale session reports from .coder/
  • Relocated nickel-installation-guide.md into docs/src/setup/

2026-01-08 — Nickel IaC migration complete

Repository: provisioning (standalone, nickel branch) Changes: Nickel IaC migration complete — Legacy KCL and config cleanup


📋 Summary

Complete migration to Nickel-based infrastructure-as-code with consolidated configuration strategy. Legacy KCL schemas, deprecated config files, and redundant documentation removed. New project structure with .cargo/, .github/, and schema-driven configuration system.


📁 Changes by Directory

REMOVED (Legacy KCL Ecosystem)

  • config/ - Deprecated TOML configs (config.defaults.toml, kms.toml, plugins.toml, etc.)
  • config/cedar-policies/ - Legacy Cedar policies (moved to Nickel schemas)
  • config/templates/ - Old Jinja2 templates (replaced by Nickel generator/)
  • config/installer-examples/ - KCL-based examples
  • docs/src/ - Legacy documentation (full migration to provisioning/docs/src/)
  • kcl/ - Complete removal (all workspaces migrated to Nickel)
  • tools/kcl-packager.nu - KCL packaging system

ADDED (Nickel IaC & New Structure)

  • .cargo/ - Rust build configuration (clippy settings, rustfmt.toml)
  • .github/ - GitHub Actions CI/CD workflows
  • schemas/ - Nickel schema definitions (primary IaC format)
    • main.ncl, provider-aws.ncl, provider-local.ncl, provider-upcloud.ncl
    • Infrastructure, deployment, services, operations schemas
  • docs/src/architecture/adr/ - ADR updates for Nickel migration
    • adr-010-configuration-format-strategy.md
    • adr-011-nickel-migration.md
    • adr-012-nushell-nickel-plugin-cli-wrapper.md

📝 UPDATED (Core System)

  • provisioning/docs/src/ - Comprehensive product documentation

    • API reference, architecture, guides, operations, security, testing
    • Nickel configuration guide with examples
    • Migrated from legacy KCL documentation
  • core/ - Updated with Nickel integration

    • Scripts, plugins, CLI updated for Nickel schema parsing
  • justfiles/ - Added ci.just for Nickel-aware CI/CD

  • README.md - Complete restructure for Nickel-first approach

  • .gitignore - Updated to ignore Nickel build artifacts


📊 Change Statistics

Category Removed Added Modified
Configuration 50+ 10+ 3
Documentation 150+ 200+ 40+
Infrastructure 1 (kcl/) - -
Plugins 1 - 5+
Build System 5 8+ 3
Total ~220 files ~250 files 50+ files

⚠️ Breaking Changes

  1. KCL Sunset: All KCL infrastructure code removed. Migrate workspaces using nickel-kcl-bridge or rewrite directly in Nickel.
  2. Config Format: TOML configuration files moved to schema-driven Nickel system. Legacy config loading deprecated.
  3. Documentation: Old KCL/legacy docs removed. Use provisioning/docs/ for current product documentation.
  4. Plugin System: Updated to Nickel-aware plugin API. Legacy Nushell plugins require recompilation.

🔧 Migration Path

# For existing workspaces:
provisioning workspace migrate --from-kcl <workspace-name>

# For custom configs:
nickel eval --format json <your-config.ncl> | jq '.'

Key Features

  • Type-Safe: Nickel schemas eliminate silent config errors
  • Composable: Modular infrastructure definitions with lazy evaluation
  • Documented: Schema validation built-in, IDE support via LSP
  • Validated: All imports pre-checked, circular dependencies prevented
  • Bridge Available: nickel-kcl-bridge for gradual KCL→Nickel migration

📝 Implementation Details

Nickel Schema System

  • Three-tier architecture: infrastructure, operations, deployment
  • Lazy evaluation: Efficient resource binding and composition
  • Record merging: Clean override patterns without duplication
  • Type validation: LSP-aware with IDE auto-completion
  • Generator system: Nickel-based dynamic configuration at runtime

Documentation Reorganization

  • provisioning/docs/src/ (200+ files) - Customer-facing product docs
  • docs/src/ (20-30 files) - Architecture and development guidelines
  • .coder/ - Session files and implementation records
  • Separation of concerns: Product docs isolated from session artifacts

CI/CD Integration

  • GitHub Actions workflows for Rust, Nickel, Nushell
  • Automated schema validation pre-commit
  • Cross-platform testing (Linux, macOS)
  • Build artifact caching for fast iteration

⚠️ Compatibility Notes

Breaking: KCL workspaces require migration to Nickel. Use schema-aware tooling for validation.

Migration support: nickel-kcl-bridge tool and guides available in provisioning/docs/src/development/.

Legacy configs: Old TOML files no longer loaded. Migrate to Nickel schema format via CLI tool.


Status: Nickel migration complete. System is production-ready. Date: 2026-01-08 Branch: nickel


Last updated: 2026-05-12