code: clean-start baseline — orchestration spine (constellation materialization)

This commit is contained in:
Jesús Pérez 2026-07-09 21:56:19 +01:00
parent b1c8e38a61
commit a81bb46ff3
Signed by: jesus
GPG key ID: 9F243E355E0BC939
1496 changed files with 203958 additions and 22 deletions

37
.cargo/audit.toml Normal file
View file

@ -0,0 +1,37 @@
# Generated by dev-system/ci
# cargo-audit configuration for security vulnerability scanning
# Database configuration
[advisories]
# The database path
db-path = "~/.cargo/advisory-db"
# Advisory database URLs
db-urls = ["https://github.com/rustsec/advisory-db"]
# How to handle different kinds of advisories
# "allow" - Pass the check despite the warning
# "warn" - Pass the check but warn about the issue
# "deny" - Fail the check
deny = ["unmaintained", "unsound", "yanked"]
# Specific vulnerability IDs to ignore (in case of false positives)
# You can use: https://rustsec.org/
ignore = [
# Example: { id = "RUSTSEC-2023-XXXX", reason = "Not applicable to our use case" }
]
# How to handle vulnerabilities based on severity
[output]
# Deny on high severity vulnerabilities
deny = ["high", "critical"]
# Warn on medium severity vulnerabilities
warn = ["medium", "low"]
# Advisory format: "terminal", "json"
format = "terminal"
# Target configuration
[target]
# Check only specific targets
# Uncomment to restrict to specific target triples
# triple = "x86_64-unknown-linux-gnu"

84
.cargo/config.toml Normal file
View file

@ -0,0 +1,84 @@
# Generated by dev-system/ci
# Cargo configuration for build and compilation settings
[build]
# Number of parallel jobs for compilation
jobs = 4
# Code generation backend
# codegen-backend = "llvm"
[profile.dev]
# Development profile - fast compilation, debug info
debug = true
debug-assertions = true
incremental = true
lto = false
opt-level = 0
overflow-checks = true
panic = "unwind"
[profile.release]
# Release profile - slow compilation, optimized binary
codegen-units = 1
debug = false
debug-assertions = false
incremental = false
lto = "thin"
opt-level = 3
overflow-checks = false
panic = "abort"
strip = false
[profile.test]
# Test profile - inherits from dev but can be optimized
debug = true
debug-assertions = true
incremental = true
lto = false
opt-level = 1
overflow-checks = true
[profile.bench]
# Benchmark profile - same as release
codegen-units = 1
debug = false
debug-assertions = false
incremental = false
lto = "thin"
opt-level = 3
overflow-checks = false
[term]
# Terminal colors
color = "auto"
progress.when = "auto"
progress.width = 80
verbose = false
[net]
# Network settings
git-fetch-with-cli = true
offline = false
# Strict version requirements for dependencies
# force-non-semver-pre = true
[profile.ci-test]
# Pre-commit: no debug info, no incremental — shared .rlib between test + docs hooks
inherits = "test"
debug = 0
incremental = false
[profile.ci]
# CI pipeline: line-tables-only for actionable backtraces on remote failures
inherits = "test"
debug = "line-tables-only"
incremental = false
[alias]
# Custom cargo commands
build-all = "build --all-targets"
check-all = "check --all-targets --all-features"
doc-all = "doc --all-features --no-deps --open"
test-all = "test --all-features --workspace"

17
.clippy.toml Normal file
View file

@ -0,0 +1,17 @@
# Generated by dev-system/ci
# Clippy configuration for Rust linting
# Lint level thresholds
cognitive-complexity-threshold = 25
excessive-nesting-threshold = 5
type-complexity-threshold = 500
# Allowed patterns (prevent lints on specific code)
# allow-expect-in-tests = true
# allow-unwrap-in-tests = true
# Single-character variable name threshold
single-char-binding-names-threshold = 4
# Note: Lint configurations belong in Cargo.toml under [lints.clippy] or [workspace.lints.clippy]
# This file only contains clippy configuration parameters, not lint levels

9
.config/nextest.toml Normal file
View file

@ -0,0 +1,9 @@
[profile.ci-test]
# Pre-commit runs: fail fast, no retries — fast feedback loop.
fail-fast = true
retries = 0
[profile.ci]
# CI pipeline runs: collect all failures, one retry for flaky infra tests.
fail-fast = false
retries = 1

44
.env.local.template Normal file
View file

@ -0,0 +1,44 @@
# Provisioning Local Development Environment
# Copy this file to .env.local and customize as needed
# Service Ports
API_GATEWAY_PORT=8080
EXTENSION_REGISTRY_PORT=8082
ORCHESTRATOR_PORT=9090
ETCD_CLIENT_PORT=2379
ETCD_PEER_PORT=2380
PROMETHEUS_PORT=9091
# Paths
EXTENSIONS_PATH=./provisioning/extensions
SCHEMAS_PATH=./provisioning/schemas
WORKFLOWS_PATH=./provisioning/workflows
WORKSPACES_PATH=./workspaces
CONFIG_PATH=./provisioning/config
# Logging
LOG_LEVEL=info
# debug: Verbose logging with all details
# info: Normal operation logging
# warn: Only warnings and errors
# error: Only errors
# ETCD Configuration
ETCD_CLUSTER_TOKEN=provisioning-cluster
ETCD_SNAPSHOT_COUNT=10000
# Orchestrator Configuration
ORCHESTRATOR_WORKER_THREADS=4
ORCHESTRATOR_BATCH_TIMEOUT=300
# Docker Image Tags (should match build tags)
EXTENSION_REGISTRY_IMAGE=provisioning/catalog-registry:latest
ORCHESTRATOR_IMAGE=provisioning/orchestrator:latest
API_GATEWAY_IMAGE=provisioning/api-gateway:latest
# Development Mode
# Set to true to enable debug logging and hot-reload
DEV_MODE=false
# Enable Prometheus metrics collection
PROMETHEUS_ENABLED=true

116
.github/workflows/nickel-typecheck.yml vendored Normal file
View file

@ -0,0 +1,116 @@
# GitHub Actions Nickel Type Checking Workflow
# Generated by dev-system/ci
# Validates all Nickel schemas with nickel typecheck
name: Nickel Type Check
on:
push:
branches: [main, develop]
paths: ['**.ncl']
pull_request:
branches: [main]
paths: ['**.ncl']
jobs:
typecheck:
name: Nickel Type Checking
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nickel
run: |
#!/usr/bin/env bash
set -e
echo "📦 Installing Nickel..."
if command -v nickel &> /dev/null; then
echo "✓ Nickel already installed"
nickel --version
else
echo "Installing via homebrew..."
brew install nickel || {
echo "Homebrew installation failed, trying from source..."
curl --proto '=https' --tlsv1.2 -sSf https://install.nickel-lang.org | bash || exit 1
}
fi
nickel --version
- name: Setup environment
run: |
#!/usr/bin/env bash
# Set NICKEL_IMPORT_PATH for schema imports
export NICKEL_IMPORT_PATH="/Users/Akasha/Tools/dev-system/ci/schemas:/Users/Akasha/Tools/dev-system/ci/validators:/Users/Akasha/Tools/dev-system/ci/defaults"
echo "NICKEL_IMPORT_PATH=$NICKEL_IMPORT_PATH" >> $GITHUB_ENV
- name: Type check schemas
run: |
#!/usr/bin/env bash
set -e
echo "🔍 Type checking Nickel schemas..."
# Find all .ncl files
SCHEMAS=$(find . -name "*.ncl" -type f \
! -path "./target/*" \
! -path "./.git/*" \
! -path "./node_modules/*" \
| sort)
if [ -z "$SCHEMAS" ]; then
echo "⚠️ No Nickel schemas found"
exit 0
fi
FAILED=0
PASSED=0
# Set import path
export NICKEL_IMPORT_PATH="/Users/Akasha/Tools/dev-system/ci/schemas:/Users/Akasha/Tools/dev-system/ci/validators:/Users/Akasha/Tools/dev-system/ci/defaults:."
for schema in $SCHEMAS; do
echo "Checking: $schema"
if nickel typecheck "$schema" > /dev/null 2>&1; then
echo " ✓ Valid"
((PASSED++))
else
echo " ❌ Type error"
nickel typecheck "$schema" || true
((FAILED++))
fi
done
echo ""
echo "Summary: $PASSED passed, $FAILED failed"
if [ $FAILED -gt 0 ]; then
exit 1
fi
- name: Export and validate
run: |
#!/usr/bin/env bash
set -e
echo "📊 Exporting Nickel configurations..."
export NICKEL_IMPORT_PATH="/Users/Akasha/Tools/dev-system/ci/schemas:/Users/Akasha/Tools/dev-system/ci/validators:/Users/Akasha/Tools/dev-system/ci/defaults:."
# Export main configs if they exist
if [ -f ".typedialog/ci/schemas/ci-local.ncl" ]; then
echo "Exporting CI config..."
nickel export .typedialog/ci/schemas/ci-local.ncl > /tmp/ci-export.json
if [ $? -eq 0 ]; then
echo " ✓ Successfully exported"
else
echo " ❌ Export failed"
exit 1
fi
fi
echo "✓ All exports successful"

28
.github/workflows/nushell-lint.yml vendored Normal file
View file

@ -0,0 +1,28 @@
jobs:
validate:
name: Nushell IDE Check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nushell
uses: hustcer/setup-nu@v3
with:
version: latest
- name: Validate scripts
run: find . -name '*.nu' -type f -exec nu --ide-check 100 {} \; 2>/dev/null | grep -E '^(Error|error)' && exit 1 || true
- name: Check formatting
run: echo 'NuShell validation passed'
name: Nushell Validation
on:
pull_request:
branches:
- main
paths:
- '**.nu'
push:
branches:
- main
- develop
paths:
- '**.nu'

47
.github/workflows/rust-ci.yml vendored Normal file
View file

@ -0,0 +1,47 @@
jobs:
audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Audit
run: cargo audit --deny warnings
- name: Deny Check
run: cargo deny check licenses advisories
check:
name: Check + Test + Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust-version }}
- name: Cache
uses: Swatinem/rust-cache@v2
- name: Check
run: cargo check --all-targets
- name: Format Check
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Tests
run: cargo test --workspace
strategy:
matrix:
rust-version:
- stable
- nightly
name: Rust CI
on:
pull_request:
branches:
- main
push:
branches:
- main
- develop

23
.gitignore vendored
View file

@ -1,3 +1,4 @@
.internal.git/
# ============================================================================ # ============================================================================
# Provisioning Repository .gitignore Model # Provisioning Repository .gitignore Model
# Purpose: Track core system & platform, exclude catalog & runtime data # Purpose: Track core system & platform, exclude catalog & runtime data
@ -11,11 +12,6 @@ rollback_instructions*
/book /book
# === SEPARATE REPOSITORIES ===
# These are tracked in their own repos or pulled from external sources
catalog/
core/plugins/nushell-plugins/
# === USER WORKSPACE DATA === # === USER WORKSPACE DATA ===
# User-specific data, should never be committed # User-specific data, should never be committed
# NOTE: provisioning/workspace/ contains system templates and SHOULD be tracked # NOTE: provisioning/workspace/ contains system templates and SHOULD be tracked
@ -31,13 +27,6 @@ OLD/
*.log *.log
logs/ logs/
# Platform service runtime data
platform/orchestrator/data/*.json
platform/orchestrator/data/tasks/**
platform/control-center/data/
platform/api-gateway/data/
platform/mcp-server/data/
# Keep .gitkeep files for directory structure # Keep .gitkeep files for directory structure
!**/data/.gitkeep !**/data/.gitkeep
@ -91,10 +80,6 @@ config.*back
!config.example.toml !config.example.toml
!config.toml.example !config.toml.example
# Platform service configs (user overrides)
platform/*/.env.local
platform/*/config.local.*
# === GENERATED & CACHED FILES === # === GENERATED & CACHED FILES ===
# KCL cache # KCL cache
**/.kcl_cache/ **/.kcl_cache/
@ -145,12 +130,6 @@ npm-debug.log
yarn-error.log yarn-error.log
.pnpm-debug.log .pnpm-debug.log
# Frontend build outputs
platform/*/dist/
platform/*/build/
platform/*/.next/
platform/*/.nuxt/
# === DOCUMENTATION BUILD OUTPUTS === # === DOCUMENTATION BUILD OUTPUTS ===
book/ book/
book-output/ book-output/

103
.markdownlint-cli2.jsonc Normal file
View file

@ -0,0 +1,103 @@
// Markdownlint-cli2 Configuration
// Documentation quality enforcement aligned with CLAUDE.md guidelines
// See: https://github.com/igorshubovych/markdownlint-cli2
{
"config": {
"default": true,
// Headings - enforce proper hierarchy
"MD001": false, // heading-increment (relaxed - allow flexibility)
"MD026": { "punctuation": ".,;:!?" }, // heading-punctuation
// Lists - enforce consistency
"MD004": { "style": "consistent" }, // ul-style (consistent list markers)
"MD005": false, // inconsistent-indentation (relaxed)
"MD007": { "indent": 2 }, // ul-indent
"MD029": false, // ol-prefix (allow flexible list numbering)
"MD030": { "ul_single": 1, "ol_single": 1, "ul_multi": 1, "ol_multi": 1 },
// Code blocks - fenced only
"MD046": { "style": "fenced" }, // code-block-style
// CRITICAL: MD040 only checks for missing language on opening fence.
// It does NOT catch malformed closing fences with language specifiers (e.g., ```plaintext).
// This is a CommonMark violation that must be caught by custom pre-commit hook.
// Pre-commit hook: check-malformed-fences (provisioning/core/.pre-commit-config.yaml)
// Script: provisioning/scripts/check-malformed-fences.nu
// Formatting - strict whitespace
"MD009": true, // no-hard-tabs
"MD010": true, // hard-tabs
"MD011": true, // reversed-link-syntax
"MD018": true, // no-missing-space-atx
"MD019": true, // no-multiple-space-atx
"MD020": true, // no-missing-space-closed-atx
"MD021": true, // no-multiple-space-closed-atx
"MD023": true, // heading-starts-line
"MD027": true, // no-multiple-spaces-blockquote
"MD037": true, // no-space-in-emphasis
"MD039": true, // no-space-in-links
// Trailing content
"MD012": false, // no-multiple-blanks (relaxed - allow formatting space)
"MD024": false, // no-duplicate-heading (too strict for docs)
"MD028": false, // no-blanks-blockquote (relaxed)
"MD031": false, // blanks-around-fences (too strict for technical docs)
"MD047": true, // single-trailing-newline
// Links and references
"MD034": true, // no-bare-urls (links must be formatted)
"MD040": true, // fenced-code-language (code blocks need language)
"MD042": true, // no-empty-links
// HTML - allow for documentation formatting and images
"MD033": { "allowed_elements": ["br", "hr", "details", "summary", "p", "img"] },
// Line length - relaxed for technical documentation
"MD013": {
"line_length": 150,
"heading_line_length": 150,
"code_block_line_length": 150,
"code_blocks": true,
"tables": true,
"headers": true,
"headers_line_length": 150,
"strict": false,
"stern": false
},
// Images
"MD045": true, // image-alt-text
// Disable rules that conflict with relaxed style
"MD003": false, // consistent-indentation
"MD041": false, // first-line-heading
"MD025": false, // single-h1 / multiple-top-level-headings
"MD022": false, // blanks-around-headings (flexible spacing)
"MD032": false, // blanks-around-lists (flexible spacing)
"MD035": false, // hr-style (consistent)
"MD036": false, // no-emphasis-as-heading
"MD044": false, // proper-names
"MD060": true // table-column-style (enforce proper table formatting)
},
// Documentation patterns
"globs": [
"docs/**/*.md",
"!docs/node_modules/**",
"!docs/build/**"
],
// Ignore build artifacts, external content, and operational directories
"ignores": [
"node_modules/**",
"target/**",
".git/**",
"build/**",
"dist/**",
".coder/**",
".claude/**",
".wrks/**",
".vale/**"
]
}

View file

@ -0,0 +1,8 @@
{
"kubernetes": {
"cmd_task": "install",
"major_version": "1.31",
"name": "kubernetes",
"version": "1.31.0"
}
}

View file

@ -0,0 +1,163 @@
{
"alternatives_considered": [
{
"option": "Single binary with feature flags for CLI/HTTP/MCP surfaces",
"why_rejected": "stdio hijack (MCP) and persistent HTTP server are incompatible runtime modes in one process without complex flag matrices. The feature-flag model also bloats binary size — every CLI user ships the full HTTP server. The separate-binary model with shared library gives the same code-reuse guarantee without the runtime coupling."
},
{
"option": "Ship only the daemon — CLI becomes a thin HTTP client",
"why_rejected": "The user's current workflow is CLI-first and offline-first. Requiring a daemon would regress the unsurprising property that `provisioning workspace list` works with no running services. Autonomy was listed as irrenunciable in the A0 decisions."
},
{
"option": "Keep mcp-server and CLI as independent codebases, add the daemon as a third",
"why_rejected": "Sync irrenunciable fails. Every new operation would need implementation in three places, and divergence was already observable (parameter shape mismatches between MCP tools and CLI handlers). Adding a third surface would multiply drift rather than fix it."
},
{
"option": "Use MCP stdio as the 'backend' — HTTP daemon and CLI would invoke MCP internally",
"why_rejected": "MCP is a client-server protocol designed for stdin/stdout framing. Using it as an internal backend forces the HTTP daemon to spawn and manage an MCP subprocess for every request — adding latency and serialisation overhead — and couples the daemon's availability to MCP protocol versioning. A shared library avoids both issues."
},
{
"option": "Use ontoref-ontology crate as the ontology source for provisioning-daemon",
"why_rejected": "Compile-time dependency on ontoref would force coordinated releases and embed ontoref's SurrealDB+schema choices into provisioning's build. The `domain_daemon` config hook achieves delegation with no crate coupling — provisioning owns its domain ontology; ontoref-daemon discovers and delegates at runtime."
}
],
"consequences": {
"negative": [
"The Nushell legacy branch (tier 3) must be maintained until every handler is migrated to the fallback chain — currently only `workspace list` is wired; the other 36 operations still call Nushell legacy directly",
"Adding a tool now requires Rust compilation — faster iteration is lost versus the previous 'edit a Nushell file, reload' pattern. Mitigated by `cargo watch -x 'build -p provisioning-daemon'` during development",
"The fallback chain incurs up to two failed probes (daemon ping + `which provisioning-tool`) before falling through to tier 3 on cold offline use. Latency measured at ~50ms on macOS — acceptable but not zero",
"G3 can only assert semantic equivalence on payloads it can normalise. Fields not listed in `normalise()` (trace_id/timestamp/etc.) could still mask real divergence if an unknown volatile field is introduced. Mitigated by reviewing the normaliser when any new metadata field is added",
"The mcp-server binary `provisioning-mcp-server` still exists alongside `prov-mcp` (the new Registry-backed binary) during migration. Users must be told which to use"
],
"positive": [
"Adding a new operation is a single `impl Tool` in provisioning-core — it appears in all three surfaces at once without surface-specific code",
"The admin UI is unblocked: it calls the same HTTP API the CLI uses, consuming the same Registry responses",
"MCP stdio and HTTP daemon can be deployed or disabled independently without affecting the CLI's offline workflow",
"G3 contract test catches silent drift at CI time instead of production",
"Schema is generated once by `Tool::schema()` and consumed by tools/list (MCP), GET /api/v1/tools (HTTP), and `provisioning-tool schema <name>` (CLI) — no duplicate JSON Schema files",
"`--fmt text|json|yaml|toml|md` and `--clip` global CLI flags replace the scattered `--format`, `--output`, `--json` per-handler options"
]
},
"constraints": [
{
"check": {
"must_be_empty": true,
"paths": [
"platform/crates/provisioning-tool/src",
"platform/crates/provisioning-daemon/src",
"platform/crates/mcp-server/src"
],
"pattern": "Tool::invoke|tool\\.invoke\\(",
"tag": "Grep"
},
"claim": "All three surfaces (CLI via provisioning-tool, HTTP via provisioning-daemon, MCP via mcp-server) must invoke operations through Registry::invoke — no surface may bypass the Registry with direct tool instantiation",
"id": "registry-sole-dispatch-path",
"rationale": "A surface that bypasses the Registry makes the G3 contract test meaningless for that operation because the shared dispatch path is not exercised. Enforcing Registry::invoke keeps the three surfaces contractually equivalent.",
"scope": "platform/crates/provisioning-tool, platform/crates/provisioning-daemon, platform/crates/mcp-server",
"severity": "Hard"
},
{
"check": {
"cmd": "cargo test -p contract-tests --manifest-path platform/Cargo.toml",
"expect_exit": 0,
"tag": "NuCmd"
},
"claim": "The contract-tests crate must pass with 5 tests: listing agreement, echo agreement, invalid-param error agreement, failing-tool error agreement, and tools/list count agreement",
"id": "g3-contract-test-must-pass",
"rationale": "G3 is the mechanism that converts sync-irrenunciable into an architectural invariant. A failing G3 means one surface has silently diverged from the others.",
"scope": "platform/crates/contract-tests",
"severity": "Hard"
},
{
"check": {
"must_be_empty": false,
"paths": [
"provisioning/core/nulib/domain"
],
"pattern": "tool-call|tool-list",
"tag": "Grep"
},
"claim": "Every call to tool-call / tool-list in Nushell must pass an explicit legacy closure — not a stub, not an error, but a working Nushell-native implementation",
"id": "nushell-fallback-legacy-closure-required",
"rationale": "Tier 3 is the offline-first guarantee. If the legacy closure errors or is empty, the fallback chain breaks when the daemon is down and provisioning-tool is not installed. This is the retirement gate: tier 3 can only be removed per-operation after G3 passes for that operation.",
"scope": "provisioning/core/nulib/domain",
"severity": "Hard"
},
{
"check": {
"must_be_empty": false,
"paths": [
"platform/crates/mcp-server/src/registry_server.rs"
],
"pattern": "pub async fn handle_request",
"tag": "Grep"
},
"claim": "McpServer must expose `pub async fn handle_request(Value) -> Value` — the in-process entry point used by G3 contract tests",
"id": "mcp-dispatch-exposed-via-handle-request",
"rationale": "Without handle_request the G3 MCP tier would require spawning a subprocess with pipes — brittle under concurrent test execution. Keeping handle_request public is a testability contract.",
"scope": "platform/crates/mcp-server/src/registry_server.rs",
"severity": "Hard"
},
{
"check": {
"must_be_empty": true,
"paths": [
"provisioning/platform/crates/provisioning-core",
"provisioning/platform/crates/provisioning-daemon"
],
"pattern": "ontoref-ontology|ontoref-derive",
"tag": "Grep"
},
"claim": "provisioning workspace Cargo.toml must not contain ontoref-* path dependencies or the `ai` feature flag enabling them at the workspace level",
"id": "ontoref-zero-crate-dependency",
"rationale": "Coupling to ontoref crates inverts the delegation model: the decision is that provisioning's .ontoref/config.ncl declares a domain_daemon hook, and ontoref-daemon discovers it. provisioning must not import ontoref.",
"scope": "provisioning/platform/Cargo.toml, provisioning/platform/crates/provisioning-core/Cargo.toml, provisioning/platform/crates/provisioning-daemon/Cargo.toml",
"severity": "Soft"
}
],
"context": "Before this decision the provisioning platform exposed three user-facing surfaces — the Nushell CLI (`provisioning ...`), the MCP stdio server (`crates/mcp-server`), and the future admin HTTP UI — as three independent codebases. Each had its own dispatch logic, its own parameter validation, and its own response formatting. A single operation like `workspace list` was implemented once in Nushell for the CLI and once as a `simple_main.rs` MCP tool with separate logic. The admin UI was pending because there was no shared backend it could consume. This divergence was already causing drift: `provision_cluster_create` in MCP accepted a different parameter shape than `provisioning cluster create` in the CLI, and neither agreed with the orchestrator's HTTP POST body. The user's irrenunciable requirement was ontoref-style synchronization — one operation, one semantics, three surfaces — without forcing any surface to depend on the others (CLI must work offline; MCP stdio must not require an HTTP daemon; admin UI must not embed the CLI).",
"date": "2026-04-19",
"decision": "Introduce a four-crate layered architecture: (1) `provisioning-core` is a pure library exposing the `Tool` trait and `Registry`; all 37 operations are implemented as `impl Tool` inside it. (2) `provisioning-tool` is a thin CLI binary that instantiates the Registry and exposes `list`/`schema`/`invoke` over stdout JSON. (3) `provisioning-daemon` is an Axum HTTP+NATS server that wraps the Registry with JWT+RBAC middleware, domain-state tracking, a config-file watcher, an embedded admin UI, and Tera ontology templates. (4) `mcp-server` is reimplemented internally as a JSON-RPC 2.0 dispatcher over the same Registry, consumed via `McpServer::handle_request` for in-process tests and via stdin/stdout for the MCP protocol. The Nushell CLI uses a three-tier fallback chain (`platform/clients/fallback.nu::tool-call`): tier 1 is the HTTP daemon if reachable; tier 2 is the `provisioning-tool` child process; tier 3 is the caller-supplied Nushell legacy closure. A G3 contract test (`crates/contract-tests`) asserts that the same tool invoked through all three surfaces produces semantically equivalent payloads after envelope normalisation and validates each tier's output against a shared JSON Schema. An `.ontoref/config.ncl` hook (`domain_daemon`) declares provisioning as an external domain so ontoref-daemon can delegate `provisioning.*` ontology queries without provisioning importing any ontoref crates.",
"id": "adr-029",
"ontology_check": {
"decision_string": "Unify CLI+HTTP+MCP surfaces on a shared provisioning-core Registry with a three-tier fallback in Nushell, JWT+RBAC middleware only at the HTTP layer, G3 contract test asserting semantic parity, and ontoref federation via config hook instead of crate dependency",
"invariants_at_risk": [
"config-driven-always",
"type-safety-always",
"solid-boundaries"
],
"verdict": "Safe"
},
"rationale": [
{
"claim": "A shared Rust library is the only architecture that gives autonomy + sync simultaneously",
"detail": "The three surfaces have incompatible runtime models: the CLI can run without any long-running process, MCP stdio cannot share a process with an HTTP server (stdio hijacks stdin/stdout), and the admin UI requires a persistent backend. A shared service (daemon-only) forces the CLI to depend on the daemon — breaks autonomy. A shared protocol (REST-only) forces MCP to wrap HTTP — breaks stdio's contract. A shared library is the only option where each surface instantiates Registry independently and dispatches identically. Autonomy is structural; sync is guaranteed by construction because the dispatch code is literally the same function call."
},
{
"claim": "The three-tier fallback keeps CLI hardcoded offline-first",
"detail": "The user's current workflow is `provisioning workspace list` on a laptop with no daemon running. Tier 3 (Nushell legacy closure) preserves that behavior indefinitely. Tier 1 (HTTP daemon) opportunistically accelerates when the daemon is up — lets multi-developer setups cache Registry state. Tier 2 (provisioning-tool child) is the bridge: it reuses the Rust Registry but spawns a fresh process, so operations don't require a daemon yet also don't reimplement logic in Nushell. The chain is checked at call time, not configuration time, so the user never manages daemon state — it either works faster or it works the same."
},
{
"claim": "G3 contract test converts 'sync irrenunciable' into a CI invariant",
"detail": "Without G3, the three surfaces would drift silently as new tools are added. G3 asserts that for each fixture tool, all three tiers produce the same normalised payload and the same error code. This is structural: the test doesn't know which tier is 'right' — it knows they must agree. If a future change to the HTTP envelope breaks parity with MCP, CI fails. If a new error variant is added to ToolError but not mapped in `routes.rs::tool_error_code` or `registry_server.rs::tool_error_to_rpc`, the G3 error-code tests catch it. The contract cost is one integration test crate; the insurance is architectural."
},
{
"claim": "Ontoref federation via config hook, not crate dependency",
"detail": "Earlier plan revisions had provisioning-daemon depending on `ontoref-ontology` and `ontoref-derive` crates. This would force provisioning's release cadence onto ontoref's and vice versa. The `domain_daemon` config hook in `.ontoref/config.ncl` inverts the dependency: provisioning declares its HTTP URL and ontology endpoints; ontoref-daemon reads this config and delegates. provisioning has zero compile-time ontoref deps. The coupling is runtime, one-directional, and can be disabled by setting `domain_daemon.required = false` (the default)."
},
{
"claim": "37 tools, not 45+ as originally planned",
"detail": "A0 inventory revealed 37 actual tools in mcp-server (7 provision_*, 5 guidance_*, 7 installer_*, 17 legacy infra, 1 ai_query). The remaining 'tools' counted in early plans were enum values for taskservs (cicd, coredns, grafana…), not operations. Renaming to `<domain>_<action>` (workspace_list, server_create, dag_show) preserves the 37 operations under cleaner names since no external MCP consumers exist yet."
}
],
"related_adrs": [
"adr-014-solid-enforcement",
"adr-022-ncl-sync-daemon",
"adr-025-unified-lazy-loading",
"adr-026-nulib-restructure",
"adr-027-prvng-cli-daemon",
"adr-028-daemon-target-registry-field"
],
"status": "Accepted",
"title": "Smart Interface Unification: CLI ↔ HTTP ↔ MCP via Shared Registry"
}

View file

@ -0,0 +1,12 @@
{
"dependencies": [],
"name": "runc",
"version": {
"check_latest": true,
"current": "1.1.15",
"grace_period": 86400,
"site": "",
"source": "github.com/opencontainers/runc",
"tags": ""
}
}

View file

@ -0,0 +1,44 @@
{
"DefaultLianBuild": {
"context_src": "",
"ctx_type": "plain",
"image": "",
"lian_build_root": "/Users/Akasha/Development/lian-build",
"registry": {
"credential_path": "infra/libre-wuji/secrets/registry-push.sops.yaml",
"endpoint": "reg.librecloud.online"
},
"secrets_base": "/Users/Akasha/Development/project-provisioning/workspaces/libre-wuji",
"ssh_key": "/Users/Akasha/Development/.ssh/lian-build-runner",
"workspace": ""
},
"LianBuild": {
"context_src": "",
"ctx_type": "plain",
"image": "",
"lian_build_root": "/Users/Akasha/Development/lian-build",
"registry": {
"credential_path": "infra/libre-wuji/secrets/registry-push.sops.yaml",
"endpoint": "reg.librecloud.online"
},
"secrets_base": "/Users/Akasha/Development/project-provisioning/workspaces/libre-wuji",
"ssh_key": "/Users/Akasha/Development/.ssh/lian-build-runner",
"workspace": ""
},
"Version": "1.0.0",
"defaults": {
"lian_build": {
"context_src": "",
"ctx_type": "plain",
"image": "",
"lian_build_root": "/Users/Akasha/Development/lian-build",
"registry": {
"credential_path": "infra/libre-wuji/secrets/registry-push.sops.yaml",
"endpoint": "reg.librecloud.online"
},
"secrets_base": "/Users/Akasha/Development/project-provisioning/workspaces/libre-wuji",
"ssh_key": "/Users/Akasha/Development/.ssh/lian-build-runner",
"workspace": ""
}
}
}

View file

@ -0,0 +1,62 @@
{
"kubernetes": {
"addons": "",
"admin_user": "root",
"auth_mode": "Node,RBAC",
"bind_port": 6443,
"cert_sans": [
"{{hostname}}",
"{{cluster_name}}",
"127.0.0.1"
],
"certs_dir": "/etc/kubernetes/pki",
"cluster_name": "k8s-cluster",
"cmd_task": "install",
"cni": "cilium",
"cni_version": "",
"cp_ip": null,
"cp_name": "controlplane",
"cri": "crio",
"dns_domain": "cluster.local",
"etcd_ca_path": "/etc/kubernetes/pki/etcd/ca.crt",
"etcd_cert_path": "/etc/kubernetes/pki/apiserver-etcd-client.crt",
"etcd_certs_path": "etcd_certs",
"etcd_cluster_name": "",
"etcd_endpoints": [],
"etcd_key_path": "/etc/kubernetes/pki/apiserver-etcd-client.key",
"etcd_mode": "stacked",
"etcd_peers": "",
"etcd_prefix": "",
"external_ips": [],
"hostname": "k8s-node",
"install_log_path": "/tmp/k8s.log",
"ip": "10.0.0.1",
"major_version": "1.35",
"mode": "controlplane",
"name": "kubernetes",
"operations": {
"delete": true,
"health": true,
"install": true,
"reinstall": true
},
"pod_net": "10.244.0.0/16",
"prov_etcd_path": "etcdcerts",
"pull_policy": "IfNotPresent",
"repo": "registry.k8s.io",
"runtime_default": "crun",
"runtimes": "crun,runc",
"service_net": "10.96.0.0/12",
"skip_phases": [],
"sysctl": {
"disable_ipv6": false
},
"taint_node": true,
"taints_effect": "PreferNoSchedule",
"target_path": "/opt/kubernetes",
"timeout_cp": "4m0s",
"tpl": "kubeadm-config.yaml.j2",
"version": "1.35.3",
"work_path": "/opt/kubernetes/$cluster_name"
}
}

View file

@ -0,0 +1,54 @@
{
"DefaultCoreDNS": {
"cmd_task": "install",
"domains_search": "",
"entries": [
{
"domain": ".",
"etcd_cluster_name": "",
"file": null,
"forward": {
"forward_ip": null,
"source": "."
},
"port": 53,
"records": [],
"use_cache": true,
"use_errors": true,
"use_log": true
}
],
"etc_corefile": "/etc/coredns/Corefile",
"hostname": "dns-server",
"name": "coredns",
"nameservers": [],
"version": "1.14.2"
},
"defaults": {
"coredns": {
"cmd_task": "install",
"domains_search": "",
"entries": [
{
"domain": ".",
"etcd_cluster_name": "",
"file": null,
"forward": {
"forward_ip": null,
"source": "."
},
"port": 53,
"records": [],
"use_cache": true,
"use_errors": true,
"use_log": true
}
],
"etc_corefile": "/etc/coredns/Corefile",
"hostname": "dns-server",
"name": "coredns",
"nameservers": [],
"version": "1.14.2"
}
}
}

View file

@ -0,0 +1,3 @@
{
"name": "web"
}

View file

@ -0,0 +1,21 @@
{
"artifact": {
"description": "Build cache access policy. Declares which cache namespaces a mode may read, write, or append-only access, enabling CI/session namespace isolation for build caches stored in an OCI registry.",
"id": "cache-management",
"layers": [
{
"description": "contract.ncl — CacheManagementContext, CacheEntry, CacheAccessMode",
"media_type": "application/vnd.ontoref.domain.contract.v1",
"required": true
},
{
"description": "example.json — sample context for a lian-build CI run",
"media_type": "application/vnd.ontoref.domain.example.v1",
"required": false
}
],
"media_type": "application/vnd.ontoref.domain.v1",
"uses_registry": "primary",
"version": "0.1.0"
}
}

View file

@ -0,0 +1,38 @@
{
"DefaultServerDefaults": {
"fix_local_hosts": true,
"installer_user": "${user}",
"labels": "",
"lock": false,
"network_public_ipv4": true,
"network_public_ipv6": false,
"network_utility_ipv4": true,
"network_utility_ipv6": false,
"running_timeout": 200,
"running_wait": 10,
"storage_os_find": "name: debian-12 | arch: x86_64",
"time_zone": "UTC",
"user": "",
"user_home": "/home/${user}",
"user_ssh_port": 22
},
"defaults": {
"server_defaults": {
"fix_local_hosts": true,
"installer_user": "${user}",
"labels": "",
"lock": false,
"network_public_ipv4": true,
"network_public_ipv6": false,
"network_utility_ipv4": true,
"network_utility_ipv6": false,
"running_timeout": 200,
"running_wait": 10,
"storage_os_find": "name: debian-12 | arch: x86_64",
"time_zone": "UTC",
"user": "",
"user_home": "/home/${user}",
"user_ssh_port": 22
}
}
}

View file

@ -0,0 +1,12 @@
{
"dependencies": [],
"name": "youki",
"version": {
"check_latest": true,
"current": "0.6.0",
"grace_period": 86400,
"site": "https://github.com/containers/youki",
"source": "https://github.com/containers/youki/releases",
"tags": "https://github.com/containers/youki/tags"
}
}

View file

@ -0,0 +1,12 @@
{
"DefaultLinkerd": {
"name": "linkerd",
"version": "2.16.0"
},
"defaults": {
"linkerd": {
"name": "linkerd",
"version": "2.16.0"
}
}
}

View file

@ -0,0 +1,88 @@
{
"AWSConfig": {
"credentials": {},
"region": "us-east-1",
"timeout_seconds": 30
},
"ControlCenterConfig": {
"database": {},
"enabled": true,
"port": 3000,
"timeout_seconds": 30,
"url": "http://localhost:3000"
},
"DatabaseConfig": {
"backend": "memory"
},
"HetznerConfig": {
"api_url": "https://api.hetzner.cloud/v1",
"credentials": {},
"timeout_seconds": 30
},
"KMSConfig": {
"backend": "age",
"enabled": true,
"rotation_days": 90
},
"LocalConfig": {
"base_path": "/tmp/provisioning-local",
"timeout_seconds": 10
},
"OrchestratorConfig": {
"enabled": true,
"endpoint": "http://localhost:9090",
"health_check_interval_seconds": 5,
"port": 9090,
"timeout_seconds": 30
},
"PlatformServicesConfig": {
"control_center": {},
"kms_service": {},
"orchestrator": {}
},
"ProviderConfig": {},
"ProviderCredentialsReference": {
"credentials_source": "",
"credentials_source_type": "rustyvault"
},
"RustyVaultBootstrap": {
"encrypted_key_format": "age",
"encrypted_key_path": ""
},
"SystemConfig": {
"cache_base_path": "/var/cache/provisioning",
"config_base_path": "/etc/provisioning",
"cpu_count": 8,
"disk_total_gb": 500,
"install_path": "/opt/provisioning",
"memory_total_gb": 32,
"os_name": "linux",
"os_version": "5.15.0",
"setup_by_user": "provisioning",
"setup_date": "2025-12-11T00:00:00Z",
"setup_hostname": "provisioning-host",
"system_architecture": "x86_64",
"version": "1.0.0",
"workspaces_dir": "/opt/workspaces"
},
"UpCloudConfig": {
"api_url": "https://api.upcloud.com/1.3",
"credentials": {},
"interface": "API",
"timeout_seconds": 30
},
"UserPreferences": {
"auto_confirm_operations": false,
"default_timeout_seconds": 300,
"log_level": "info",
"preferred_editor": "vim",
"preferred_output_format": "text"
},
"WorkspaceConfig": {
"active_infrastructure": "",
"active_providers": [],
"provider_config": {},
"workspace_name": "",
"workspace_path": ""
}
}

View file

@ -0,0 +1,19 @@
{
"provisioning_daemon": {
"log_level": "info",
"nats_url": "nats://127.0.0.1:4222",
"nickel_import_path": "/Users/Akasha/Development/ontoref",
"ontology_templates": "/Users/Akasha/Development/project-provisioning/provisioning/platform/crates/provisioning-daemon/ontology_templates",
"orchestrator_url": "http://localhost:9011",
"project_name": "provisioning",
"project_root": "/Users/Akasha/Development/project-provisioning/provisioning",
"provisioning_bin": "provisioning",
"server": {
"host": "0.0.0.0",
"port": 9014
},
"ui_templates_dir": "/Users/Akasha/Development/project-provisioning/provisioning/platform/crates/provisioning-daemon/ui/templates",
"watch_paths": [],
"workspaces_root": "/Users/Akasha/Development/project-provisioning/workspaces"
}
}

View file

@ -0,0 +1,157 @@
{
"Containerd": {
"cmd_task": "install",
"concerns": {
"backup": {
"kind": "disabled",
"reason": "stateless: configuration in git, no runtime data to capture"
},
"certs": {
"kind": "disabled",
"reason": "no ACME issuer required"
},
"dns": {
"kind": "disabled",
"reason": "no DNS records owned by this component"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "disabled",
"reason": "no TLS termination at this layer"
}
},
"live_check": {
"aggregate": "all_must_pass",
"scope": "all_servers",
"service": "containerd",
"strategy": "systemd"
},
"mode": "taskserv",
"name": "containerd",
"operations": {
"delete": false,
"health": true,
"install": true,
"reinstall": true
},
"runtimes": "runc",
"version": "1.7.24"
},
"DefaultContainerd": {
"cmd_task": "install",
"concerns": {
"backup": {
"kind": "disabled",
"reason": "stateless: configuration in git, no runtime data to capture"
},
"certs": {
"kind": "disabled",
"reason": "no ACME issuer required"
},
"dns": {
"kind": "disabled",
"reason": "no DNS records owned by this component"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "disabled",
"reason": "no TLS termination at this layer"
}
},
"live_check": {
"aggregate": "all_must_pass",
"scope": "all_servers",
"service": "containerd",
"strategy": "systemd"
},
"mode": "taskserv",
"name": "containerd",
"operations": {
"delete": false,
"health": true,
"install": true,
"reinstall": true
},
"runtimes": "runc",
"version": "1.7.24"
},
"Version": {
"dependencies": [],
"name": "containerd",
"version": {
"check_latest": true,
"current": "1.7.24",
"grace_period": 86400,
"site": "containerd.io",
"source": "github.com/containerd/containerd",
"tags": ""
}
},
"defaults": {
"containerd": {
"cmd_task": "install",
"concerns": {
"backup": {
"kind": "disabled",
"reason": "stateless: configuration in git, no runtime data to capture"
},
"certs": {
"kind": "disabled",
"reason": "no ACME issuer required"
},
"dns": {
"kind": "disabled",
"reason": "no DNS records owned by this component"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "disabled",
"reason": "no TLS termination at this layer"
}
},
"live_check": {
"aggregate": "all_must_pass",
"scope": "all_servers",
"service": "containerd",
"strategy": "systemd"
},
"mode": "taskserv",
"name": "containerd",
"operations": {
"delete": false,
"health": true,
"install": true,
"reinstall": true
},
"runtimes": "runc",
"version": "1.7.24"
}
}
}

View file

@ -0,0 +1,80 @@
{
"zot": {
"concerns": {
"backup": {
"backlog_ref": "BACKUP-REGISTRY-001",
"kind": "pending",
"reason": "BackupPolicy declared at workspace level (registry-stack BackupGroup)"
},
"certs": {
"certs_impl": {
"acme_server": "https://acme-v02.api.letsencrypt.org/directory",
"email": "",
"provider": "cloudflare",
"secret_ref": ""
},
"kind": "enabled"
},
"dns": {
"dns_impl": {
"internal": [],
"zone": ""
},
"kind": "enabled"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "enabled",
"tls_impl": {
"hostnames": [],
"issuer_ref": "letsencrypt-prod",
"secret_name": "zot-tls"
}
}
},
"data_dir": "/var/lib/data/zot",
"image": "ghcr.io/project-zot/zot-linux-arm64:latest",
"live_check": {
"scope": "cp_only",
"strategy": "k8s_pods"
},
"mode": "cluster",
"name": "zot",
"namespace": "data",
"operations": {
"delete": true,
"health": true,
"install": true,
"update": true
},
"port": 5000,
"provides": {
"port": 5000,
"service": "zot"
},
"requires": {
"credentials": [],
"ports": [
{
"exposure": "private",
"port": 5000
}
],
"storage": {
"persistent": true,
"size": "50Gi"
}
},
"storage_class": "nfs-shared",
"version": "latest"
}
}

View file

@ -0,0 +1,24 @@
{
"arch_support": [
"x86_64",
"aarch64",
"mips",
"powerpc",
"sparc"
],
"capabilities": [
"emulation",
"virtualization",
"cross-arch"
],
"category": "hypervisor",
"description": "QEMU processor emulation and machine emulation platform",
"name": "qemu",
"platform_support": [
"linux",
"macos",
"windows"
],
"release": "2024-01-15",
"version": "8.2.0"
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,81 @@
{
"stalwart": {
"cluster_issuer": "letsencrypt-prod",
"data_dir": "/var/lib/stalwart",
"domain": "example.com",
"hostname": "mail.example.com",
"http_port": 8080,
"image": "stalwartlabs/mail-server:v0.11.6",
"imaps_port": 993,
"lb_ipam_ip": "",
"mode": "cluster",
"name": "stalwart",
"namespace": "mail",
"node": "",
"operations": {
"backup": true,
"config": true,
"delete": true,
"health": true,
"install": true,
"restart": true,
"restore": true,
"update": true
},
"provides": {
"endpoints": [],
"ports": [
25,
465,
587,
993
],
"service": "stalwart"
},
"relay": {
"host": "",
"name": "ses",
"port": 587,
"tls": true
},
"requires": {
"credentials": [
"RELAY_USERNAME",
"RELAY_PASSWORD",
"ADMIN_PASSWORD"
],
"ports": [
{
"exposure": "public",
"port": 25
},
{
"exposure": "public",
"port": 465
},
{
"exposure": "public",
"port": 587
},
{
"exposure": "public",
"port": 993
},
{
"exposure": "internal",
"port": 8080
}
],
"storage": {
"persistent": true,
"size": "20Gi"
}
},
"smtp_port": 25,
"smtps_port": 465,
"storage_class": "hcloud-volumes",
"submission_port": 587,
"tls_secret": "stalwart-tls",
"version": "0.11.6"
}
}

View file

@ -0,0 +1,15 @@
{
"containerd": {
"cmd_task": "install",
"mode": "taskserv",
"name": "containerd",
"operations": {
"delete": false,
"health": true,
"install": true,
"reinstall": true
},
"runtimes": "runc",
"version": "1.7.24"
}
}

View file

@ -0,0 +1,104 @@
{
"alternatives_considered": [
{
"option": "prvng-cli daemon: route read-only CLI commands to a separate Rust HTTP server via Unix socket",
"why_rejected": "Solves only specific read commands (<100ms), not the general nickel export cost. Adds a second daemon with socket/PID lifecycle. Nu call sites still need output formatting to match Nu tables. Doesn't help operation-path commands that also call nickel export."
},
{
"option": "Lazy-load Nu modules (refactor main_provisioning/mod.nu)",
"why_rejected": "The dispatcher already lazy-loads commands/ subdirectory. The Nu interpreter startup (~200-400ms) is unavoidable regardless. Module parse cost is ~600-1200ms — a real problem but separate from the nickel export stall. This plan targets nickel export; module parse is a future orthogonal improvement."
},
{
"option": "Nu-side cache with file-mtime check (no daemon)",
"why_rejected": "Nu processes are ephemeral — no proactive warming. First command of each session still pays the nickel export cost. Concurrent Nu processes (Makefile, CI) cause cache stampede: multiple processes miss simultaneously and all run nickel export. No file watching — cache becomes stale silently after NCL edits."
},
{
"option": "Separate cache directories for daemon and plugin",
"why_rejected": "Requires a coordination protocol (socket, IPC, or manifest polling) so the plugin can find daemon-written entries. The shared-directory approach eliminates coordination entirely — the key derivation IS the coordination protocol."
}
],
"consequences": {
"negative": [
"Nu startup cost (~1.2s module parse) is unaffected — a separate problem",
"First invocation of the day: cache cold until daemon warm-up completes (~500ms-2s)",
"ncl-sync binary must be installed and in PATH for performance benefits; absence degrades gracefully to direct nickel export"
],
"positive": [
"prvng component list, workflow list: ~1.5s (from ~3-7s) — Nu module parse only, no nickel export stall",
"prvng deploy: ~3-5s (from ~15-30s) — multiple nickel exports are cache hits",
"Cache survives across prvng invocations — warm-up on platform start amortizes the cost for the whole session",
"nu_plugin_nickel is now usable for all config reads (--import-path gap closed)"
]
},
"constraints": [
{
"check": {
"must_be_empty": true,
"paths": [
"provisioning/core/nulib/"
],
"pattern": "save.*config-cache.*\\.json",
"tag": "Grep"
},
"claim": "Nu processes NEVER write .json files to the cache directory directly",
"id": "ncl-sync-single-writer",
"rationale": "Single-writer principle: concurrent Nu processes writing cache files would corrupt manifest state and produce partial JSON. Only ncl-sync daemon writes to the cache directory.",
"scope": "provisioning/core/nulib/",
"severity": "Hard"
},
{
"check": {
"must_be_empty": true,
"paths": [
"provisioning/platform/crates/ncl-sync/"
],
"pattern": "platform-nats|platform-db|surrealdb",
"tag": "Grep"
},
"claim": "ncl-sync binary must not depend on platform-nats, platform-db, or surrealdb",
"id": "ncl-sync-no-platform-services",
"rationale": "Bootstrap circularity: NATS and SurrealDB are platform services whose configuration is managed by ncl-sync. The daemon cannot depend on services it configures.",
"scope": "provisioning/platform/crates/ncl-sync/Cargo.toml",
"severity": "Hard"
}
],
"context": "Every `prvng` CLI invocation that reads configuration runs `nickel export --format json` at least once, often multiple times. There are 124 call sites across the Nu codebase; each export costs 2-5s. The Nu module parse cost (~600-1200ms for 345 files) is a separate problem. This plan targets only the Nickel export cost. `lib_provisioning/config/cache/` already existed with the correct API shape (`cache-lookup`, `lookup-nickel-cache`, etc.) but every function was a no-op — the infrastructure was there but never wired to actual storage. Additionally, `nu_plugin_nickel` already implements file-content-based caching (`nickel-eval` command) but lacked `--import-path` support, which is why all 124 call sites used `^nickel export` directly instead of the plugin.",
"date": "2026-04-16",
"decision": "A Rust daemon (`ncl-sync`) compiles NCL to JSON proactively and maintains a shared cache at `~/.cache/provisioning/config-cache/`. The daemon and the `nu_plugin_nickel` plugin share a single cache directory and a single key derivation strategy: `SHA256(file_content + sorted_import_paths_joined_by_colon + format)`. This makes the key content-addressed — identical file content produces the same key regardless of path, and the daemon's pre-warmed entries are immediately visible to `nickel-eval` without any coordination protocol. Nu call sites in the hot path replace `^nickel export --format json ... | from json` with `nickel-eval ... --import-path [...]`. For soft-failure call sites (where export failure is acceptable), a `ncl-eval-soft` wrapper in `lib_provisioning/utils/nickel_processor.nu` isolates the single necessary `try/catch` and exposes clean call sites. The daemon is started by `prvng platform start` via `ncl-sync-start` in `service-manager.nu` and stopped by `prvng platform stop`. Nu processes signal re-export needs after mutations by writing `.sync-<pid>.json` sidecar files (atomic rename); the daemon drains these every 500ms.",
"id": "adr-022",
"ontology_check": {
"decision_string": "ncl-sync Rust daemon + nu_plugin_nickel shared cache at ~/.cache/provisioning/config-cache/ with content-based key SHA256(content+imports+format)",
"invariants_at_risk": [
"config-driven-always",
"type-safety-nickel"
],
"verdict": "Safe"
},
"rationale": [
{
"claim": "Shared cache dir + content-based key eliminates the need for a socket or IPC between daemon and Nu processes",
"detail": "The plugin's `lookup_cache` reads `~/.cache/provisioning/config-cache/<key>.json` directly from disk. The daemon writes to the same path. There is no runtime coordination — the plugin simply finds the file or falls back to direct `nickel export`. Alternative: daemon exposes a Unix socket for reads (prvng-cli daemon plan) — requires Nu processes to know the socket path, handle connection failures, and adds 10-15ms of socket overhead. The file-based approach gives <5ms reads and zero coupling."
},
{
"claim": "Content-addressed key (SHA256 of file content) is more correct than path+mtime-based key",
"detail": "A path+mtime key would falsely invalidate the cache if a file is touched without content change (e.g. `git checkout`, `touch`). A content-based key ensures that identical NCL files share a cache entry regardless of path, and that the cache only misses when the file actually changed. The tradeoff is that the key computation requires reading the file — mitigated by the daemon doing this proactively at warm-up rather than on each Nu invocation."
},
{
"claim": "Extending nu_plugin_nickel with --import-path is the correct fix for the 124 ^nickel call sites",
"detail": "The plugin existed precisely for this purpose but lacked `--import-path` support, forcing all provisioning code to use `^nickel export` directly. Adding `--import-path` to `nickel-eval` and `nickel-export` unblocks the migration. The plugin already converts JSON to Nu values natively (eliminating `| from json`), handles caching, and preserves error semantics via `LabeledError`."
},
{
"claim": "ncl-sync does not require NATS, SurrealDB, or any platform service",
"detail": "The daemon watches the filesystem via `notify`, runs `nickel` as a subprocess, and writes JSON files. It has no network dependencies. If ncl-sync depended on NATS to function, it would have a bootstrap circularity: NATS is a platform service whose configuration is described in NCL. A config cache daemon cannot depend on the services whose configuration it caches."
},
{
"claim": "Nu processes are never writers to the cache directory",
"detail": "Single-writer principle: only ncl-sync writes `<key>.json` files to the cache. Nu processes write `.sync-<pid>.json` sidecar files as signals to the daemon, then immediately continue execution. The daemon drains sidecars and writes cache entries. This prevents concurrent-write corruption of cache files without requiring locks."
}
],
"related_adrs": [
"adr-023-ncl-export-wrapper"
],
"status": "Accepted",
"title": "ncl-sync: Nickel Configuration Sync Daemon"
}

View file

@ -0,0 +1,39 @@
{
"downstream": [
{
"kind": "CoDeveloped",
"node": "dag-visualization",
"note": "Provisioning DAG graph exposed to ontoref-daemon as a DagGraphProvider plugin. Co-developed: provisioning emits structured DAG events on provisioning.dag.>; ontoref-daemon consumes and visualizes them.",
"project": "ontoref",
"url": "",
"via": "dag-ui"
}
],
"peers": [],
"upstream": [
{
"kind": "LibraryDependency",
"node": "protocol-as-standalone",
"note": "Ontoref protocol: .ontology/ schemas, ADR lifecycle, reflection modes, daemon sync.",
"project": "ontoref",
"url": "",
"via": "local"
},
{
"kind": "LibraryDependency",
"node": "",
"note": "stratum-graph, stratum-state, platform-nats, platform-db used by orchestrator and platform services.",
"project": "stratumiops",
"url": "",
"via": "local"
},
{
"kind": "LibraryDependency",
"node": "",
"note": "Embedded in platform/secretumvault — Vault service for secrets management. Local age key in solo mode, Shamir threshold in multi-user.",
"project": "secretumvault",
"url": "",
"via": "local"
}
]
}

View file

@ -0,0 +1,4 @@
{
"nickel_api": "1.0",
"version": "15"
}

View file

@ -0,0 +1,14 @@
{
"dependencies": [
"os"
],
"name": "k0s",
"version": {
"check_latest": true,
"current": "1.35.3+k0s.0",
"grace_period": 86400,
"site": "",
"source": "github.com/k0sproject/k0s",
"tags": ""
}
}

View file

@ -0,0 +1,15 @@
{
"dependencies": [
"kubernetes",
"cert-manager"
],
"name": "linkerd",
"version": {
"check_latest": true,
"current": "2.16.0",
"grace_period": 86400,
"site": "https://linkerd.io",
"source": "https://github.com/linkerd/linkerd2/releases",
"tags": "https://github.com/linkerd/linkerd2/tags"
}
}

View file

@ -0,0 +1,40 @@
{
"best_practices": [
"bp_007",
"bp_012"
],
"conflicts_with": [],
"dependencies": [
"postgresql",
"forgejo"
],
"description": "Woodpecker CI — lightweight pipeline runner connected to Forgejo for automated builds",
"modes": [
"cluster"
],
"name": "woodpecker",
"provides": [
{
"id": "ci-runner",
"interface": "woodpecker",
"version": "latest"
}
],
"requires": [
{
"capability": "sql-database",
"kind": "Required"
},
{
"capability": "git-forge",
"kind": "Required"
}
],
"tags": [
"ci",
"pipeline",
"automation",
"appserv"
],
"version": "1.0.0"
}

View file

@ -0,0 +1,12 @@
{
"dependencies": [],
"name": "containerd",
"version": {
"check_latest": true,
"current": "1.7.24",
"grace_period": 86400,
"site": "containerd.io",
"source": "github.com/containerd/containerd",
"tags": ""
}
}

View file

@ -0,0 +1,56 @@
{
"extension_registry": {
"build": {
"base_image": "rust:1.82-trixie",
"binary": "extension-registry",
"buildkit": {
"cache_mode": "local",
"inline_cache": false,
"parallel_jobs": 2
},
"chef_enabled": true,
"config_file": "",
"extra_runtime_pkgs": [],
"features": [],
"health_path": "/health",
"package": "extension-registry",
"port": 9094,
"runtime_image": "debian:trixie-slim",
"sccache": {
"bucket": "rust-cache",
"enabled": false,
"region": ""
},
"user_id": 1000
},
"cache": {
"capacity": 500,
"enable_list_cache": true,
"enable_metadata_cache": true,
"ttl_seconds": 600
},
"distributions": {
"oci": []
},
"server": {
"enable_compression": true,
"enable_cors": false,
"host": "127.0.0.1",
"port": 8084,
"workers": 2
},
"sources": {
"forgejo": [],
"gitea": [
{
"organization": "provisioning",
"timeout_seconds": 30,
"token_path": "/etc/secrets/gitea-token.txt",
"url": "http://localhost:3000",
"verify_ssl": false
}
],
"github": []
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,46 @@
{
"cloud_api_enabled": true,
"datacenter": null,
"labels": "provisioning=true,environment=production",
"liveness_interval": 300,
"liveness_ip": "{{network_public_ip}}",
"liveness_port": 22,
"network_ipv6": true,
"network_private_ip": null,
"network_public_ip": null,
"nixos_anywhere_enabled": false,
"not_use": false,
"plan": "CX21",
"provider": "hetzner",
"region": "eu-central",
"storages": [
{
"fstab": true,
"mount": true,
"mount_path": "/",
"name": "root",
"parts": [
{
"mount": true,
"mount_path": "/",
"name": "root",
"size": 30,
"total": 30,
"type": "ext4"
},
{
"mount": true,
"mount_path": "/var/lib",
"name": "data",
"size": 10,
"total": 10,
"type": "ext4"
}
],
"size": 30,
"total": 40,
"type": "ext4"
}
],
"taskservs": []
}

View file

@ -0,0 +1,15 @@
{
"lian_build": {
"context_src": "",
"ctx_type": "plain",
"image": "",
"lian_build_root": "/Users/Akasha/Development/lian-build",
"registry": {
"credential_path": "infra/libre-wuji/secrets/registry-push.sops.yaml",
"endpoint": "reg.librecloud.online"
},
"secrets_base": "/Users/Akasha/Development/project-provisioning/workspaces/libre-wuji",
"ssh_key": "/Users/Akasha/Development/.ssh/lian-build-runner",
"workspace": ""
}
}

View file

@ -0,0 +1,19 @@
{
"server_defaults": {
"fix_local_hosts": true,
"installer_user": "${user}",
"labels": "",
"lock": false,
"network_public_ipv4": true,
"network_public_ipv6": false,
"network_utility_ipv4": true,
"network_utility_ipv6": false,
"running_timeout": 200,
"running_wait": 10,
"storage_os_find": "name: debian-12 | arch: x86_64",
"time_zone": "UTC",
"user": "",
"user_home": "/home/${user}",
"user_ssh_port": 22
}
}

View file

@ -0,0 +1,19 @@
{
"k8s_nodejoin": {
"admin_host": null,
"admin_port": null,
"admin_user": null,
"cluster": "kubernetes",
"cp_hostname": "controlplane",
"name": "k8s-nodejoin",
"operations": {
"health": true,
"install": true
},
"source_cmd": "kubeadm token create --print-join-command > /tmp/k8s_join.sh",
"source_path": "/tmp/k8s_join.sh",
"ssh_key_path": null,
"target_cmd": "sudo bash /tmp/k8s_join.sh",
"target_path": "/tmp/k8s_join.sh"
}
}

View file

@ -0,0 +1,125 @@
{
"ci": {
"ci_providers": {
"github_actions": {
"branches_pr": "main",
"branches_push": "main,develop",
"enabled": true
},
"woodpecker": {
"enabled": true
}
},
"features": {
"enable_ci_cd": true,
"enable_cross_compilation": true,
"enable_pre_commit": true,
"generate_code_of_conduct": true,
"generate_contributing": true,
"generate_dockerfiles": true,
"generate_security": true,
"generate_taplo_config": true
},
"project": {
"description": "Provisioning",
"detected_languages": [
"rust",
"nushell",
"nickel",
"bash",
"markdown"
],
"name": "provisioning",
"primary_language": "nushell",
"repo_url": "https://repo.jesusperez.pro/jesus/provisioning",
"site_url": "https://provisioning.systems"
},
"settings": {
"job_timeout_minutes": 1,
"parallel_jobs": 1,
"require_status_checks": true,
"run_on_draft_prs": true
},
"tools": {
"audit": {
"enabled": true,
"install_method": "cargo"
},
"black": {
"enabled": true,
"install_method": "pip"
},
"clippy": {
"deny_warnings": true,
"enabled": true,
"install_method": "cargo"
},
"deny": {
"enabled": true,
"install_method": "cargo"
},
"eslint": {
"enabled": true,
"install_method": "npm"
},
"jest": {
"enabled": true,
"install_method": "npm"
},
"llvm-cov": {
"enabled": true,
"install_method": "cargo"
},
"markdownlint": {
"enabled": true,
"install_method": "npm"
},
"nickel": {
"check_all": true,
"enabled": true,
"install_method": "brew"
},
"nushell": {
"check_all": true,
"enabled": true,
"install_method": "builtin"
},
"prettier": {
"enabled": true,
"install_method": "npm"
},
"pytest": {
"enabled": true,
"install_method": "pip"
},
"ruff": {
"enabled": true,
"install_method": "pip"
},
"sbom": {
"enabled": true,
"install_method": "cargo"
},
"shellcheck": {
"enabled": true,
"install_method": "brew"
},
"shfmt": {
"enabled": true,
"install_method": "brew"
},
"taplo": {
"enabled": true,
"install_method": "cargo"
},
"vale": {
"enabled": true,
"install_method": "brew"
},
"yamllint": {
"enabled": true,
"install_method": "brew"
}
}
}
}

View file

@ -0,0 +1,12 @@
{
"K0s": {
"cluster_name": "k0s",
"data_dir": "/var/lib/k0s",
"disable_ipv6": true,
"disable_kube_proxy": true,
"kine_dir": "",
"role": "controller_worker",
"storage_backend": "dqlite",
"version": "1.35.3+k0s.0"
}
}

View file

@ -0,0 +1,13 @@
{
"external_nfs": {
"ip": "{{network_private_ip}}",
"net": "$net",
"operations": {
"delete": true,
"health": true,
"install": true,
"reinstall": true
},
"shared": "/shared"
}
}

View file

@ -0,0 +1,16 @@
{
"artifact": {
"description": "Credentials for docker-mailserver: SMTP relay auth and admin mailbox.",
"id": "mailserver",
"layers": [
{
"description": "contract.ncl — MailserverSecrets field definitions",
"media_type": "application/vnd.ontoref.domain.contract.v1",
"required": true
}
],
"media_type": "application/vnd.ontoref.domain.v1",
"uses_registry": "primary",
"version": "1.0.0"
}
}

View file

@ -0,0 +1,74 @@
{
"backup_manager": {
"concurrency": {
"max_parallel_backups": 4,
"max_parallel_per_destination": 2
},
"daemon_http_port": 9099,
"daemon_image": "ghcr.io/jesusperezlorenzo/backup-manager-runner:0.1.0",
"daemon_metrics_port": 9100,
"daemon_replicas": 1,
"host_install": {
"binary_path": "/usr/local/bin/prvng-backup",
"cron_dir": "/etc/cron.d",
"env_dir": "/etc/prvng-backup",
"systemd_dir": "/etc/systemd/system"
},
"mode": "cluster",
"mount_image": "ghcr.io/jesusperezlorenzo/backup-manager-mount:0.1.0",
"name": "backup-manager",
"namespace": "backup-system",
"nats_topology_ref": "infra/libre-wuji/backup-nats-topology.ncl",
"nats_url": "nats://nats.ops-system.svc.cluster.local:4222",
"operations": {
"delete": true,
"health": true,
"install": true,
"restart": true,
"update": true
},
"policies": {
"components_path": "infra/libre-wuji/components/*.ncl",
"groups_path": "infra/libre-wuji/backup-groups.ncl",
"nickel_import_path": "/opt/provisioning",
"system_backups_path": "infra/libre-wuji/system-backups.ncl",
"verify_recipes_path": "infra/libre-wuji/verify-recipes"
},
"provides": {
"endpoints": [
"/metrics",
"/api/v1/policy",
"/api/v1/queue"
],
"port": 9099,
"service": "backup-manager"
},
"requires": {
"credentials": [
"VAULT_BOOTSTRAP_TOKEN",
"NATS_BOOTSTRAP_NKEY_SEED",
"BACKUP_AGE_KEY",
"BACKUP_S3_PRIMARY_ACCESS_KEY",
"BACKUP_S3_PRIMARY_SECRET_KEY",
"BACKUP_B2_REPLICA_KEY_ID",
"BACKUP_B2_REPLICA_APPLICATION_KEY"
],
"ports": [
{
"exposure": "internal",
"port": 9099
},
{
"exposure": "internal",
"port": 9100
}
],
"storage": {
"persistent": true,
"size": "5Gi"
}
},
"vault_bootstrap_secret_ref": "backup-manager-bootstrap",
"vault_endpoint": "https://vault.libre-wuji.svc.cluster.local:8200"
}
}

View file

@ -0,0 +1,142 @@
{
"DefaultVmCapacity": {
"host_name": "",
"max_vms": 0,
"running_vms": 0,
"total_cpu_cores": 0,
"total_disk_gb": 0,
"total_memory_mb": 0,
"used_cpu_cores": 0,
"used_disk_gb": 0,
"used_memory_mb": 0
},
"DefaultVmCloudInit": {
"enabled": true
},
"DefaultVmConfig": {
"auto_cleanup": false,
"backend": "libvirt",
"base_image": "ubuntu-22.04",
"cpu": 2,
"disk_gb": 20,
"graphics_enable": false,
"memory_mb": 4096,
"name": "",
"nested_virt": false,
"network_mode": "bridge",
"permanent": false,
"serial_console": true,
"taskservs": [],
"temporary": false
},
"DefaultVmImage": {
"base_os": "ubuntu",
"format": "qcow2",
"name": "",
"os_version": "22.04",
"path": "",
"size_gb": 0
},
"DefaultVmMount": {
"guest_path": "",
"host_path": "",
"readonly": false
},
"DefaultVmNetwork": {
"name": "default",
"type": "nat"
},
"DefaultVmPortMapping": {
"guest_port": 0,
"host_port": 0,
"protocol": "tcp"
},
"DefaultVmRegistry": {
"permanent_count": 0,
"temporary_count": 0,
"updated_at": "",
"vms": []
},
"DefaultVmState": {
"created_at": "",
"permanent": false,
"state": "stopped",
"vm_name": ""
},
"DefaultVmVolume": {
"format": "qcow2",
"name": "",
"size_gb": 0
},
"defaults": {
"vm_capacity": {
"host_name": "",
"max_vms": 0,
"running_vms": 0,
"total_cpu_cores": 0,
"total_disk_gb": 0,
"total_memory_mb": 0,
"used_cpu_cores": 0,
"used_disk_gb": 0,
"used_memory_mb": 0
},
"vm_cloud_init": {
"enabled": true
},
"vm_config": {
"auto_cleanup": false,
"backend": "libvirt",
"base_image": "ubuntu-22.04",
"cpu": 2,
"disk_gb": 20,
"graphics_enable": false,
"memory_mb": 4096,
"name": "",
"nested_virt": false,
"network_mode": "bridge",
"permanent": false,
"serial_console": true,
"taskservs": [],
"temporary": false
},
"vm_image": {
"base_os": "ubuntu",
"format": "qcow2",
"name": "",
"os_version": "22.04",
"path": "",
"size_gb": 0
},
"vm_mount": {
"guest_path": "",
"host_path": "",
"readonly": false
},
"vm_network": {
"name": "default",
"type": "nat"
},
"vm_port_mapping": {
"guest_port": 0,
"host_port": 0,
"protocol": "tcp"
},
"vm_registry": {
"permanent_count": 0,
"temporary_count": 0,
"updated_at": "",
"vms": []
},
"vm_state": {
"created_at": "",
"permanent": false,
"state": "stopped",
"vm_name": ""
},
"vm_volume": {
"format": "qcow2",
"name": "",
"size_gb": 0
}
}
}

View file

@ -0,0 +1,46 @@
{
"best_practices": [
"bp_001",
"bp_033"
],
"category": "storage",
"conflicts_with": [],
"dependencies": [
"k0s",
"cilium",
"external_nfs"
],
"description": "democratic-csi NFS-client driver — Kubernetes CSI provisioner backed by any Linux NFS server",
"modes": [
"taskserv"
],
"name": "democratic_csi",
"provides": [
{
"id": "nfs-storage-csi",
"interface": "k8s-csi",
"version": "0.14"
}
],
"requires": [
{
"capability": "kubernetes-cluster",
"kind": "Required"
},
{
"capability": "cni",
"kind": "Required"
},
{
"capability": "nfs-server",
"kind": "Required"
}
],
"tags": [
"storage",
"csi",
"nfs",
"kubernetes"
],
"version": "0.14.6"
}

View file

@ -0,0 +1,37 @@
{
"best_practices": [
"bp_001",
"bp_008",
"bp_027"
],
"category": "infrastructure",
"conflicts_with": [],
"dependencies": [
"os"
],
"description": "k0s Kubernetes distribution — controller+worker single-node mode",
"modes": [
"taskserv"
],
"name": "k0s",
"provides": [
{
"id": "kubernetes-cluster",
"interface": "k8s",
"version": "1.35"
}
],
"requires": [
{
"capability": "linux-node",
"kind": "Required"
}
],
"tags": [
"kubernetes",
"cluster",
"infrastructure",
"k0s"
],
"version": "1.35.3+k0s.0"
}

View file

@ -0,0 +1,37 @@
{
"best_practices": [
"bp_001",
"bp_008"
],
"category": "storage",
"conflicts_with": [],
"dependencies": [
"os",
"vol_prepare"
],
"description": "Longhorn node OS prerequisites — installs open-iscsi, nfs-common, cryptsetup on storage nodes",
"modes": [
"taskserv"
],
"name": "longhorn_node_prep",
"provides": [
{
"id": "longhorn-node-prereqs",
"interface": "storage-node",
"version": "1.0"
}
],
"requires": [
{
"capability": "linux-node",
"kind": "Required"
}
],
"tags": [
"storage",
"longhorn",
"iscsi",
"kubernetes"
],
"version": "1.0.0"
}

View file

@ -0,0 +1,604 @@
{
"commands": [
{
"aliases": [
"h",
"-h",
"--help"
],
"command": "help",
"daemon_target": "none",
"description": "Show help for commands",
"help_category": "infrastructure",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"plat",
"p"
],
"command": "platform",
"daemon_target": "none",
"description": "Manage platform services",
"help_category": "platform",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"guides",
"howto"
],
"command": "guide",
"daemon_target": "none",
"description": "Show guides and tutorials",
"help_category": "guides",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"sc"
],
"command": "shortcuts",
"daemon_target": "none",
"description": "Show command shortcuts",
"help_category": "guides",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"quick"
],
"command": "quickstart",
"daemon_target": "none",
"description": "Quick start guide",
"help_category": "guides",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"scratch"
],
"command": "from-scratch",
"daemon_target": "none",
"description": "Start from scratch guide",
"help_category": "guides",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"custom"
],
"command": "customize",
"daemon_target": "none",
"description": "Customization guide",
"help_category": "guides",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"bstrap"
],
"command": "bootstrap",
"daemon_target": "none",
"description": "L1 Hetzner resource bootstrap (network, firewall, SSH key, Floating IPs)",
"help_category": "infrastructure",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"floating-ip"
],
"command": "fip",
"daemon_target": "none",
"description": "Floating IP management (list, show, assign, unassign, protection)",
"help_category": "infrastructure",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"d"
],
"command": "dns",
"daemon_target": "none",
"description": "Manage DNS records via the configured provider middleware",
"help_category": "infrastructure",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"vol"
],
"command": "volume",
"daemon_target": "none",
"description": "Volume management (list, create, attach, detach, delete)",
"help_category": "infrastructure",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"s"
],
"command": "server",
"daemon_target": "none",
"description": "Server management",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [],
"command": "ssh",
"daemon_target": "none",
"description": "SSH shortcut: connect to a server by hostname (e.g. prvng ssh sgoyol-1)",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"ops"
],
"command": "op",
"daemon_target": "none",
"description": "Op governance — workspace operation tracking with jj + Radicle + restic (init, start, finish, log, show, rollback)",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"c",
"comp"
],
"command": "component",
"daemon_target": "none",
"description": "Component lifecycle — install, delete, update, reinstall, restart, backup, restore, health, list, show",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"cl"
],
"command": "cluster",
"daemon_target": "none",
"description": "Cluster management — multi-server cluster lifecycle (deploy, status, scale, delete)",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"cat"
],
"command": "catalog",
"daemon_target": "none",
"description": "IaC catalog — browse catalog/components/ building blocks and metadata",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"i"
],
"command": "integration",
"daemon_target": "none",
"description": "Integration mode OCI artifact management — domain publish/pull/describe/verify; ecosystem domains",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"new"
],
"command": "create",
"daemon_target": "none",
"description": "Create resources (server, taskserv, cluster)",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": true,
"requires_services": true,
"uses_cache": false
},
{
"aliases": [
"d"
],
"command": "delete",
"daemon_target": "none",
"description": "Delete resources (server, taskserv, cluster)",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"ws"
],
"command": "workspace",
"daemon_target": "none",
"description": "Workspace management",
"help_category": "workspace",
"requires_args": true,
"requires_daemon": true,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"val"
],
"command": "validate",
"daemon_target": "none",
"description": "Validate configuration",
"help_category": "config",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [],
"command": "config",
"daemon_target": "none",
"description": "Configuration management",
"help_category": "setup",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [],
"command": "env",
"daemon_target": "none",
"description": "Environment configuration",
"help_category": "config",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"a",
"al"
],
"command": "alias",
"daemon_target": "none",
"description": "Show command aliases — alias list (al) displays the full shortcut table",
"help_category": "utils",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [],
"command": "show",
"daemon_target": "none",
"description": "Show configuration",
"help_category": "config",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"st"
],
"command": "setup",
"daemon_target": "none",
"description": "Initial setup",
"help_category": "setup",
"requires_args": false,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"st"
],
"command": "state",
"daemon_target": "none",
"description": "Workspace provisioning state management",
"help_category": "state",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"j"
],
"command": "job",
"daemon_target": "none",
"description": "Orchestrator job management (list, status, monitor, submit)",
"help_category": "orchestration",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"w",
"wflow"
],
"command": "workflow",
"daemon_target": "none",
"description": "Workspace workflow management — WorkflowDef lifecycle (list, show, run, validate, status)",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"bak"
],
"command": "backup",
"daemon_target": "none",
"description": "Backup subsystem — multi-context orchestrator (one-shot, daemon, standalone, coordinator). Subcommands: status, list, run, restore, mount, verify, policy, providers, destinations, daemon, drill, events.",
"help_category": "infrastructure",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"b",
"bat"
],
"command": "batch",
"daemon_target": "none",
"description": "Batch operations",
"help_category": "orchestration",
"requires_args": true,
"requires_daemon": true,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"o",
"orch"
],
"command": "orchestrator",
"daemon_target": "none",
"description": "Orchestrator management",
"help_category": "orchestration",
"requires_args": true,
"requires_daemon": true,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"mod"
],
"command": "module",
"daemon_target": "none",
"description": "Module management",
"help_category": "development",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"lyr"
],
"command": "layer",
"daemon_target": "none",
"description": "Layer management",
"help_category": "development",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"disc"
],
"command": "discover",
"daemon_target": "none",
"description": "Discover modules",
"help_category": "development",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [],
"command": "status",
"daemon_target": "none",
"description": "Show status",
"help_category": "diagnostics",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [],
"command": "health",
"daemon_target": "none",
"description": "Health check",
"help_category": "diagnostics",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"diag"
],
"command": "diagnostics",
"daemon_target": "none",
"description": "Run diagnostics",
"help_category": "diagnostics",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"bd"
],
"command": "build",
"daemon_target": "none",
"description": "Build operations",
"help_category": "build",
"requires_args": true,
"requires_daemon": true,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [],
"command": "auth",
"daemon_target": "none",
"description": "Authentication management",
"help_category": "authentication",
"requires_args": true,
"requires_daemon": true,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [],
"command": "login",
"daemon_target": "none",
"description": "Login",
"help_category": "authentication",
"requires_args": true,
"requires_daemon": true,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"int"
],
"command": "integrations",
"daemon_target": "none",
"description": "Integration management",
"help_category": "integrations",
"requires_args": true,
"requires_daemon": true,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [],
"command": "vm",
"daemon_target": "none",
"description": "VM management",
"help_category": "vm",
"requires_args": true,
"requires_daemon": true,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [
"r"
],
"command": "runner",
"daemon_target": "none",
"description": "Manage lian-build daemon runners (list/destroy/pin/unpin/status)",
"help_category": "build",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": true
},
{
"aliases": [],
"command": "runners",
"daemon_target": "none",
"description": "Ephemeral build runners — list, status, kill, gc (hcloud-backed)",
"help_category": "build",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
},
{
"aliases": [
"reg"
],
"command": "registry",
"daemon_target": "none",
"description": "OCI registry — ls, tags, manifest, rm (crane client against reg.librecloud.online)",
"help_category": "build",
"requires_args": true,
"requires_daemon": false,
"requires_services": false,
"uses_cache": false
}
]
}

View file

@ -0,0 +1,62 @@
{
"cilium": {
"cluster_id": 0,
"cluster_name": "",
"concerns": {
"backup": {
"kind": "disabled",
"reason": "state in K8s API captured by SystemBackupDef.cluster_resources"
},
"certs": {
"kind": "disabled",
"reason": "no ACME issuer required"
},
"dns": {
"kind": "disabled",
"reason": "no DNS records owned by this component"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "disabled",
"reason": "controller-level RBAC, not TLS endpoint"
}
},
"enable_gateway_api": false,
"enable_ingress_controller": false,
"envoy_enabled": true,
"gateway_api_version": "v1.2.1",
"gateway_name": "",
"gateway_sharing_key": "",
"ingress_lb_mode": "shared",
"k8s_service_host": "",
"k8s_service_port": 6443,
"kube_proxy_replacement": false,
"live_check": {
"namespace": "kube-system",
"scope": "cp_only",
"selector": "cilium",
"strategy": "k8s_pods"
},
"name": "cilium",
"operations": {
"delete": true,
"health": true,
"install": true,
"reinstall": true
},
"private_fip": "",
"private_gateway_name": "",
"public_fip": "",
"socket_lb": true,
"version": "1.19.3"
}
}

View file

@ -0,0 +1,16 @@
{
"artifact": {
"description": "Async event emission protocol. Participants publish lifecycle events (requested/progress/completed/failed) to NATS subjects. Subject prefix is injected via cabling to avoid cross-Mode collisions.",
"id": "event-emission",
"layers": [
{
"description": "contract.ncl — EventEnvelope schema + EventEmissionInputs (subject_prefix requirement)",
"media_type": "application/vnd.ontoref.domain.contract.v1",
"required": true
}
],
"media_type": "application/vnd.ontoref.domain.v1",
"uses_registry": "primary",
"version": "0.1.0"
}
}

View file

@ -0,0 +1,54 @@
{
"auth": {
"enabled": false,
"jwks_uri": null,
"jwt_audience": null,
"jwt_issuer_uri": null
},
"database": {
"database": "kms",
"host": "127.0.0.1",
"password": null,
"path": "/var/lib/kms/kms.db",
"port": null,
"ssl_mode": "disable",
"typ": "sqlite",
"username": null
},
"kms": {
"auth": {
"enabled": false
},
"bind_addr": "0.0.0.0",
"ca_cert_file": null,
"cert_file": null,
"config_file": "kms.toml",
"config_path": "/etc/cosmian",
"database": {
"database": "kms",
"host": "127.0.0.1",
"path": "/var/lib/kms/kms.db",
"ssl_mode": "disable",
"typ": "sqlite"
},
"fips_mode": false,
"key_file": null,
"log_level": "info",
"name": "kms",
"port": 9998,
"run_path": "/usr/local/bin/cosmian_kms",
"run_user": {
"group": "kms",
"home": "/home/kms",
"name": "kms"
},
"tls_enabled": false,
"version": "latest",
"work_path": "/var/lib/kms"
},
"run_user": {
"group": "kms",
"home": "/home/kms",
"name": "kms"
}
}

View file

@ -0,0 +1,115 @@
{
"alternatives_considered": [
{
"option": "Compile-time enforcement only via crate visibility",
"why_rejected": "Insufficient for Nushell code which has no compile-time type system. Pre-commit and Claude hooks are needed to cover .nu files where the Rust compiler cannot help."
},
{
"option": "Documentation + code review process",
"why_rejected": "The failure mode this ADR addresses (direct provider CLI calls from Nushell) was introduced despite existing documentation. Enforcement must be automatic, not manual."
}
],
"consequences": {
"negative": [
"Adding a new cloud provider requires changes to Orchestrator only — correct by design but requires understanding the dispatch model",
"The pre-commit hook adds ~200ms to commit time for grep scans",
"CLI cannot query provider state directly — must call Orchestrator API, adding one HTTP hop"
],
"positive": [
"All provider credentials are scoped to Orchestrator's process — no credential leakage path to CLI",
"Task state machine in Orchestrator provides rollback for every provider operation",
"Auth defects are isolated to Control Center — other services cannot accidentally implement auth",
"SOLID violations are caught at the earliest possible layer (usually compile-time or dev-time), not in production"
]
},
"constraints": [
{
"check": {
"cmd": "rg 'hcloud|aws|doctl|upctl' --include='*.rs' provisioning/ | grep -v 'orchestrator'",
"expect_exit": 1,
"tag": "NuCmd"
},
"claim": "Provider API calls (hcloud, aws, doctl, upctl) must only exist in the orchestrator crate",
"id": "provider-calls-orchestrator-only",
"rationale": "All provider API calls must flow through the Orchestrator dispatch model to maintain audit trail and rollback capability.",
"scope": "provisioning/",
"severity": "Hard"
},
{
"check": {
"cmd": "rg 'russh|ssh2' --include='*.rs' provisioning/platform/crates/ | grep -v 'orchestrator\\|machines'",
"expect_exit": 1,
"tag": "NuCmd"
},
"claim": "SSH operations (russh, ssh2) must only exist in orchestrator and machines crates",
"id": "ssh-orchestrator-machines-only",
"rationale": "SSH operations that bypass Orchestrator bypass the task state machine and lose rollback capability and audit trail.",
"scope": "provisioning/platform/crates/",
"severity": "Hard"
},
{
"check": {
"must_be_empty": true,
"paths": [
"platform/crates/"
],
"pattern": "bypass|skip.*auth|no.*auth",
"tag": "Grep"
},
"claim": "solo_auth_middleware is the only place in the codebase where auth is bypassed; it must be gated behind --mode solo and never used in production routing",
"id": "solo-auth-middleware-single-bypass",
"rationale": "A single documented and tested auth bypass is auditable. Multiple bypass paths create an audit surface that cannot be systematically verified.",
"scope": "platform/crates/control-center/src/middleware/",
"severity": "Hard"
},
{
"check": {
"cmd": "rg 'roles.contains|role ==' --include='*.rs' platform/crates/ | grep -v test",
"expect_exit": 1,
"tag": "NuCmd"
},
"claim": "No ad-hoc role checks (if user.roles.contains) in business logic — Cedar policies are the only authorization mechanism",
"id": "cedar-only-authorization",
"rationale": "Ad-hoc role checks create authorization logic scattered across services that cannot be audited or modified atomically.",
"scope": "platform/crates/",
"severity": "Soft"
}
],
"context": "As the platform expanded from a CLI tool to a multi-service control plane, a critical failure mode emerged: the Nushell CLI directly called cloud provider CLIs (hcloud, aws, doctl). This violated Single Responsibility (the CLI acquired infrastructure execution responsibility) and Dependency Inversion (CLI depended on concrete provider CLIs instead of the Orchestrator abstraction). Consequences: provider credentials leaked into CLI process environment (HCLOUD_TOKEN as env var), no audit trail for provider API calls made outside Orchestrator, SSH operations done by CLI bypassed the task state machine and rollback capability, auth decisions (JWT validation) duplicated across services instead of delegated to Control Center, secret files read directly by multiple services bypassing Vault's lease lifecycle. Documentation alone fails to enforce boundaries: engineers under time pressure skip it. The enforcement must be structural.",
"date": "2026-02-17",
"decision": "Six hard boundaries are enforced at six independent layers. Each layer is a fail-safe — any single layer catching a violation is sufficient to prevent it shipping. The six boundaries: (1) Provider API calls only in orchestrator crate, (2) SSH operations only in orchestrator+machines crates, (3) SurrealDB access from CLI forbidden, (4) Secret credentials forbidden in NATS messages, (5) Auth decisions only in control-center crate, (6) Raw secret file/env reads in services forbidden. Enforcement layers: compile-time (pub(crate) visibility), dev-time (Claude PreToolUse hook), pre-commit (git hook), CI (architecture tests), runtime (Cedar policies), continuous audit (NATS audit subject).",
"id": "adr-014",
"ontology_check": {
"decision_string": "Six hard SOLID boundaries enforced at six independent layers; solo_auth_middleware is the only documented auth bypass; Cedar is the only authorization mechanism",
"invariants_at_risk": [
"solid-boundaries",
"provider-abstraction"
],
"verdict": "Safe"
},
"rationale": [
{
"claim": "Documentation alone is insufficient — enforcement must be structural",
"detail": "Engineers under time pressure bypass documentation. The six-layer enforcement stack means a violation must simultaneously evade compile-time type checking, the Claude dev hook, the pre-commit grep, the CI architecture test, Cedar policy evaluation, and the NATS audit collector. Any single layer is sufficient to catch it."
},
{
"claim": "Compile-time is the cheapest enforcement layer",
"detail": "Provider client types are pub(crate) inside orchestrator. Other crates cannot import them — the Rust compiler rejects the build before any test runs. This is O(0) runtime cost."
},
{
"claim": "AUTH corollaries prevent auth fragmentation",
"detail": "solo_auth_middleware is the only documented auth bypass, gated behind --mode solo. All protected routes are inside route_layer(). UserContext is extracted from request extensions, never from headers directly. Cedar policies are the only authorization mechanism — no ad-hoc role checks."
},
{
"claim": "NATS audit subject provides continuous violation detection at runtime",
"detail": "provisioning.audit.violation.solid is published on runtime violations. AuditCollector persists these to SurrealDB. Violations discovered after deployment are recorded and queryable."
}
],
"related_adrs": [
"adr-012-nats-event-broker",
"adr-013-surrealdb-global-store",
"adr-015-solo-mode-architecture"
],
"status": "Accepted",
"title": "SOLID Architecture Boundaries with Multi-Layer Enforcement"
}

View file

@ -0,0 +1,3 @@
{
"CERT_MANAGER_VERSION": "v1.19.5"
}

View file

@ -0,0 +1,222 @@
{
"DefaultMariaDB": {
"concerns": {
"backup": {
"backlog_ref": "BACKUP-DB-MARIADB-001",
"kind": "pending",
"reason": "BackupPolicy with database scope + dump_strategy declared at workspace level"
},
"certs": {
"kind": "disabled",
"reason": "no ACME issuer required"
},
"dns": {
"kind": "disabled",
"reason": "no public DNS records"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "disabled",
"reason": "internal cluster service, ingress-level TLS handled separately"
}
},
"data_dir": "/var/lib/mysql",
"databases": [],
"image": "mariadb:12.2.2-ubi10",
"live_check": {
"scope": "cp_only",
"strategy": "k8s_pods"
},
"mode": "cluster",
"name": "mariadb",
"operations": {
"backup": true,
"delete": true,
"health": true,
"install": true,
"restart": true,
"restore": true,
"update": true
},
"port": 3306,
"provides": {
"databases": [],
"port": 3306,
"service": "mariadb"
},
"requires": {
"credentials": [
"MARIADB_ROOT_PASSWORD"
],
"ports": [
{
"exposure": "private",
"port": 3306
}
],
"storage": {
"persistent": true,
"size": "4Gi"
}
},
"version": "12.2.2"
},
"MariaDB": {
"concerns": {
"backup": {
"backlog_ref": "BACKUP-DB-MARIADB-001",
"kind": "pending",
"reason": "BackupPolicy with database scope + dump_strategy declared at workspace level"
},
"certs": {
"kind": "disabled",
"reason": "no ACME issuer required"
},
"dns": {
"kind": "disabled",
"reason": "no public DNS records"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "disabled",
"reason": "internal cluster service, ingress-level TLS handled separately"
}
},
"data_dir": "/var/lib/mysql",
"databases": [],
"image": "mariadb:12.2.2-ubi10",
"live_check": {
"scope": "cp_only",
"strategy": "k8s_pods"
},
"mode": "cluster",
"name": "mariadb",
"operations": {
"backup": true,
"delete": true,
"health": true,
"install": true,
"restart": true,
"restore": true,
"update": true
},
"port": 3306,
"provides": {
"databases": [],
"port": 3306,
"service": "mariadb"
},
"requires": {
"credentials": [
"MARIADB_ROOT_PASSWORD"
],
"ports": [
{
"exposure": "private",
"port": 3306,
"protocol": "TCP"
}
],
"storage": {
"persistent": true,
"size": "4Gi"
}
},
"version": "12.2.2"
},
"Version": {
"nickel_api": "1.0",
"version": "12.2.2"
},
"defaults": {
"mariadb": {
"concerns": {
"backup": {
"backlog_ref": "BACKUP-DB-MARIADB-001",
"kind": "pending",
"reason": "BackupPolicy with database scope + dump_strategy declared at workspace level"
},
"certs": {
"kind": "disabled",
"reason": "no ACME issuer required"
},
"dns": {
"kind": "disabled",
"reason": "no public DNS records"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "disabled",
"reason": "internal cluster service, ingress-level TLS handled separately"
}
},
"data_dir": "/var/lib/mysql",
"databases": [],
"image": "mariadb:12.2.2-ubi10",
"live_check": {
"scope": "cp_only",
"strategy": "k8s_pods"
},
"mode": "cluster",
"name": "mariadb",
"operations": {
"backup": true,
"delete": true,
"health": true,
"install": true,
"restart": true,
"restore": true,
"update": true
},
"port": 3306,
"provides": {
"databases": [],
"port": 3306,
"service": "mariadb"
},
"requires": {
"credentials": [
"MARIADB_ROOT_PASSWORD"
],
"ports": [
{
"exposure": "private",
"port": 3306
}
],
"storage": {
"persistent": true,
"size": "4Gi"
}
},
"version": "12.2.2"
}
}
}

View file

@ -0,0 +1,18 @@
{
"dependencies": [],
"detector": {
"capture": "capture0",
"command": "hcloud version",
"method": "command",
"pattern": "hcloud ([\\d.]+)"
},
"name": "hcloud",
"version": {
"check_latest": true,
"current": "1.57.0",
"grace_period": 86400,
"site": "https://github.com/hetznercloud/cli",
"source": "https://github.com/hetznercloud/cli/releases",
"tags": "https://github.com/hetznercloud/cli/tags"
}
}

View file

@ -0,0 +1,105 @@
{
"alternatives_considered": [
{
"option": "Generic LLM without schema grounding (GitHub Copilot style)",
"why_rejected": "Generates syntactically valid but semantically wrong configs — wrong enum values, missing required fields, invalid option combinations. Schema validation must happen after generation and frequently fails."
},
{
"option": "Fine-tuned model on project schemas",
"why_rejected": "Fine-tuning is expensive, requires retraining on every schema change, and does not generalize across projects. RAG is dynamic and always reflects the current schema state."
}
],
"consequences": {
"negative": [
"RAG index must be kept current as schemas and docs evolve — stale index degrades answer quality",
"ai-service adds a service dependency for all AI-assisted operations",
"Cost tracking required: rate limiting at 60 req/min, 1M tokens/day, $100/day"
],
"positive": [
"AI cannot generate configs that fail Nickel schema validation — structural correctness enforced",
"Cedar prevents AI from accessing secrets or deploying without human approval",
"RAG over project artifacts reduces hallucination on project-specific options",
"MCP tool calling (nickel_validate, schema_query) enables LLM agents to self-correct"
]
},
"constraints": [
{
"check": {
"must_be_empty": false,
"paths": [
"platform/"
],
"pattern": "ai-service.*Secret|Secret.*ai-service",
"tag": "Grep"
},
"claim": "ai-service must have a Cedar policy explicitly forbidding access to any Secret resource",
"id": "ai-cannot-access-secrets",
"rationale": "AI agents with secret access create unaudited credential exposure. The constraint must be at the authorization layer, not in the LLM prompt.",
"scope": "platform/crates/control-center/src/policies/",
"severity": "Hard"
},
{
"check": {
"must_be_empty": false,
"paths": [
"platform/"
],
"pattern": "human_approved",
"tag": "Grep"
},
"claim": "Any deployment action triggered by ai-service must have context.human_approved == true in the Cedar evaluation context",
"id": "ai-deployment-requires-human-approval",
"rationale": "Autonomous deployment without human review is an unacceptable risk for production infrastructure. The approval gate is enforced by Cedar, not by AI self-restraint.",
"scope": "platform/crates/orchestrator/src/",
"severity": "Hard"
},
{
"check": {
"must_be_empty": false,
"paths": [
"platform/crates/ai-service/"
],
"pattern": "nickel.*export|nickel_validate",
"tag": "Grep"
},
"claim": "All AI-generated Nickel configs must be validated via nickel export before being presented to the user or submitted to the orchestrator",
"id": "ai-generation-validates-against-schema",
"rationale": "Post-generation validation closes the loop — if the LLM generates an invalid config despite schema grounding, the user sees a validation error, not a deployment failure.",
"scope": "platform/crates/ai-service/src/",
"severity": "Hard"
}
],
"context": "Infrastructure configuration generation via LLM is unreliable without grounding: generic AI produces plausible but structurally invalid configs (wrong field names, invalid enum values, incompatible option combinations). Two risks: (1) hallucination — AI generates configs that fail schema validation; (2) security — AI agents with unrestricted access to secrets and deployment operations create unaudited paths. The platform has Nickel schemas for all configuration surfaces and Cedar for authorization — both can be used to constrain AI behavior.",
"date": "2026-01-08",
"decision": "AI config generation is constrained by Nickel schemas at generation time and by Cedar policies at authorization time. The ai-service is the HTTP entry point for all AI operations. RAG indexes Nickel schemas, documentation, and past deployments as retrieval context — AI generates WITH schema context, making hallucination structurally harder. Cedar policy forbids ai-service from accessing any secret and requires `context.human_approved == true` before any deployment operation. The mcp-server exposes tool calling (nickel_validate, schema_query, best_practices) to LLM agents.",
"id": "adr-019",
"ontology_check": {
"decision_string": "AI config generation is constrained by Nickel schemas (RAG grounding) and Cedar policies (secret isolation, human approval gate)",
"invariants_at_risk": [
"solid-boundaries",
"type-safety-nickel"
],
"verdict": "Safe"
},
"rationale": [
{
"claim": "Schema-constrained generation eliminates invalid config hallucination",
"detail": "Generic LLMs generate `engine = 'postgresql'` when the contract says `engine | [| 'postgres, 'mysql |]`. Providing the schema as RAG context gives the model the exact valid values. Post-generation nickel export validates the output against the same contract."
},
{
"claim": "Cedar is the enforcement layer — not prompt engineering",
"detail": "Prompting AI to 'not access secrets' is not a security boundary. Cedar policy `forbid(principal == Service::\"ai-service\", action == Action::\"read\", resource in Secret::\"*\")` is enforced at the platform layer regardless of what the LLM requests."
},
{
"claim": "RAG over project artifacts is more accurate than generic LLM for project-specific configs",
"detail": "Indexing `schemas/`, `docs/`, and past successful deployments means AI answers are grounded in actual project patterns — not generic infrastructure knowledge that may conflict with project constraints."
}
],
"related_adrs": [
"adr-014-solid-enforcement",
"adr-017-typedialog-web-ui",
"adr-018-secretumvault-integration"
],
"status": "Accepted",
"title": "Schema-Aware AI and RAG — Nickel Contracts Constrain AI Config Generation"
}

View file

@ -0,0 +1,62 @@
{
"dependencies": [],
"features": {
"supports_archive": true,
"supports_pruning": true,
"supports_rpc": true,
"supports_telemetry": true,
"supports_ws": true,
"supports_wss": true
},
"maintainer": {
"name": "Provisioning System",
"url": "https://github.com/paritytech/polkadot"
},
"networks": [
"polkadot",
"kusama",
"westend"
],
"node_types": [
"full",
"light",
"validator"
],
"ports": {
"http": 9933,
"p2p": 30333,
"rpc": 9944,
"ws": 9944
},
"requirements": {
"min_cpu_cores": 4,
"min_disk_gb": 500,
"min_memory_mb": 8192,
"network_required": true
},
"sync_modes": [
"full",
"fast",
"warp"
],
"tags": [
"polkadot",
"node",
"blockchain",
"relay-chain",
"web3"
],
"taskserv": {
"category": "infrastructure",
"description": "Polkadot full/light node for relay chain participation",
"display_name": "Polkadot Node",
"documentation_url": "https://wiki.polkadot.network/docs/maintain-sync",
"name": "polkadot-node",
"subcategory": "blockchain"
},
"version": {
"config_format": "nickel",
"schema": "1.0.0",
"taskserv": "1.15.0"
}
}

View file

@ -0,0 +1,16 @@
{
"artifact": {
"description": "Credentials for an Odoo instance: database password and master admin password.",
"id": "odoo",
"layers": [
{
"description": "contract.ncl — OdooSecrets field definitions",
"media_type": "application/vnd.ontoref.domain.contract.v1",
"required": true
}
],
"media_type": "application/vnd.ontoref.domain.v1",
"uses_registry": "primary",
"version": "1.0.0"
}
}

View file

@ -0,0 +1,110 @@
{
"OCIReg": {
"config": {
"distSpecVersion": "1.0.1",
"extensions": {
"scrub": {
"enable": true,
"interval": "24h"
},
"search": {
"cve": {
"updateInterval": "24h"
},
"enable": true
},
"ui": {
"enable": true
}
},
"http": {
"address": "0.0.0.0",
"port": 5000,
"realm": "zot"
},
"log": {
"audit": "/var/log/zot/zot-audit.log",
"level": "debug",
"output": "/var/log/zot/zot.log"
},
"storage": {
"dedupe": true,
"gc": false,
"remoteCache": false,
"rootDirectory": "/data/zot"
}
},
"copy_paths": [],
"name": "zot",
"oci_bin_path": "/usr/local/bin",
"oci_cmds": "zot zli",
"oci_data": "/data/zot",
"oci_etc": "/etc/zot",
"oci_log": "/var/log/zot",
"oci_memory_high": 30,
"oci_memory_max": 32,
"oci_user": "zot",
"oci_user_group": "zot",
"version": "v2.1.15"
},
"Version": {
"dependencies": [],
"name": "oci_reg",
"version": {
"check_latest": true,
"current": "v2.1.15.",
"grace_period": 86400,
"site": "https://zotregistry.dev/",
"source": "https://github.com/project-zot/zot/releases",
"tags": "https://github.com/project-zot/zot/tags"
}
},
"oci_reg": {
"config": {
"distSpecVersion": "1.0.1",
"extensions": {
"scrub": {
"enable": true,
"interval": "24h"
},
"search": {
"cve": {
"updateInterval": "24h"
},
"enable": true
},
"ui": {
"enable": true
}
},
"http": {
"address": "0.0.0.0",
"port": 5000,
"realm": "zot"
},
"log": {
"audit": "/var/log/zot/zot-audit.log",
"level": "debug",
"output": "/var/log/zot/zot.log"
},
"storage": {
"dedupe": true,
"gc": false,
"remoteCache": false,
"rootDirectory": "/data/zot"
}
},
"copy_paths": [],
"name": "zot",
"oci_bin_path": "/usr/local/bin",
"oci_cmds": "zot zli",
"oci_data": "/data/zot",
"oci_etc": "/etc/zot",
"oci_log": "/var/log/zot",
"oci_memory_high": 30,
"oci_memory_max": 32,
"oci_user": "zot",
"oci_user_group": "zot",
"version": "v2.1.15"
}
}

View file

@ -0,0 +1,35 @@
{
"zot": {
"data_dir": "/var/lib/data/zot",
"image": "ghcr.io/project-zot/zot-linux-arm64:latest",
"mode": "cluster",
"name": "zot",
"namespace": "data",
"operations": {
"delete": true,
"health": true,
"install": true,
"update": true
},
"port": 5000,
"provides": {
"port": 5000,
"service": "zot"
},
"requires": {
"credentials": [],
"ports": [
{
"exposure": "private",
"port": 5000
}
],
"storage": {
"persistent": true,
"size": "50Gi"
}
},
"storage_class": "nfs-shared",
"version": "latest"
}
}

View file

@ -0,0 +1,14 @@
{
"dependencies": [
"os"
],
"name": "vol-prepare",
"version": {
"check_latest": false,
"current": "1.0.0",
"grace_period": 0,
"site": "",
"source": "internal",
"tags": ""
}
}

View file

@ -0,0 +1,30 @@
{
"radicle_httpd": {
"assets_path": null,
"bind_addr": "0.0.0.0",
"bind_port": 8080,
"enabled": true
},
"radicle_node": {
"announce": true,
"bind_addr": "0.0.0.0",
"bind_port": 8776,
"config_path": "/etc/radicle",
"connect_timeout": 10,
"external_addresses": [],
"log_level": "info",
"name": "radicle",
"peer_port": 8777,
"run_path": "/usr/local/bin/rad",
"run_user": {
"group": "radicle",
"home": "/home/radicle",
"name": "radicle"
},
"seeds": [],
"storage_path": "/var/lib/radicle/storage",
"version": "0.10.0",
"web_ui_port": 8080,
"work_path": "/var/lib/radicle"
}
}

View file

@ -0,0 +1,35 @@
{
"best_practices": [
"bp_007"
],
"conflicts_with": [],
"dependencies": [],
"description": "WireGuard VPN server via wg-easy — UDP LB-IPAM endpoint with web UI",
"modes": [
"cluster"
],
"name": "wireguard",
"provides": [
{
"id": "vpn-server",
"interface": "wireguard",
"version": "15"
}
],
"requires": [
{
"capability": "block-storage-csi",
"kind": "Required"
},
{
"capability": "lb-ipam",
"kind": "Required"
}
],
"tags": [
"vpn",
"network",
"security"
],
"version": "15"
}

View file

@ -0,0 +1,113 @@
{
"alternatives_considered": [
{
"option": "Free-form capability tags (Array String) instead of typed CapabilityEntry",
"why_rejected": "Free strings cannot be validated for kind, cannot be queried by type, and cannot carry descriptions. The typed record is required for `provisioning extensions capabilities --type Service` to function and for the `provisioning-dag-integrity` mode to distinguish Required from Optional resolution failures."
},
{
"option": "Single `capabilities` array with a `direction` discriminant (provides/requires encoded as field)",
"why_rejected": "A flat array conflates semantically different operations — providing a capability and requiring one have different validation rules and different consumers. Separate `provides` and `requires` arrays make intent explicit and allow independent schema validation."
},
{
"option": "Encode conflicts_with as capability-level conflicts (A.provides X conflicts with B.provides X)",
"why_rejected": "Capability-level conflicts are more granular but harder to author — taskserv authors must reason about every capability pair. Taskserv-level mutual exclusion (`conflicts_with: [\"containerd\"]`) is the correct granularity for installation-time enforcement and maps directly to the package manager mental model."
},
{
"option": "Central capability registry file (a single capabilities.ncl across all extensions)",
"why_rejected": "A central registry creates a write-contention hotspot when multiple extensions are developed in parallel. Distributed declarations in each metadata.ncl, aggregated by the reflection mode and CLI, achieve the same discoverability with independent authoring."
}
],
"consequences": {
"negative": [
"New taskservs must populate provides/requires/conflicts_with or fail schema validation — increases authoring burden",
"Capability IDs are not validated against a central registry — a typo in `requires[].capability` fails silently if no provider declares the misspelled ID",
"CapabilityKind enum is closed — adding a new kind requires updating `schemas/lib/dag/contracts.ncl` and re-exporting all metadata files that use it"
],
"positive": [
"All 10 built-in taskservs now have typed capability declarations — `provisioning-dag-integrity` runs clean",
"CLI `provisioning extensions capabilities` and `provisioning extensions graph` are powered by these declarations",
"WorkspaceComposition dependency resolution can validate inter-formula capability chains at dag.ncl export time",
"Capability declarations are a first-class artifact — diffable, auditable, versionable in git"
]
},
"constraints": [
{
"check": {
"must_be_empty": true,
"paths": [
"extensions/taskservs/"
],
"pattern": "id = \"[^.]+\"",
"tag": "Grep"
},
"claim": "All capability IDs in provides[].id and requires[].capability must use dot-namespaced format: `<domain>.<name>` (e.g. `kubernetes.api-server`, `storage.ceph-block`)",
"id": "capability-ids-dot-namespaced",
"rationale": "Flat IDs (no dot) are ambiguous and collision-prone. The dot namespace convention is the only disambiguation mechanism without a central registry.",
"scope": "extensions/taskservs/*/metadata.ncl",
"severity": "Hard"
},
{
"check": {
"cmd": "provisioning extensions capabilities 2>/dev/null | length | test { $in > 0 }",
"expect_exit": 0,
"tag": "NuCmd"
},
"claim": "Every taskserv metadata.ncl must declare provides, requires, and conflicts_with — even if as empty arrays",
"id": "all-taskservs-must-declare-capability-fields",
"rationale": "Missing fields are caught by the schema contract but also by `provisioning-dag-integrity`. A taskserv without declarations is invisible to capability resolution — it will never be identified as a provider or dependency.",
"scope": "extensions/taskservs/*/metadata.ncl, schemas/lib/extension-metadata.ncl",
"severity": "Hard"
},
{
"check": {
"cmd": "nu -c 'ls extensions/taskservs/ | get name | each { |d| $d | path basename }'",
"expect_exit": 0,
"tag": "NuCmd"
},
"claim": "conflicts_with[] must contain taskserv directory names (e.g. `\"containerd\"`), not capability IDs",
"id": "conflicts-with-holds-taskserv-names-not-capability-ids",
"rationale": "The conflict resolution algorithm in `provisioning-validate-formula` looks up taskserv names in the extensions registry. Capability IDs in conflicts_with would never match and silently fail to enforce the constraint.",
"scope": "extensions/taskservs/*/metadata.ncl",
"severity": "Hard"
}
],
"context": "ADR-016 introduced typed Formula DAGs for intra-server taskserv execution order. To enable formula dependency resolution at the workspace composition layer (inter-formula DAGs, ADR-021), the Orchestrator needs a machine-readable declaration of what each taskserv produces and what it depends on. Without this, a workspace composition DAG cannot validate that a Formula consuming `kubernetes-api-server` has at least one upstream Formula that provides it — the constraint is implicit and unenforced. Ten built-in taskservs existed with only `name/version/description/supported_providers` metadata — no capability declarations.",
"date": "2026-04-03",
"decision": "Every taskserv `metadata.ncl` file must declare three fields: `provides: Array CapabilityEntry` (capabilities this taskserv makes available after successful execution), `requires: Array CapabilityRequirement` (capabilities this taskserv needs from another provider before it can run), and `conflicts_with: Array String` (taskserv names that are mutually exclusive — installing both would produce an irreconcilable conflict). A `CapabilityEntry` carries `id: String` (dot-namespaced, e.g. `kubernetes.api-server`), `kind: CapabilityKind` (`'Service | 'StorageClass | 'NetworkPolicy | 'Runtime | 'CertManager | 'Monitoring | 'Registry | 'DNS | 'Auth`), and `description: String`. A `CapabilityRequirement` carries `capability: String` (the capability `id`), `kind: RequirementKind` (`'Required | 'Optional`), and `description: String`. These fields are validated by `schemas/lib/extension-metadata.ncl` at Nickel typecheck time and audited at runtime by the `provisioning-dag-integrity` reflection mode.",
"id": "adr-020",
"ontology_check": {
"decision_string": "Extension capability declarations via provides/requires/conflicts_with typed fields in metadata.ncl, validated by extension-metadata schema and provisioning-dag-integrity reflection mode",
"invariants_at_risk": [
"type-safety-nickel",
"config-driven-always"
],
"verdict": "Safe"
},
"rationale": [
{
"claim": "Machine-readable capability declarations enable schema-time resolution validation",
"detail": "The `provisioning-dag-integrity` reflection mode cross-checks every `Required` capability in `requires[]` against the set of `provides[].id` values across all taskservs. An unresolved Required capability is a hard error surfaced before any deployment attempt. Without typed declarations, this check requires reading code comments or documentation."
},
{
"claim": "ConflictsWith enforces mutual exclusion at the registry level",
"detail": "A taskserv pair in `conflicts_with` that both appear in a Formula is caught by `provisioning-validate-formula`. The registry-level declaration makes conflicts auditable and tool-enforceable — no runtime failure needed to discover an incompatible combination."
},
{
"claim": "CapabilityKind enum scopes the semantic surface of each capability",
"detail": "Using a typed enum (`'Service | 'StorageClass | ...`) rather than free-form strings prevents capability ID sprawl. The DAG resolution query `extensions capabilities --type Service` is only possible with a bounded kind set."
},
{
"claim": "Optional vs Required separation allows partial-graph deployments",
"detail": "`'Optional` requirements express soft preferences (e.g. coredns optionally uses an upstream DNS). `'Required` requirements are hard blockers. The distinction enables the orchestrator to warn on unresolved Optional capabilities while failing on unresolved Required ones."
},
{
"claim": "Dot-namespaced capability IDs provide scoping without a global registry",
"detail": "IDs like `kubernetes.api-server`, `storage.ceph-block`, `network.cni` are self-documenting and conflict-resistant without requiring a central registry. The namespace prefix is the domain (kubernetes, storage, network, container, tls, dns, monitoring, identity)."
}
],
"related_adrs": [
"adr-016-workspace-formula-dag"
],
"status": "Accepted",
"title": "Extension Capability Declarations: provides/requires/conflicts_with Taxonomy"
}

View file

@ -0,0 +1,77 @@
{
"alternatives_considered": [
{
"option": "Wrap each call site individually with do { } | complete (existing pattern)",
"why_rejected": "Works only for external commands, not for Nu plugin commands. Plugin commands raise LabeledError — not catchable via complete. Keeping ^nickel export at call sites means all cache benefits are lost."
},
{
"option": "Single ncl-export.nu wrapper delegating to ^nickel export with inline cache check",
"why_rejected": "Duplicates the cache logic already inside nu_plugin_nickel. Two cache implementations with different key strategies would diverge. The plugin is the correct cache owner — the wrapper should delegate to it."
},
{
"option": "Migrate all 124 call sites at once",
"why_rejected": "Risk surface too large. Priority-ordered migration (C1 hot-path first) allows validating cache correctness on the most-exercised paths before touching validation, bootstrap, and diagnostic paths that are harder to test."
}
],
"consequences": {
"negative": [
"nu_plugin_nickel must be registered in the Nu session for performance benefits; unregistered sessions fall back to the `^nickel export` path in nickel-eval-soft (via the catch branch)",
"Block C2/C3 (remaining 120 call sites) are not yet migrated — those paths still use ^nickel export directly"
],
"positive": [
"Hot-path call sites (4 files, C1) are now cache-backed via nu_plugin_nickel",
"Single try/catch location for soft-failure pattern — easy to audit",
"| from json eliminated from migrated call sites"
]
},
"constraints": [
{
"check": {
"must_be_empty": true,
"paths": [
"provisioning/core/nulib/main_provisioning/dispatcher.nu",
"provisioning/core/nulib/main_provisioning/components.nu",
"provisioning/core/nulib/main_provisioning/workflow.nu",
"provisioning/core/nulib/main_provisioning/extensions.nu"
],
"pattern": "^nickel export",
"tag": "Grep"
},
"claim": "Hot-path files (C1) must not contain direct ^nickel export calls after migration",
"id": "c1-no-direct-nickel-export",
"rationale": "Direct ^nickel export in C1 files bypasses the plugin cache, negating the performance benefit of ADR-022. All C1 exports must go through ncl-eval or ncl-eval-soft.",
"scope": "dispatcher.nu, components.nu, workflow.nu, extensions.nu",
"severity": "Hard"
}
],
"context": "After ADR-022 established the ncl-sync daemon and shared cache, Nu call sites needed to be migrated from `^nickel export --format json ... | from json` to the plugin. Two call patterns exist: hard-failure (export failure should propagate as an error — uses `error make`) and soft-failure (export failure should return a fallback value — uses `if $result.exit_code != 0`). Distributing try/catch across 124 call sites would violate the guideline against widespread use of try/catch for Nu plugin commands.",
"date": "2026-04-16",
"decision": "Two wrapper functions in `lib_provisioning/utils/nickel_processor.nu` serve as the single abstraction layer: `ncl-eval [path import_paths]` for hard-failure call sites (error propagates from the plugin directly — no try/catch needed), and `ncl-eval-soft [path import_paths fallback]` for soft-failure call sites (a single try/catch returns `fallback` on any plugin error). Block C1 migrates the four hot-path call sites: `dispatcher.nu` (commands-registry), `components.nu` (comp-ncl-export helper + servers.ncl), `workflow.nu` (wf-ncl-export helper + settings.ncl + state.ncl), `extensions.nu` (metadata.ncl per taskserv). Block C2/C3 cover the remaining operation-path and validation call sites.",
"id": "adr-023",
"ontology_check": {
"decision_string": "ncl-eval + ncl-eval-soft wrappers in nickel_processor.nu replace ^nickel export at hot-path call sites; single try/catch in ncl-eval-soft",
"invariants_at_risk": [
"config-driven-always"
],
"verdict": "Safe"
},
"rationale": [
{
"claim": "ncl-eval-soft isolates the single try/catch to one location",
"detail": "In Nu 0.111.0, try/catch is valid for Nu internal commands (including plugins). However, dispersing try/catch across dozens of call sites increases cognitive load and creates inconsistency. Centralizing it in ncl-eval-soft means the pattern is reviewed once and applied uniformly. Callers declare intent via the `fallback` parameter (`{}`, `[]`, or `null`) rather than embedding error-handling logic inline."
},
{
"claim": "ncl-eval (hard-failure) requires no try/catch — plugin LabeledError propagates naturally",
"detail": "When `nickel-eval` fails, it raises a `LabeledError` that Nu surfaces as a structured error. This is identical in behavior to `error make { msg: ... }` in the existing code. The call site is simply `ncl-eval $path [$ws $prov]` — one line instead of four. No error handling is needed because the error propagation is the correct behavior."
},
{
"claim": "nickel-eval returns Nu values natively, eliminating | from json",
"detail": "The plugin converts `serde_json::Value` to `nu_protocol::Value` via `json_value_to_nu_value`. Call sites receive a Nu record or list directly and can use cell path access (`$data.components`, `$data.dimensions`) without an intermediate string parse step. This removes a class of parse errors where `from json` would fail on empty stdout from a cached result."
}
],
"related_adrs": [
"adr-022-ncl-sync-daemon"
],
"status": "Accepted",
"title": "ncl-eval wrapper: nu_plugin_nickel as the single ^nickel export abstraction in Nu"
}

View file

@ -0,0 +1,112 @@
{
"DefaultAIProvider": {
"enable_query_ai": true,
"enable_template_ai": true,
"enable_webhook_ai": false,
"enabled": false,
"max_tokens": 2048,
"provider": "openai",
"temperature": 0.3,
"timeout": 30
},
"DefaultKmsConfig": {
"auth_method": "certificate",
"server_url": "",
"timeout": 30,
"verify_ssl": true
},
"DefaultRunSet": {
"inventory_file": "./inventory.yaml",
"output_format": "human",
"output_path": "tmp/NOW-deploy",
"use_time": true,
"wait": true
},
"DefaultSecretProvider": {
"provider": "sops"
},
"DefaultSettings": {
"cluster_admin_host": "",
"cluster_admin_port": 22,
"cluster_admin_user": "root",
"clusters_paths": [
"clusters"
],
"clusters_save_path": "/${main_name}/clusters",
"created_clusters_dirpath": "./tmp/NOW_clusters",
"created_taskservs_dirpath": "./tmp/NOW_deployment",
"defaults_provs_dirpath": "./defs",
"defaults_provs_suffix": "_defaults.k",
"main_name": "",
"main_title": "",
"prov_clusters_path": "./clusters",
"prov_data_dirpath": "./data",
"prov_data_suffix": "_settings.k",
"prov_local_bin_path": "./bin",
"prov_resources_path": "./resources",
"servers_paths": [
"servers"
],
"servers_wait_started": 27,
"settings_path": "./settings.yaml"
},
"DefaultSopsConfig": {
"use_age": true
},
"defaults": {
"ai_provider": {
"enable_query_ai": true,
"enable_template_ai": true,
"enable_webhook_ai": false,
"enabled": false,
"max_tokens": 2048,
"provider": "openai",
"temperature": 0.3,
"timeout": 30
},
"kms_config": {
"auth_method": "certificate",
"server_url": "",
"timeout": 30,
"verify_ssl": true
},
"run_set": {
"inventory_file": "./inventory.yaml",
"output_format": "human",
"output_path": "tmp/NOW-deploy",
"use_time": true,
"wait": true
},
"secret_provider": {
"provider": "sops"
},
"settings": {
"cluster_admin_host": "",
"cluster_admin_port": 22,
"cluster_admin_user": "root",
"clusters_paths": [
"clusters"
],
"clusters_save_path": "/${main_name}/clusters",
"created_clusters_dirpath": "./tmp/NOW_clusters",
"created_taskservs_dirpath": "./tmp/NOW_deployment",
"defaults_provs_dirpath": "./defs",
"defaults_provs_suffix": "_defaults.k",
"main_name": "",
"main_title": "",
"prov_clusters_path": "./clusters",
"prov_data_dirpath": "./data",
"prov_data_suffix": "_settings.k",
"prov_local_bin_path": "./bin",
"prov_resources_path": "./resources",
"servers_paths": [
"servers"
],
"servers_wait_started": 27,
"settings_path": "./settings.yaml"
},
"sops_config": {
"use_age": true
}
}
}

View file

@ -0,0 +1,114 @@
{
"orchestrator": {
"batch": {
"checkpointing": {
"enabled": true,
"interval": 100,
"max_checkpoints": 10
},
"metrics": false,
"operation_timeout_minutes": 3,
"parallel_limit": 2,
"rollback": {
"enabled": true,
"max_rollback_depth": 5,
"strategy": "checkpoint_based"
}
},
"build": {
"base_image": "rust:1.82-trixie",
"binary": "provisioning-orchestrator",
"buildkit": {
"cache_mode": "local",
"inline_cache": false,
"parallel_jobs": 2
},
"chef_enabled": true,
"config_file": "config.defaults.toml",
"extra_runtime_pkgs": [],
"features": [],
"health_path": "/health",
"package": "provisioning-orchestrator",
"port": 9090,
"runtime_image": "debian:trixie-slim",
"sccache": {
"bucket": "rust-cache",
"enabled": false,
"region": ""
},
"user_id": 1000
},
"extensions": {
"auto_load": false,
"discovery_interval": 300,
"max_concurrent": 2,
"sandbox": true,
"timeout": 30000
},
"logging": {
"format": "text",
"level": "info"
},
"monitoring": {
"enabled": false,
"health_check": {
"enabled": false,
"interval": 30
},
"metrics": {
"enabled": false,
"interval": 60
},
"resources": {
"alert_threshold": 80,
"cpu": false,
"disk": false,
"memory": false,
"network": false
}
},
"performance": {
"cpu_affinity": false,
"profiling": false
},
"queue": {
"dead_letter_queue": {
"enabled": true,
"max_size": 1000
},
"max_concurrent_tasks": 10,
"metrics": false,
"persist": true,
"priority_queue": false,
"retry_attempts": 3,
"retry_delay": 60000,
"task_timeout": 1800000
},
"server": {
"graceful_shutdown": true,
"host": "127.0.0.1",
"keep_alive": 75,
"max_connections": 512,
"port": 9011,
"request_timeout": 30000,
"shutdown_timeout": 30,
"workers": 2
},
"storage": {
"backend": "filesystem",
"cache": {
"enabled": true,
"eviction_policy": "lru",
"ttl": 3600,
"type": "in_memory"
},
"path": "/var/lib/provisioning/orchestrator/data"
},
"workspace": {
"enabled": true,
"multi_workspace": false,
"name": "default",
"path": "/var/lib/provisioning/orchestrator"
}
}
}

View file

@ -0,0 +1,47 @@
{
"best_practices": [
"bp_001",
"bp_033"
],
"category": "storage",
"conflicts_with": [],
"dependencies": [
"kubernetes",
"cilium",
"longhorn_node_prep"
],
"description": "Longhorn distributed block storage — Helm deploy from CP, manages StorageClass default promotion",
"modes": [
"taskserv"
],
"name": "longhorn",
"provides": [
{
"id": "distributed-block-storage-csi",
"interface": "k8s-csi",
"version": "1.11"
}
],
"requires": [
{
"capability": "kubernetes-cluster",
"kind": "Required"
},
{
"capability": "cni",
"kind": "Required"
},
{
"capability": "longhorn-node-prereqs",
"kind": "Required"
}
],
"tags": [
"storage",
"longhorn",
"csi",
"kubernetes",
"distributed"
],
"version": "1.11.1"
}

View file

@ -0,0 +1,113 @@
{
"alternatives_considered": [
{
"option": "Free-form capability tags (Array String) instead of typed CapabilityEntry",
"why_rejected": "Free strings cannot be validated for kind, cannot be queried by type, and cannot carry descriptions. The typed record is required for `provisioning catalog capabilities --type Service` to function and for the `provisioning-dag-integrity` mode to distinguish Required from Optional resolution failures."
},
{
"option": "Single `capabilities` array with a `direction` discriminant (provides/requires encoded as field)",
"why_rejected": "A flat array conflates semantically different operations — providing a capability and requiring one have different validation rules and different consumers. Separate `provides` and `requires` arrays make intent explicit and allow independent schema validation."
},
{
"option": "Encode conflicts_with as capability-level conflicts (A.provides X conflicts with B.provides X)",
"why_rejected": "Capability-level conflicts are more granular but harder to author — taskserv authors must reason about every capability pair. Taskserv-level mutual exclusion (`conflicts_with: [\"containerd\"]`) is the correct granularity for installation-time enforcement and maps directly to the package manager mental model."
},
{
"option": "Central capability registry file (a single capabilities.ncl across all extensions)",
"why_rejected": "A central registry creates a write-contention hotspot when multiple extensions are developed in parallel. Distributed declarations in each metadata.ncl, aggregated by the reflection mode and CLI, achieve the same discoverability with independent authoring."
}
],
"consequences": {
"negative": [
"New taskservs must populate provides/requires/conflicts_with or fail schema validation — increases authoring burden",
"Capability IDs are not validated against a central registry — a typo in `requires[].capability` fails silently if no provider declares the misspelled ID",
"CapabilityKind enum is closed — adding a new kind requires updating `schemas/lib/dag/contracts.ncl` and re-exporting all metadata files that use it"
],
"positive": [
"All 10 built-in taskservs now have typed capability declarations — `provisioning-dag-integrity` runs clean",
"CLI `provisioning catalog capabilities` and `provisioning extensions graph` are powered by these declarations",
"WorkspaceComposition dependency resolution can validate inter-formula capability chains at dag.ncl export time",
"Capability declarations are a first-class artifact — diffable, auditable, versionable in git"
]
},
"constraints": [
{
"check": {
"must_be_empty": true,
"paths": [
"catalog/taskservs/"
],
"pattern": "id = \"[^.]+\"",
"tag": "Grep"
},
"claim": "All capability IDs in provides[].id and requires[].capability must use dot-namespaced format: `<domain>.<name>` (e.g. `kubernetes.api-server`, `storage.ceph-block`)",
"id": "capability-ids-dot-namespaced",
"rationale": "Flat IDs (no dot) are ambiguous and collision-prone. The dot namespace convention is the only disambiguation mechanism without a central registry.",
"scope": "catalog/taskservs/*/metadata.ncl",
"severity": "Hard"
},
{
"check": {
"cmd": "provisioning catalog capabilities 2>/dev/null | length | test { $in > 0 }",
"expect_exit": 0,
"tag": "NuCmd"
},
"claim": "Every taskserv metadata.ncl must declare provides, requires, and conflicts_with — even if as empty arrays",
"id": "all-taskservs-must-declare-capability-fields",
"rationale": "Missing fields are caught by the schema contract but also by `provisioning-dag-integrity`. A taskserv without declarations is invisible to capability resolution — it will never be identified as a provider or dependency.",
"scope": "catalog/taskservs/*/metadata.ncl, schemas/lib/extension-metadata.ncl",
"severity": "Hard"
},
{
"check": {
"cmd": "nu -c 'ls catalog/taskservs/ | get name | each { |d| $d | path basename }'",
"expect_exit": 0,
"tag": "NuCmd"
},
"claim": "conflicts_with[] must contain taskserv directory names (e.g. `\"containerd\"`), not capability IDs",
"id": "conflicts-with-holds-taskserv-names-not-capability-ids",
"rationale": "The conflict resolution algorithm in `provisioning-validate-formula` looks up taskserv names in the extensions registry. Capability IDs in conflicts_with would never match and silently fail to enforce the constraint.",
"scope": "catalog/taskservs/*/metadata.ncl",
"severity": "Hard"
}
],
"context": "ADR-016 introduced typed Formula DAGs for intra-server taskserv execution order. To enable formula dependency resolution at the workspace composition layer (inter-formula DAGs, ADR-021), the Orchestrator needs a machine-readable declaration of what each taskserv produces and what it depends on. Without this, a workspace composition DAG cannot validate that a Formula consuming `kubernetes-api-server` has at least one upstream Formula that provides it — the constraint is implicit and unenforced. Ten built-in taskservs existed with only `name/version/description/supported_providers` metadata — no capability declarations.",
"date": "2026-04-03",
"decision": "Every taskserv `metadata.ncl` file must declare three fields: `provides: Array CapabilityEntry` (capabilities this taskserv makes available after successful execution), `requires: Array CapabilityRequirement` (capabilities this taskserv needs from another provider before it can run), and `conflicts_with: Array String` (taskserv names that are mutually exclusive — installing both would produce an irreconcilable conflict). A `CapabilityEntry` carries `id: String` (dot-namespaced, e.g. `kubernetes.api-server`), `kind: CapabilityKind` (`'Service | 'StorageClass | 'NetworkPolicy | 'Runtime | 'CertManager | 'Monitoring | 'Registry | 'DNS | 'Auth`), and `description: String`. A `CapabilityRequirement` carries `capability: String` (the capability `id`), `kind: RequirementKind` (`'Required | 'Optional`), and `description: String`. These fields are validated by `schemas/lib/extension-metadata.ncl` at Nickel typecheck time and audited at runtime by the `provisioning-dag-integrity` reflection mode.",
"id": "adr-020",
"ontology_check": {
"decision_string": "Extension capability declarations via provides/requires/conflicts_with typed fields in metadata.ncl, validated by extension-metadata schema and provisioning-dag-integrity reflection mode",
"invariants_at_risk": [
"type-safety-nickel",
"config-driven-always"
],
"verdict": "Safe"
},
"rationale": [
{
"claim": "Machine-readable capability declarations enable schema-time resolution validation",
"detail": "The `provisioning-dag-integrity` reflection mode cross-checks every `Required` capability in `requires[]` against the set of `provides[].id` values across all taskservs. An unresolved Required capability is a hard error surfaced before any deployment attempt. Without typed declarations, this check requires reading code comments or documentation."
},
{
"claim": "ConflictsWith enforces mutual exclusion at the registry level",
"detail": "A taskserv pair in `conflicts_with` that both appear in a Formula is caught by `provisioning-validate-formula`. The registry-level declaration makes conflicts auditable and tool-enforceable — no runtime failure needed to discover an incompatible combination."
},
{
"claim": "CapabilityKind enum scopes the semantic surface of each capability",
"detail": "Using a typed enum (`'Service | 'StorageClass | ...`) rather than free-form strings prevents capability ID sprawl. The DAG resolution query `extensions capabilities --type Service` is only possible with a bounded kind set."
},
{
"claim": "Optional vs Required separation allows partial-graph deployments",
"detail": "`'Optional` requirements express soft preferences (e.g. coredns optionally uses an upstream DNS). `'Required` requirements are hard blockers. The distinction enables the orchestrator to warn on unresolved Optional capabilities while failing on unresolved Required ones."
},
{
"claim": "Dot-namespaced capability IDs provide scoping without a global registry",
"detail": "IDs like `kubernetes.api-server`, `storage.ceph-block`, `network.cni` are self-documenting and conflict-resistant without requiring a central registry. The namespace prefix is the domain (kubernetes, storage, network, container, tls, dns, monitoring, identity)."
}
],
"related_adrs": [
"adr-016-workspace-formula-dag"
],
"status": "Accepted",
"title": "Extension Capability Declarations: provides/requires/conflicts_with Taxonomy"
}

View file

@ -0,0 +1,24 @@
{
"DefaultOS": {
"admin_group": "devadm",
"admin_user": "devadm",
"name": "os",
"src_user_path": "devadm-home",
"ssh_keys": "",
"sysctl": {
"disable_ipv6": false
}
},
"defaults": {
"os": {
"admin_group": "devadm",
"admin_user": "devadm",
"name": "os",
"src_user_path": "devadm-home",
"ssh_keys": "",
"sysctl": {
"disable_ipv6": false
}
}
}
}

View file

@ -0,0 +1,10 @@
{
"dependencies": [],
"name": "provisioning",
"version": {
"check_latest": false,
"current": "3.5.0",
"grace_period": 86400,
"site": "Internal provisioning component"
}
}

View file

@ -0,0 +1,265 @@
{
"Contracts": {},
"Defaults": {
"Cache": {
"adapter": "memory",
"db": 0,
"enabled": false,
"host": "localhost",
"max_entries": 10000,
"password": "",
"port": 6379,
"ttl": 86400
},
"Database": {
"conn_max_lifetime": 3600,
"host": "localhost",
"log_sql": false,
"max_idle_conns": 10,
"max_open_conns": 100,
"name": "forgejo",
"password": "",
"path": "/var/lib/forgejo/data/forgejo.db",
"port": 5432,
"ssl_mode": "disable",
"typ": "sqlite",
"user": "forgejo"
},
"Forgejo": {
"app_name": "Forgejo",
"app_path": "/opt/forgejo",
"cache": {
"adapter": "memory",
"db": 0,
"enabled": false,
"host": "localhost",
"max_entries": 10000,
"password": "",
"port": 6379,
"ttl": 86400
},
"custom_path": "/etc/forgejo/custom",
"data_path": "/var/lib/forgejo/data",
"database": {
"conn_max_lifetime": 3600,
"host": "localhost",
"log_sql": false,
"max_idle_conns": 10,
"max_open_conns": 100,
"name": "forgejo",
"password": "",
"path": "/var/lib/forgejo/data/forgejo.db",
"port": 5432,
"ssl_mode": "disable",
"typ": "sqlite",
"user": "forgejo"
},
"enable_gist": true,
"enable_issues": true,
"enable_migrations": true,
"enable_packages": false,
"enable_pull_requests": true,
"enable_wiki": true,
"http": {
"cert_file": "",
"cors_enabled": false,
"cors_origins": [],
"http_addr": "127.0.0.1",
"http_port": 3000,
"key_file": "",
"protocol": "http",
"root_url": "http://localhost:3000",
"tls_ciphers": "",
"tls_min_version": "1.2"
},
"log_format": "console",
"log_level": "info",
"log_path": "/var/log/forgejo",
"mail": {
"enabled": false,
"protocol": "smtp",
"skip_verify": false,
"smtp_addr": "localhost",
"smtp_from": "noreply@example.com",
"smtp_password": "",
"smtp_port": 25,
"smtp_user": "",
"use_tls": false
},
"name": "forgejo",
"oauth2": {
"enabled": true,
"jwt_secret": ""
},
"performance": {
"max_connections": 100,
"request_timeout": 60,
"slow_query_log": false,
"slow_query_threshold": 1000,
"workers": 4
},
"repository": {
"default_branch": "main",
"default_private": false,
"enable_push_create_org": false,
"enable_push_create_user": false,
"lfs_enabled": false,
"lfs_max_file_size": 1073741824,
"root_path": "/var/lib/forgejo/repos"
},
"security": {
"cookie_secure": true,
"cookie_username": "gitea_awesome",
"csrf_cookie_http_only": true,
"csrf_cookie_secure": true,
"disable_registration": false,
"enable_openid": true,
"enable_openid_signup": false,
"login_remember_days": 7,
"min_password_length": 6,
"register_email_confirm": false
},
"session": {
"cookie_http_only": true,
"cookie_name": "i_like_forgejo",
"cookie_secure": true,
"expire_time": 604800,
"gc_interval": 86400,
"provider": "memory",
"same_site": "Lax"
},
"ssh": {
"enabled": true,
"key_test_path": "/var/lib/forgejo/ssh",
"listen_addr": "127.0.0.1",
"listen_port": 2222,
"min_priv_key_length": 1024,
"root_path": "/var/lib/forgejo/.ssh",
"server_ciphers": "aes128-ctr, aes192-ctr, aes256-ctr",
"server_key_exchange_algorithms": "diffie-hellman-group14-sha1",
"server_macs": "hmac-sha2-256, hmac-sha2-256-etm@openssh.com"
},
"storage": {
"path": "/var/lib/forgejo/data",
"s3_access_key": "",
"s3_bucket": "",
"s3_endpoint": "",
"s3_path_style": true,
"s3_region": "us-east-1",
"s3_secret_key": "",
"typ": "local"
},
"temp_path": "/tmp/forgejo",
"time_format": "RFC1123",
"user": {
"gid": 5000,
"home": "/var/lib/forgejo",
"name": "forgejo",
"shell": "/usr/sbin/nologin",
"uid": 5000
},
"version": "latest"
},
"HTTP": {
"cert_file": "",
"cors_enabled": false,
"cors_origins": [],
"http_addr": "127.0.0.1",
"http_port": 3000,
"key_file": "",
"protocol": "http",
"root_url": "http://localhost:3000",
"tls_ciphers": "",
"tls_min_version": "1.2"
},
"Mail": {
"enabled": false,
"protocol": "smtp",
"skip_verify": false,
"smtp_addr": "localhost",
"smtp_from": "noreply@example.com",
"smtp_password": "",
"smtp_port": 25,
"smtp_user": "",
"use_tls": false
},
"OAuth2": {
"enabled": true,
"jwt_secret": ""
},
"Performance": {
"max_connections": 100,
"request_timeout": 60,
"slow_query_log": false,
"slow_query_threshold": 1000,
"workers": 4
},
"Repository": {
"default_branch": "main",
"default_private": false,
"enable_push_create_org": false,
"enable_push_create_user": false,
"lfs_enabled": false,
"lfs_max_file_size": 1073741824,
"root_path": "/var/lib/forgejo/repos"
},
"SSH": {
"enabled": true,
"key_test_path": "/var/lib/forgejo/ssh",
"listen_addr": "127.0.0.1",
"listen_port": 2222,
"min_priv_key_length": 1024,
"root_path": "/var/lib/forgejo/.ssh",
"server_ciphers": "aes128-ctr, aes192-ctr, aes256-ctr",
"server_key_exchange_algorithms": "diffie-hellman-group14-sha1",
"server_macs": "hmac-sha2-256, hmac-sha2-256-etm@openssh.com"
},
"Security": {
"cookie_secure": true,
"cookie_username": "gitea_awesome",
"csrf_cookie_http_only": true,
"csrf_cookie_secure": true,
"disable_registration": false,
"enable_openid": true,
"enable_openid_signup": false,
"login_remember_days": 7,
"min_password_length": 6,
"register_email_confirm": false
},
"Session": {
"cookie_http_only": true,
"cookie_name": "i_like_forgejo",
"cookie_secure": true,
"expire_time": 604800,
"gc_interval": 86400,
"provider": "memory",
"same_site": "Lax"
},
"Storage": {
"path": "/var/lib/forgejo/data",
"s3_access_key": "",
"s3_bucket": "",
"s3_endpoint": "",
"s3_path_style": true,
"s3_region": "us-east-1",
"s3_secret_key": "",
"typ": "local"
},
"User": {
"gid": 5000,
"home": "/var/lib/forgejo",
"name": "forgejo",
"shell": "/usr/sbin/nologin",
"uid": 5000
}
},
"Version": {
"name": "forgejo",
"version": {
"check_latest": true,
"current": "latest",
"grace_period": 86400,
"source": "https://github.com/forgejo/forgejo/releases"
}
}
}

View file

@ -0,0 +1,99 @@
{
"alternatives_considered": [
{
"option": "Direct HTTP polling between services",
"why_rejected": "Creates coupling between services and requires each service to know the addresses of others. No delivery guarantee, no audit trail, polling adds latency."
},
{
"option": "Redis Pub/Sub for event distribution",
"why_rejected": "Redis Pub/Sub has no persistence — messages are lost if no subscriber is listening. No work-queue semantics, no backpressure, no durable audit trail."
}
],
"consequences": {
"negative": [
"nats-server is a required external process in solo mode, adding a startup step",
"Message ordering within a subject is guaranteed but cross-subject ordering is not",
"JetStream persistence requires disk space for AUDIT stream (90-day retention)",
"Pull consumers in VAULT stream add one round-trip vs direct HTTP for lease issuance"
],
"positive": [
"Full audit trail: every state transition is a durable NATS message consumed by AuditCollector",
"No polling: Control Center streams task status to browser via WebSocket",
"Backpressure: JetStream consumers ack explicitly; unacknowledged messages are redelivered",
"SOLID enforcement: CLI can only submit to provisioning.tasks.submitted; cannot call provider APIs directly"
]
},
"constraints": [
{
"check": {
"cmd": "rg 'publish|nats' platform/crates/ -l | xargs rg -l 'token|secret|password|key'",
"expect_exit": 1,
"tag": "NuCmd"
},
"claim": "Actual credentials (tokens, secrets, keys) must never be published to any NATS subject",
"id": "credentials-never-in-nats",
"rationale": "Credentials in NATS messages would be visible to all subscribers on the subject. Only lease_id, task_id, and session_id travel over NATS; actual secrets are fetched over HTTPS from Vault.",
"scope": "platform/crates/orchestrator/src/, platform/crates/platform-nats/",
"severity": "Hard"
},
{
"check": {
"cmd": "rg 'create_stream|add_stream' platform/crates/ --include='*.rs' -l | grep -v orchestrator",
"expect_exit": 1,
"tag": "NuCmd"
},
"claim": "JetStream stream definitions (TASKS, VAULT, AUTH, WORKSPACE, AUDIT, HEALTH) are created by Orchestrator on startup and must not be redefined by other services",
"id": "six-streams-defined-by-orchestrator",
"rationale": "Single point of stream definition prevents conflicting stream configurations. Other services are consumers only.",
"scope": "platform/crates/orchestrator/src/nats.rs",
"severity": "Hard"
},
{
"check": {
"cmd": "rg '\"[a-z]' platform/crates/ --include='*.rs' | grep -v 'provisioning\\.'",
"expect_exit": 1,
"tag": "NuCmd"
},
"claim": "All NATS subjects must be under the provisioning.> hierarchy with the stream-to-subject mapping documented in schemas/platform/common/nats.ncl",
"id": "nats-subject-hierarchy",
"rationale": "Consistent subject hierarchy enables subject-level access control and prevents cross-context pollution.",
"scope": "platform/crates/platform-nats/",
"severity": "Soft"
}
],
"context": "The provisioning platform has four runtime execution contexts — CLI, platform services (Orchestrator, Control Center, Vault, Extension Registry), remote taskservs, and AI/MCP — that must coordinate without leaking credentials or state into transient channels. Prior to this decision, services communicated via direct HTTP polling, shared filesystem state, and environment variables. This created audit gaps (no durable record of which service triggered which operation), credential leakage (provider tokens passed as env vars or written to disk by the CLI process), race conditions (multiple CLI invocations racing over shared config files with no delivery guarantee), and no backpressure (a slow consumer could starve or block a fast producer with no visibility).",
"date": "2026-02-17",
"decision": "NATS with JetStream is the exclusive inter-service event broker. All inter-service communication that is not synchronous credential retrieval (Vault HTTPS) or session validation (Control Center HTTPS) must use NATS subjects under the `provisioning.>` hierarchy. Six JetStream streams are defined at startup by Orchestrator: TASKS (work queue), VAULT (interest), AUTH (interest), WORKSPACE (7-day limits), AUDIT (90-day limits), HEALTH (interest). Credentials never travel over NATS — only identifiers (lease_id, task_id, session_id) are published. Solo mode: nats-server -js as child process. Multi-user: external NATS cluster.",
"id": "adr-012",
"ontology_check": {
"decision_string": "NATS JetStream is the exclusive inter-service event broker; credentials never travel over NATS; six streams defined by Orchestrator",
"invariants_at_risk": [
"solid-boundaries"
],
"verdict": "Safe"
},
"rationale": [
{
"claim": "At-least-once delivery with durable persistence",
"detail": "JetStream provides durable message persistence for task log replay and audit trail reconstruction. Pull consumers ack explicitly; unacknowledged messages are redelivered."
},
{
"claim": "Work-queue semantics enforce SOLID — CLI cannot call providers directly",
"detail": "CLI submits to provisioning.tasks.submitted only. It cannot call provider APIs directly. This is the primary structural enforcement of the SOLID boundary between CLI and Orchestrator."
},
{
"claim": "Push semantics for real-time status streaming without polling",
"detail": "Control Center streams task status to browser via WebSocket without polling Orchestrator. NATS push consumers bridge the event stream to the WebSocket layer."
},
{
"claim": "Multi-tenant subject namespacing maps to bounded contexts",
"detail": "The provisioning.> hierarchy with six streams maps each stream to its bounded context (tasks, vault, auth, workspace, audit, health). Each service subscribes only to its own subjects."
}
],
"related_adrs": [
"adr-014-solid-enforcement",
"adr-015-solo-mode-architecture"
],
"status": "Accepted",
"title": "NATS JetStream as Exclusive Inter-Service Event Broker"
}

View file

@ -0,0 +1,124 @@
{
"config": {
"api_version": "10.0.0",
"audit_logging": false,
"dynamic_ownership": true,
"enforced_security": true,
"listen_tcp": false,
"listen_unix": true,
"max_connections": 512,
"mem_limit_mb": 512,
"name": "libvirt",
"socket_activation": true,
"tls_enabled": false,
"unix_sock_group": "libvirt",
"version": "latest"
},
"dependencies": {
"arch_support": [
"x86_64",
"aarch64"
],
"conflicts": [
"virtualbox",
"xen",
"docker-vm"
],
"health_checks": [
{
"command": "systemctl is-active libvirtd",
"interval": 60,
"name": "libvirtd_running",
"timeout": 5
},
{
"command": "test -S /var/run/libvirt/libvirt-sock",
"interval": 60,
"name": "libvirt_socket",
"timeout": 5
},
{
"command": "virsh version --quiet",
"interval": 60,
"name": "virsh_connectivity",
"timeout": 10
}
],
"name": "libvirt",
"notes": [
"Depends on KVM for hardware-accelerated virtualization",
"Provides unified API for VM management via virsh/libvirt-client",
"Creates libvirt daemon (libvirtd) for VM lifecycle management",
"Manages virtual networks, storage pools, and other resources",
"Supports remote management via TCP/Unix sockets"
],
"optional": [
"qemu"
],
"os_support": [
"linux"
],
"phases": [
{
"critical": true,
"name": "pre-install",
"script": "prepare",
"timeout": 60
},
{
"critical": true,
"name": "install",
"script": "install-libvirt.sh install",
"timeout": 300
},
{
"critical": true,
"name": "post-install",
"script": "_postrun",
"timeout": 120
},
{
"checks": [
{
"name": "libvirtd",
"timeout": 10,
"type": "service"
},
{
"path": "/var/run/libvirt/libvirt-sock",
"timeout": 5,
"type": "socket"
}
],
"name": "validate"
}
],
"provides": [
"virtualization-management",
"vm-api",
"hypervisor-api"
],
"release": "2024-01-15",
"requires": [
"kvm"
],
"resources": {
"cpu": {
"recommended": 2000,
"required": 1000
},
"disk": {
"recommended": 200,
"required": 50
},
"memory": {
"recommended": 2048,
"required": 1024
},
"network": false,
"privileged": true
},
"timeout": 600,
"version": "10.0.0"
}
}

View file

@ -0,0 +1,35 @@
{
"best_practices": [
"bp_007",
"bp_016"
],
"conflicts_with": [],
"dependencies": [],
"description": "SurrealDB multi-model database — document, graph, and relational in one engine",
"modes": [
"cluster"
],
"name": "surrealdb",
"provides": [
{
"id": "surreal-database",
"interface": "http+ws",
"version": "3.0"
}
],
"requires": [
{
"capability": "block-storage-csi",
"kind": "Required"
}
],
"tags": [
"database",
"nosql",
"graph",
"document",
"data",
"appserv"
],
"version": "3.0.5"
}

View file

@ -0,0 +1,112 @@
{
"wireguard": {
"client_allowed_ips": [
"10.200.0.0/24"
],
"concerns": {
"backup": {
"kind": "disabled",
"reason": "stateless: peer keys + config in git/SOPS; runtime state is connection metadata only"
},
"certs": {
"kind": "disabled",
"reason": "no ACME issuer required"
},
"dns": {
"kind": "disabled",
"reason": "no DNS records owned by this component"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "disabled",
"reason": "WireGuard uses its own Noise-protocol mTLS; no x509 termination"
}
},
"credential_generators": {
"PASSWORD_HASH": {
"kind": "bcrypt_password",
"source": "WGUI_PASSWORD"
},
"WGUI_PASSWORD": {
"kind": "random_password",
"length": 32
},
"WG_PRIVATE_KEY": {
"kind": "wg_privkey"
},
"WG_PUBLIC_KEY": {
"kind": "wg_pubkey",
"source": "WG_PRIVATE_KEY"
}
},
"dns": [
"1.1.1.1",
"8.8.8.8"
],
"fip_name": "",
"image": "ghcr.io/wg-easy/wg-easy:15",
"keepalive": 25,
"lb_ipam_ip": "",
"listen_port": 51820,
"live_check": {
"scope": "cp_only",
"strategy": "k8s_pods"
},
"mode": "cluster",
"mtu": 1420,
"name": "wireguard",
"namespace": "vpn",
"node": "",
"operations": {
"delete": true,
"health": true,
"install": true,
"restart": true,
"update": true
},
"provides": {
"ports": [
51820
],
"service": "wireguard"
},
"requires": {
"credentials": [
"WGUI_PASSWORD",
"PASSWORD_HASH",
"WG_PRIVATE_KEY",
"WG_PUBLIC_KEY"
],
"ports": [
{
"exposure": "public",
"port": 51820,
"protocol": "UDP"
},
{
"exposure": "internal",
"port": 51821,
"protocol": "TCP"
}
],
"storage": {
"persistent": true,
"size": "1Gi"
},
"storage_class": "hcloud-volumes"
},
"server_ip": "10.200.0.1",
"ui_port": 51821,
"version": "15",
"vpn_subnet": "10.200.0.0/24"
}
}

View file

@ -0,0 +1,46 @@
{
"DefaultDemocraticCSI": {
"access_mode": "ReadWriteMany",
"allow_volume_expansion": false,
"nfs_server": "",
"nfs_share_base_path": "/shared",
"reclaim_policy": "Retain",
"storage_class_name": "democratic-csi-nfs",
"version": "0.14.6"
},
"DemocraticCSI": {
"access_mode": "ReadWriteMany",
"allow_volume_expansion": false,
"nfs_server": "",
"nfs_share_base_path": "/shared",
"reclaim_policy": "Retain",
"storage_class_name": "democratic-csi-nfs",
"version": "0.14.6"
},
"Version": {
"dependencies": [
"kubernetes",
"cilium"
],
"name": "democratic-csi",
"version": {
"check_latest": true,
"current": "0.14.6",
"grace_period": 86400,
"site": "",
"source": "github.com/democratic-csi/democratic-csi",
"tags": ""
}
},
"defaults": {
"democratic_csi": {
"access_mode": "ReadWriteMany",
"allow_volume_expansion": false,
"nfs_server": "",
"nfs_share_base_path": "/shared",
"reclaim_policy": "Retain",
"storage_class_name": "democratic-csi-nfs",
"version": "0.14.6"
}
}
}

View file

@ -0,0 +1,16 @@
{
"arch_support": [
"x86_64",
"aarch64"
],
"category": "hypervisor",
"description": "Docker Desktop VM hypervisor for macOS/Windows",
"name": "docker-vm",
"platform_support": [
"macos",
"windows",
"linux"
],
"release": "2024-01-15",
"version": "latest"
}

View file

@ -0,0 +1,120 @@
{
"guards": [],
"id": "provisioning-assess",
"postconditions": [
"Assessment report identifies infrastructure nodes from child project ontology",
"Report cross-references available providers and taskservs",
"Gaps between requirements and available extensions are listed"
],
"preconditions": [
"{child_project_dir}/.ontology/core.ncl exists and is valid Nickel",
"nickel is available in PATH",
"./scripts/ontoref is available and executable",
"provisioning platform catalog (catalog/providers/, catalog/taskservs/) is readable"
],
"steps": [
{
"action": "export_child_ontology",
"actor": "Agent",
"cmd": "nickel export {child_project_dir}/.ontology/core.ncl",
"depends_on": [],
"id": "read_child_core",
"note": "Export the child project's core ontology. Identifies nodes with infrastructure implications.",
"on_error": {
"backoff_s": 5,
"max": 3,
"strategy": "Stop"
}
},
{
"action": "catalog_providers",
"actor": "Agent",
"cmd": "ls catalog/providers/ 2>/dev/null || echo 'no-providers'",
"depends_on": [],
"id": "list_available_providers",
"note": "List available provider extensions. Used to cross-reference child requirements.",
"on_error": {
"backoff_s": 5,
"max": 3,
"strategy": "Continue"
}
},
{
"action": "catalog_taskservs",
"actor": "Agent",
"cmd": "ls catalog/taskservs/ 2>/dev/null || echo 'no-taskservs'",
"depends_on": [],
"id": "list_available_taskservs",
"note": "List available taskservs. Used to cross-reference child requirements.",
"on_error": {
"backoff_s": 5,
"max": 3,
"strategy": "Continue"
}
},
{
"action": "filter_infra_relevant_nodes",
"actor": "Agent",
"cmd": "nickel export {child_project_dir}/.ontology/core.ncl | jq '[.nodes[] | select(.level == \"Project\" or .level == \"Axiom\")]'",
"depends_on": [
{
"kind": "OnSuccess",
"step": "read_child_core"
}
],
"id": "identify_infra_nodes",
"note": "Filter nodes that imply infrastructure requirements — Project and Axiom level nodes typically require provisioning.",
"on_error": {
"backoff_s": 5,
"max": 3,
"strategy": "Stop"
}
},
{
"action": "match_requirements_to_providers",
"actor": "Agent",
"cmd": "nickel export {child_project_dir}/.ontology/core.ncl | jq -r '.nodes[] | select(.level == \"Project\") | .id' | while read id; do echo \"$id: check catalog/providers/$id 2>/dev/null || echo 'no match'\"; done",
"depends_on": [
{
"kind": "OnSuccess",
"step": "identify_infra_nodes"
},
{
"kind": "Always",
"step": "list_available_providers"
}
],
"id": "cross_reference_providers",
"note": "Cross-reference identified infrastructure nodes with available providers.",
"on_error": {
"backoff_s": 5,
"max": 3,
"strategy": "Continue"
}
},
{
"action": "write_assessment_output",
"actor": "Agent",
"cmd": "./scripts/ontoref describe {child_project_name} --format assessment",
"depends_on": [
{
"kind": "Always",
"step": "cross_reference_providers"
},
{
"kind": "Always",
"step": "list_available_taskservs"
}
],
"id": "generate_assessment_report",
"note": "Generate the final assessment report: what the child project needs from provisioning, what is available, what gaps exist.",
"on_error": {
"backoff_s": 5,
"max": 3,
"strategy": "Stop"
}
}
],
"strategy": "Override",
"trigger": "Assess a project's infrastructure requirements against the provisioning platform"
}

View file

@ -0,0 +1,79 @@
{
"buildkit_runner": {
"concerns": {
"backup": {
"kind": "disabled",
"reason": "ephemeral by design (ADR-039); persistent state captured by SystemBackupDef.builder_env"
},
"certs": {
"kind": "disabled",
"reason": "no ACME issuer required"
},
"dns": {
"kind": "disabled",
"reason": "no DNS records owned by this component"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "disabled",
"reason": "internal cluster service, mTLS not required"
}
},
"default_size": {
"cpu": 4,
"disk_gb": 40,
"memory_gb": 8
},
"golden_image_name": "buildkit-runner-golden",
"golden_image_rebuild_cron": "0 3 * * 0",
"hcloud": {
"location": "fsn1",
"server_type_pool": [
"cax21",
"cax31",
"cax41",
"ccx13"
]
},
"lease": {
"max_duration_min": 60
},
"mode": "ephemeral_vm",
"name": "buildkit_runner",
"operations": {
"health": true,
"install": true
},
"provides": {
"interface": "buildkit-runner",
"service": "buildkit_runner"
},
"requires": {
"capabilities": [
"vm-lifecycle",
"image-storage-s3",
"ssh-access"
],
"credentials": [
"HCLOUD_TOKEN",
"ORCHESTRATOR_SSH_PUBKEY"
]
},
"ssh": {
"orchestrator_pubkey_secret": "orchestrator-ssh-pubkey"
},
"storage": {
"persistent": false
},
"version": "1.0.0"
}
}

View file

@ -0,0 +1,150 @@
{
"DefaultWoodpecker": {
"database": "woodpecker",
"db_host": "postgresql.data.svc",
"forgejo_url": "http://forgejo.data.svc:3000",
"grpc_port": 9000,
"http_port": 8000,
"image_agent": "woodpecker/agent:latest",
"image_server": "woodpecker/server:latest",
"mode": "cluster",
"name": "woodpecker",
"namespace": "data",
"operations": {
"delete": true,
"health": true,
"install": true,
"update": true
},
"provides": {
"port": 8000,
"service": "woodpecker"
},
"requires": {
"credentials": [
"WOODPECKER_AGENT_SECRET",
"WOODPECKER_FORGEJO_SECRET",
"POSTGRES_PASSWORD"
],
"ports": [
{
"exposure": "public",
"port": 80
},
{
"exposure": "public",
"port": 443
}
],
"storage": {
"persistent": true,
"size": "5Gi"
}
},
"version": "latest"
},
"Version": {
"nickel_api": "1.0",
"version": "1.0.0"
},
"Woodpecker": {
"database": "woodpecker",
"db_host": "postgresql.data.svc",
"forgejo_url": "http://forgejo.data.svc:3000",
"grpc_port": 9000,
"http_port": 8000,
"image_agent": "woodpecker/agent:latest",
"image_server": "woodpecker/server:latest",
"mode": "cluster",
"name": "woodpecker",
"namespace": "data",
"operations": {
"backup": false,
"config": false,
"delete": true,
"health": true,
"install": true,
"reinstall": false,
"restart": false,
"restore": false,
"scripts": false,
"update": true
},
"provides": {
"databases": [],
"endpoints": [],
"port": 8000,
"service": "woodpecker"
},
"requires": {
"credentials": [
"WOODPECKER_AGENT_SECRET",
"WOODPECKER_FORGEJO_SECRET",
"POSTGRES_PASSWORD"
],
"ports": [
{
"exposure": "public",
"port": 80,
"protocol": "TCP"
},
{
"exposure": "public",
"port": 443,
"protocol": "TCP"
}
],
"storage": {
"persistent": true,
"size": "5Gi"
}
},
"version": "latest"
},
"defaults": {
"woodpecker": {
"database": "woodpecker",
"db_host": "postgresql.data.svc",
"forgejo_url": "http://forgejo.data.svc:3000",
"grpc_port": 9000,
"http_port": 8000,
"image_agent": "woodpecker/agent:latest",
"image_server": "woodpecker/server:latest",
"mode": "cluster",
"name": "woodpecker",
"namespace": "data",
"operations": {
"delete": true,
"health": true,
"install": true,
"update": true
},
"provides": {
"port": 8000,
"service": "woodpecker"
},
"requires": {
"credentials": [
"WOODPECKER_AGENT_SECRET",
"WOODPECKER_FORGEJO_SECRET",
"POSTGRES_PASSWORD"
],
"ports": [
{
"exposure": "public",
"port": 80
},
{
"exposure": "public",
"port": 443
}
],
"storage": {
"persistent": true,
"size": "5Gi"
}
},
"version": "latest"
}
}
}

View file

@ -0,0 +1,101 @@
{
"database": {
"connection_timeout": 15000,
"host": "postgres.provisioning.svc",
"pool_size": 20,
"port": 5432,
"ssl": true,
"username": "provisioning"
},
"logging": {
"file": "/var/log/provisioning/orchestrator.log",
"format": "json",
"level": "info",
"max_size": 104857600,
"output": "file",
"retention_days": 30
},
"mode": "multiuser",
"monitoring": {
"enabled": true,
"health_check_interval": 10,
"metrics_port": 9090,
"prometheus": {
"enabled": true,
"scrape_interval": "15s"
}
},
"queue": {
"dead_letter_queue": {
"enabled": true,
"max_size": 10000
},
"max_concurrent_tasks": 20,
"metrics": true,
"persist": true,
"priority_queue": true,
"retry_attempts": 5,
"retry_delay": 10000,
"task_timeout": 7200000
},
"replica_sync": {
"enabled": true,
"sync_interval": 5000
},
"replicas": 2,
"resources": {
"cpus": "2.0",
"disk": "100G",
"memory": "2048M"
},
"security": {
"auth_backend": "local",
"enable_auth": false,
"enable_rbac": false,
"token_expiry": 3600
},
"server": {
"address": "0.0.0.0",
"cors": {
"allowed_methods": [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH"
],
"allowed_origins": [
"https://control-center:8081"
],
"enabled": true
},
"port": 8080,
"rate_limiting": {
"burst_size": 100,
"enabled": true,
"requests_per_second": 500
},
"tls": true,
"tls_cert": "/etc/provisioning/certs/server.crt",
"tls_key": "/etc/provisioning/certs/server.key"
},
"storage": {
"backend": "s3",
"cache_enabled": true,
"cache_ttl": 7200,
"max_size": 107374182400,
"s3": {
"bucket": "provisioning-storage",
"endpoint": "https://s3.amazonaws.com",
"region": "us-east-1"
}
},
"workspace": {
"cache_path": "/var/provisioning/workspace/cache",
"data_path": "/var/provisioning/workspace/data",
"execution_mode": "distributed",
"isolation_level": "container",
"root_path": "/var/provisioning/workspace",
"state_path": "/var/provisioning/workspace/state"
}
}

View file

@ -0,0 +1,219 @@
{
"cicd_mode": {
"authentication": {
"auth_type": "token",
"ssh_key_storage": "kms",
"token_config": {
"expiry_seconds": 3600,
"refresh_enabled": false,
"token_format": "jwt",
"token_path": "/var/run/secrets/provisioning/token"
}
},
"description": "CI/CD pipeline automated execution",
"extensions": {
"oci_registry": {
"auth_token_path": "/var/run/secrets/provisioning/oci-token",
"cache_dir": "/tmp/provisioning-oci-cache",
"enabled": true,
"endpoint": "registry.cicd.local",
"namespace": "cicd-extensions",
"tls_enabled": true,
"verify_ssl": true
},
"source": "oci"
},
"mode_name": "cicd",
"resource_limits": {
"max_cpu_cores_per_user": 16,
"max_memory_gb_per_user": 64,
"max_servers_per_user": 5,
"max_storage_gb_per_user": 200
},
"security": {
"audit_log_path": "/var/log/provisioning/cicd-audit.log",
"audit_logging": true,
"dns_modification": "coredns",
"encryption_at_rest": true,
"encryption_in_transit": true,
"network_isolation": true,
"secret_provider": {
"provider": "vault"
}
},
"services": {
"control_center": {
"deployment": "disabled"
},
"coredns": {
"deployment": "remote",
"remote_config": {
"endpoint": "dns.cicd.local",
"port": 53
}
},
"gitea": {
"deployment": "remote",
"remote_config": {
"endpoint": "git.cicd.local",
"port": 443,
"tls_enabled": true
}
},
"oci_registry": {
"auth_required": true,
"deployment": "remote",
"endpoint": "registry.cicd.local",
"namespaces": {
"extensions": "cicd-extensions",
"kcl_packages": "cicd-kcl",
"platform_images": "cicd-platform",
"test_images": "cicd-test"
},
"remote": {
"retries": 5,
"timeout": 60,
"verify_ssl": true
},
"tls_enabled": true,
"type": "harbor"
},
"orchestrator": {
"deployment": "remote",
"remote_config": {
"endpoint": "orchestrator.cicd.local",
"port": 8080,
"retries": 5,
"timeout": 60,
"tls_enabled": true,
"verify_ssl": true
}
}
},
"workspaces": {
"git_integration": "required",
"isolation": "strict",
"locking": "disabled",
"max_workspaces_per_user": 1
}
},
"enterprise_mode": {
"authentication": {
"auth_type": "mtls",
"mtls_config": {
"ca_cert_path": "/etc/provisioning/certs/ca.crt",
"client_cert_path": "/etc/provisioning/certs/client.crt",
"client_key_path": "/etc/provisioning/certs/client.key",
"verify_server": true
},
"ssh_key_storage": "kms"
},
"description": "Production enterprise deployment with full security",
"extensions": {
"oci_registry": {
"auth_token_path": "/etc/provisioning/tokens/oci",
"cache_dir": "/var/cache/provisioning/oci",
"enabled": true,
"endpoint": "harbor.enterprise.local",
"namespace": "prod-extensions",
"tls_enabled": true,
"verify_ssl": true
},
"source": "oci"
},
"mode_name": "enterprise",
"resource_limits": {
"max_cpu_cores_per_user": 64,
"max_memory_gb_per_user": 256,
"max_servers_per_user": 20,
"max_storage_gb_per_user": 1000,
"max_total_cpu_cores": 2000,
"max_total_memory_gb": 8192,
"max_total_servers": 500
},
"security": {
"audit_log_path": "/var/log/provisioning/enterprise-audit.log",
"audit_logging": true,
"dns_modification": "system",
"encryption_at_rest": true,
"encryption_in_transit": true,
"network_isolation": true,
"secret_provider": {
"provider": "vault"
}
},
"services": {
"control_center": {
"deployment": "k8s",
"k8s_config": {
"deployment_name": "control-center",
"image": "harbor.enterprise.local/provisioning/control-center:latest",
"namespace": "provisioning-system",
"replicas": 2,
"service_name": "control-center-svc"
}
},
"coredns": {
"deployment": "k8s",
"k8s_config": {
"deployment_name": "coredns",
"image": "registry.k8s.io/coredns/coredns:latest",
"namespace": "kube-system",
"replicas": 2,
"service_name": "kube-dns"
}
},
"gitea": {
"deployment": "k8s",
"k8s_config": {
"deployment_name": "gitea",
"image": "gitea/gitea:latest",
"namespace": "provisioning-system",
"replicas": 2,
"service_name": "gitea-svc"
}
},
"oci_registry": {
"auth_required": true,
"deployment": "remote",
"endpoint": "harbor.enterprise.local",
"namespaces": {
"extensions": "prod-extensions",
"kcl_packages": "prod-kcl",
"platform_images": "prod-platform",
"test_images": "test-images"
},
"remote": {
"retries": 5,
"timeout": 60,
"verify_ssl": true
},
"tls_enabled": true,
"type": "harbor"
},
"orchestrator": {
"deployment": "k8s",
"k8s_config": {
"deployment_name": "orchestrator",
"image": "harbor.enterprise.local/provisioning/orchestrator:latest",
"namespace": "provisioning-system",
"replicas": 3,
"resources": {
"cpu_limit": "2000m",
"cpu_request": "500m",
"memory_limit": "4Gi",
"memory_request": "1Gi"
},
"service_name": "orchestrator-svc"
}
}
},
"workspaces": {
"git_integration": "required",
"isolation": "strict",
"lock_provider": "etcd",
"locking": "required",
"max_workspaces_per_user": 3
}
}
}

View file

@ -0,0 +1,19 @@
{
"best_practices": [
"bp_001",
"bp_002",
"bp_008",
"bp_033"
],
"category": "provider",
"dependencies": [],
"description": "UpCloud provider extension for provisioning on UpCloud infrastructure",
"name": "upcloud",
"tags": [
"provider",
"upcloud",
"cloud",
"vps"
],
"version": "1.0.0"
}

View file

@ -0,0 +1,18 @@
{
"best_practices": [
"bp_001",
"bp_008",
"bp_047"
],
"category": "provider",
"dependencies": [],
"description": "Local provider extension for provisioning on local machines, development environments, and testing",
"name": "local",
"tags": [
"provider",
"local",
"development",
"testing"
],
"version": "1.0.0"
}

View file

@ -0,0 +1,15 @@
{
"dependencies": [
"kubernetes",
"cilium"
],
"name": "hetzner-csi",
"version": {
"check_latest": true,
"current": "2.9.0",
"grace_period": 86400,
"site": "",
"source": "github.com/hetznercloud/csi-driver",
"tags": ""
}
}

View file

@ -0,0 +1,67 @@
{
"alternatives_considered": [
{
"option": "Wrap each call site individually with do { } | complete (existing pattern)",
"why_rejected": "Works only for external commands, not for Nu plugin commands. Plugin commands raise LabeledError — not catchable via complete. Keeping ^nickel export at call sites means all cache benefits are lost."
},
{
"option": "Single ncl-export.nu wrapper delegating to ^nickel export with inline cache check",
"why_rejected": "Duplicates the cache logic already inside nu_plugin_nickel. Two cache implementations with different key strategies would diverge. The plugin is the correct cache owner — the wrapper should delegate to it."
},
{
"option": "Migrate all 124 call sites at once",
"why_rejected": "Risk surface too large. Priority-ordered migration (C1 hot-path first) allows validating cache correctness on the most-exercised paths before touching validation, bootstrap, and diagnostic paths that are harder to test."
}
],
"consequences": {
"negative": [
"nu_plugin_nickel must be registered in the Nu session for performance benefits; unregistered sessions fall back to the `^nickel export` path in nickel-eval-soft (via the catch branch)",
"Block C2/C3 (remaining 120 call sites) are not yet migrated — those paths still use ^nickel export directly"
],
"positive": [
"Hot-path call sites (4 files, C1) are now cache-backed via nu_plugin_nickel",
"Single try/catch location for soft-failure pattern — easy to audit",
"| from json eliminated from migrated call sites"
]
},
"constraints": [
{
"check_hint": "grep '^nickel export' provisioning/core/nulib/main_provisioning/{dispatcher,components,workflow,extensions}.nu — must return empty",
"claim": "Hot-path files (C1) must not contain direct ^nickel export calls after migration",
"id": "c1-no-direct-nickel-export",
"rationale": "Direct ^nickel export in C1 files bypasses the plugin cache, negating the performance benefit of ADR-022. All C1 exports must go through ncl-eval or ncl-eval-soft.",
"scope": "dispatcher.nu, components.nu, workflow.nu, extensions.nu",
"severity": "Hard"
}
],
"context": "After ADR-022 established the ncl-sync daemon and shared cache, Nu call sites needed to be migrated from `^nickel export --format json ... | from json` to the plugin. Two call patterns exist: hard-failure (export failure should propagate as an error — uses `error make`) and soft-failure (export failure should return a fallback value — uses `if $result.exit_code != 0`). Distributing try/catch across 124 call sites would violate the guideline against widespread use of try/catch for Nu plugin commands.",
"date": "2026-04-16",
"decision": "Two wrapper functions in `lib_provisioning/utils/nickel_processor.nu` serve as the single abstraction layer: `ncl-eval [path import_paths]` for hard-failure call sites (error propagates from the plugin directly — no try/catch needed), and `ncl-eval-soft [path import_paths fallback]` for soft-failure call sites (a single try/catch returns `fallback` on any plugin error). Block C1 migrates the four hot-path call sites: `dispatcher.nu` (commands-registry), `components.nu` (comp-ncl-export helper + servers.ncl), `workflow.nu` (wf-ncl-export helper + settings.ncl + state.ncl), `extensions.nu` (metadata.ncl per taskserv). Block C2/C3 cover the remaining operation-path and validation call sites.",
"id": "adr-023",
"ontology_check": {
"decision_string": "ncl-eval + ncl-eval-soft wrappers in nickel_processor.nu replace ^nickel export at hot-path call sites; single try/catch in ncl-eval-soft",
"invariants_at_risk": [
"config-driven-always"
],
"verdict": "Safe"
},
"rationale": [
{
"claim": "ncl-eval-soft isolates the single try/catch to one location",
"detail": "In Nu 0.111.0, try/catch is valid for Nu internal commands (including plugins). However, dispersing try/catch across dozens of call sites increases cognitive load and creates inconsistency. Centralizing it in ncl-eval-soft means the pattern is reviewed once and applied uniformly. Callers declare intent via the `fallback` parameter (`{}`, `[]`, or `null`) rather than embedding error-handling logic inline."
},
{
"claim": "ncl-eval (hard-failure) requires no try/catch — plugin LabeledError propagates naturally",
"detail": "When `nickel-eval` fails, it raises a `LabeledError` that Nu surfaces as a structured error. This is identical in behavior to `error make { msg: ... }` in the existing code. The call site is simply `ncl-eval $path [$ws $prov]` — one line instead of four. No error handling is needed because the error propagation is the correct behavior."
},
{
"claim": "nickel-eval returns Nu values natively, eliminating | from json",
"detail": "The plugin converts `serde_json::Value` to `nu_protocol::Value` via `json_value_to_nu_value`. Call sites receive a Nu record or list directly and can use cell path access (`$data.components`, `$data.dimensions`) without an intermediate string parse step. This removes a class of parse errors where `from json` would fail on empty stdout from a cached result."
}
],
"related_adrs": [
"adr-022-ncl-sync-daemon"
],
"status": "Accepted",
"title": "ncl-eval wrapper: nu_plugin_nickel as the single ^nickel export abstraction in Nu"
}

View file

@ -0,0 +1,94 @@
{
"forgejo": {
"concerns": {
"backup": {
"backlog_ref": "BACKUP-FORGEJO-001",
"kind": "pending",
"reason": "BackupPolicy declared at workspace level (Git data + DB + actions runner state)"
},
"certs": {
"certs_impl": {
"acme_server": "https://acme-v02.api.letsencrypt.org/directory",
"email": "",
"provider": "cloudflare",
"secret_ref": ""
},
"kind": "enabled"
},
"dns": {
"dns_impl": {
"internal": [],
"zone": ""
},
"kind": "enabled"
},
"observability": {
"backlog_ref": "OBS-001",
"kind": "pending",
"reason": "ObservabilityImpl iteration deferred — surface stub only"
},
"security": {
"backlog_ref": "SEC-001",
"kind": "pending",
"reason": "SecurityImpl iteration deferred — surface stub only"
},
"tls": {
"kind": "enabled",
"tls_impl": {
"hostnames": [],
"issuer_ref": "letsencrypt-prod",
"secret_name": "forgejo-tls"
}
}
},
"data_dir": "/var/lib/data/forgejo",
"database": "forgejo",
"db_host": "postgresql.data.svc",
"http_port": 3000,
"image": "codeberg.org/forgejo/forgejo:latest",
"live_check": {
"scope": "cp_only",
"strategy": "k8s_pods"
},
"mode": "cluster",
"name": "forgejo",
"namespace": "data",
"operations": {
"delete": true,
"health": true,
"install": true,
"update": true
},
"provides": {
"port": 3000,
"service": "forgejo"
},
"repos_dir": "/var/lib/data/forgejo",
"requires": {
"credentials": [
"FORGEJO_ADMIN_PASSWORD",
"POSTGRES_PASSWORD"
],
"ports": [
{
"exposure": "public",
"port": 222
},
{
"exposure": "public",
"port": 80
},
{
"exposure": "public",
"port": 443
}
],
"storage": {
"persistent": true,
"size": "20Gi"
}
},
"ssh_port": 222,
"version": "latest"
}
}

View file

@ -0,0 +1,17 @@
{
"proxy": {
"backends": [],
"https_in_binds": [],
"https_log_format": "%H %ci:%cp [%t] %ft %b/%s %Tw/%Tc/%Tt %B %ts %ac/%fc/%bc/%sc/%rc %sq/%bq",
"https_options": [
"tcplog",
"dontlognull"
],
"proxy_cfg_file": "haproxy.cfg",
"proxy_lib": "/var/lib/haproxy",
"proxy_version": "latest",
"run_group": "haproxy",
"run_user": "haproxy",
"run_user_home": "/home/haproxy"
}
}

View file

@ -0,0 +1,17 @@
{
"lian_build_daemon": {
"config": {
"adapters": [],
"config_dir": "/etc/lian-build",
"data_dir": "/var/lib/lian-build",
"port": 19012,
"static_dir": "/Users/Akasha/Development/lian-build/assets/web",
"templates_dir": "/Users/Akasha/Development/lian-build/assets/web/templates"
},
"credentials": {
"registry": "infra/libre-wuji/secrets/registry-push.sops.yaml",
"ssh_key": "/Users/Akasha/Development/.ssh/orchestrator-buildkit-key"
},
"daemon_url": "http://localhost:19012"
}
}

View file

@ -0,0 +1,14 @@
{
"DefaultCrun": {
"cmd_task": "install",
"name": "crun",
"version": "1.21"
},
"defaults": {
"crun": {
"cmd_task": "install",
"name": "crun",
"version": "1.21"
}
}
}

View file

@ -0,0 +1,12 @@
{
"os": {
"admin_group": "devadm",
"admin_user": "devadm",
"name": "os",
"src_user_path": "devadm-home",
"ssh_keys": "",
"sysctl": {
"disable_ipv6": false
}
}
}

View file

@ -0,0 +1,7 @@
{
"external_nfs": {
"ip": "{{network_private_ip}}",
"net": "$net",
"shared": "/shared"
}
}

Some files were not shown because too many files have changed in this diff Show more