#!/usr/bin/env nu # domains/provisioning/commands.nu — Provisioning domain CLI for ontoref. # Dispatched by the ontoref bash wrapper when repo_kind in {DevWorkspace, Mixed}: # ore provisioning [args] # # Works for both DevWorkspace (workspace-level) and Mixed (platform-level) projects. # Commands that require project-specific files (card.ncl, reflection/backlog.ncl) # degrade gracefully when those files are absent. def project-root [] { $env.ONTOREF_PROJECT_ROOT? | default (pwd | path expand) } # ─── Loaders ────────────────────────────────────────────────────────────────── def load-state [] { ^nickel export $"(project-root)/.ontology/state.ncl" | from json } def load-core [] { ^nickel export $"(project-root)/.ontology/core.ncl" | from json } def load-gate [] { ^nickel export $"(project-root)/.ontology/gate.ncl" | from json } def load-connections [] { ^nickel export $"(project-root)/.ontology/connections.ncl" | from json } def load-manifest [] { ^nickel export $"(project-root)/.ontology/manifest.ncl" | from json } def load-card [] { let path = $"(project-root)/card.ncl" if ($path | path exists) { ^nickel export $path | from json } else { null } } def load-backlog [] { let path = $"(project-root)/reflection/backlog.ncl" if ($path | path exists) { ^nickel export $path | from json } else { { items: [] } } } # ─── state / next / validate ────────────────────────────────────────────────── export def "state" [] { load-state | get dimensions | each { |dim| { dimension: $dim.id, current: $dim.current_state, desired: $dim.desired_state, horizon: $dim.horizon } } } export def "next" [] { load-state | get dimensions | each { |dim| let active = $dim.transitions | where from == $dim.current_state if ($active | is-empty) { { dimension: $dim.id, from: $dim.current_state, to: "–", condition: "no active transition", blocker: "–", catalyst: "–" } } else { let tr = $active | first { dimension: $dim.id, from: $tr.from, to: $tr.to, condition: $tr.condition, blocker: $tr.blocker, catalyst: $tr.catalyst } } } } export def "validate" [decision: string] { let core = load-core let words = ($decision | str downcase | split row " " | where { |w| ($w | str length) > 3 }) let affected = ($core.nodes | each { |n| let text = $"($n.id) ($n.name) ($n.description)" | str downcase if ($words | any { |w| $text =~ $w }) { $n } else { null } } | compact) { decision: $decision, invariants_at_risk: ($affected | where invariant == true | select id level name), tensions_touched: ($affected | where level == "Tension" | select id name), practices_touched: ($affected | where level == "Practice" | select id name), verdict: "manual review required — if any invariant is contradicted, justification is required", } } # ─── connections / gates ────────────────────────────────────────────────────── export def "connections" [] { let c = load-connections if ($c.upstream? | default [] | is-not-empty) { print "" print "UPSTREAM" $c.upstream | each { |u| print $" ($u.project) kind=($u.kind) via=($u.via)" if ($u.node? | default "" | is-not-empty) { print $" node: ($u.node)" } print $" ($u.note)" } } if ($c.downstream? | default [] | is-not-empty) { print "" print "DOWNSTREAM" $c.downstream | each { |d| print $" ($d.project) kind=($d.kind) via=($d.via)" if ($d.node? | default "" | is-not-empty) { print $" node: ($d.node)" } print $" ($d.note)" } } if ($c.peers? | default [] | is-not-empty) { print "" print "PEERS" $c.peers | each { |p| print $" ($p.project) kind=($p.kind) via=($p.via)" } } } export def "gates" [] { load-gate | get membranes | each { |m| { membrane: $m.id, name: $m.name, active: $m.active, permeability: $m.permeability, condition: ($m.opening_condition.description? | default ""), } } } # ─── card (DevWorkspace only) ───────────────────────────────────────────────── export def "card" [] { let c = load-card if ($c | is-empty) { print "No card.ncl found — this command is only available for DevWorkspace projects." return } print $"id: ($c.id)" print $"name: ($c.name)" print $"tagline: ($c.tagline)" print $"status: ($c.status)" print $"version: ($c.version)" print $"started: ($c.started_at)" if ($c.tags? | default [] | is-not-empty) { print $"tags: ($c.tags | str join ', ')" } if ($c.tools? | default [] | is-not-empty) { print $"tools: ($c.tools | str join ', ')" } if ($c.features? | default [] | is-not-empty) { print "" print "features:" $c.features | each { |f| print $" - ($f)" } } } # ─── capabilities (manifest.capabilities) ──────────────────────────────────── export def "capabilities" [] { let caps = load-manifest | get capabilities? | default [] if ($caps | is-empty) { print "No capabilities declared in manifest."; return } $caps | select id name summary } # ─── backlog (platform only) ────────────────────────────────────────────────── export def "backlog" [--priority: string = ""] { let items = load-backlog | get items if ($items | is-empty) { print "No backlog.ncl found — this command is only available for platform projects." return } let filtered = if ($priority | is-empty) { $items } else { $items | where priority == $priority } $filtered | each { |i| { id: $i.id, priority: $i.priority, blocked_by: ($i.blocked_by | str join ", "), } } } export def "backlog show" [id: string] { let items = load-backlog | get items if ($items | is-empty) { error make { msg: "No backlog.ncl found for this project." } } let item = $items | where id == $id if ($item | is-empty) { error make { msg: $"Backlog item not found: ($id)" } } let r = $item | first print $"id: ($r.id)" print $"priority: ($r.priority)" print "" print "description:" print $" ($r.description)" if ($r.blocked_by? | default [] | is-not-empty) { print "" print $"blocked_by: ($r.blocked_by | str join ', ')" } if ($r.related_nodes? | default [] | is-not-empty) { print $"nodes: ($r.related_nodes | str join ', ')" } } # ─── install (Mixed/platform only) ─────────────────────────────────────────── def detect-platform []: nothing -> string { if (do { ^which docker } | complete | get exit_code) == 0 { "docker" } else if (do { ^which podman } | complete | get exit_code) == 0 { "podman" } else if (do { ^which kubectl } | complete | get exit_code) == 0 { "kubernetes" } else { "" } } export def "install" [ --mode: string = "solo" # Deployment mode: solo|multi-user|cicd|enterprise --platform: string = "" # Container platform: docker|podman|kubernetes (auto-detected) --dry-run # Print what would be run without executing ]: nothing -> nothing { let root = (project-root) let installer_bin = [$root, "platform", "target", "release", "provisioning-installer"] | path join let installer_src = [$root, "platform", "installer"] | path join let just_available = (do { ^which just } | complete | get exit_code) == 0 let resolved_platform = if ($platform | is-not-empty) { $platform } else { detect-platform } print $"Platform install mode=($mode) platform=(if ($resolved_platform | is-empty) { '?' } else { $resolved_platform })" print "" if ($resolved_platform | is-empty) { print "No supported container platform found (docker, podman, kubectl)." print "Install one and retry." return } if ($installer_bin | path exists) { print $"installer binary: ($installer_bin)" if $dry_run { print $"[dry-run] would run: ($installer_bin) --headless --mode ($mode) --platform ($resolved_platform) --yes" return } do { ^$installer_bin --headless --mode $mode --platform $resolved_platform --yes } | complete | ignore return } if ($installer_src | path exists) { print "installer binary not built — building from source..." if $dry_run { print $"[dry-run] would run: cargo build --release --manifest-path ($installer_src)/Cargo.toml" return } let r = do { ^cargo build --release --manifest-path $"($installer_src)/Cargo.toml" } | complete if $r.exit_code != 0 { error make { msg: $"installer build failed:\n($r.stderr)" } } do { ^$installer_bin --headless --mode $mode --platform $resolved_platform --yes } | complete | ignore return } if $just_available { print "No installer binary or source found — delegating to: just install" if $dry_run { print $"[dry-run] would run: just install mode=($mode)" return } let r = do { ^just install $"mode=($mode)" } | complete if $r.exit_code != 0 { error make { msg: $"just install failed:\n($r.stderr)" } } return } print "installer not found and 'just' is not available." print $"Build the installer first: cargo build --release --manifest-path ($installer_src)/Cargo.toml" } # ─── ops (ADR-037: NATS ops contract) ──────────────────────────────────────── def workspace-id []: nothing -> string { let card = load-card if ($card | is-empty) { "unknown" } else { $card.id } } def nats-available []: nothing -> bool { (do { ^which nats } | complete | get exit_code) == 0 } def keeper-cli-available []: nothing -> bool { (do { ^which keeper-cli } | complete | get exit_code) == 0 } def rad-available []: nothing -> bool { (do { ^which rad } | complete | get exit_code) == 0 } export def "ops list" []: nothing -> nothing { let ws = workspace-id if not (nats-available) { print "nats CLI not found — install nats-io/natscli" return } let stream = $"OPS_PENDING_(($ws | str upcase))" let r = do { ^nats stream info $stream --json } | complete if $r.exit_code != 0 { print $"Stream ($stream) not found or NATS unreachable: ($r.stderr | str trim)" return } let info = $r.stdout | from json print $"stream: ($stream)" print $"messages: ($info.state.messages)" print $"first_seq: ($info.state.first_seq)" print $"last_seq: ($info.state.last_seq)" } export def "ops describe" [id: string]: nothing -> nothing { let ws = workspace-id if not (nats-available) { print "nats CLI not found"; return } let subject = $"ops.pending.($ws).>" let r = do { ^nats req $subject "" --count 50 --json } | complete if $r.exit_code != 0 { print $"NATS request failed: ($r.stderr | str trim)"; return } let msgs = $r.stdout | lines | each { |l| $l | from json | get data? | default {} } let found = $msgs | where { |m| ($m.jti? | default "") == $id } if ($found | is-empty) { print $"Op not found: ($id)"; return } let op = $found | first print $"jti: ($op.jti)" print $"op_type: ($op.op_type? | default 'unknown')" print $"target: ($op.target? | default 'unknown')" print $"expected_version: ($op.expected_state_version? | default 'none')" print $"scopes: ($op.scopes? | default [] | str join ', ')" print $"issued_at: ($op.iat? | default 'unknown')" } export def "ops sign" [id: string]: nothing -> nothing { if not (keeper-cli-available) { print "keeper-cli not found — build from platform/crates/ops-keeper"; return } let ws = workspace-id let r = do { ^keeper-cli --workspace $ws sign $id } | complete if $r.exit_code != 0 { error make { msg: $"keeper-cli sign failed: ($r.stderr | str trim)" } } print $r.stdout } export def "ops history" [--workspace: string = ""]: nothing -> nothing { let ws = if ($workspace | is-empty) { workspace-id } else { $workspace } let repo_path = $env.HOME | path join ".local/share/radicle/repos" $"state-($ws)" if not ($repo_path | path exists) { print $"Radicle state repo not found at ($repo_path)" print "Run: rad clone rad: state-($ws)" return } let r = do { ^git -C $repo_path log --oneline --no-decorate -20 } | complete if $r.exit_code != 0 { print $"git log failed: ($r.stderr | str trim)"; return } $r.stdout | lines | each { |l| let parts = $l | split row " " | enumerate let hash = $parts | where index == 0 | get item.0 let msg = $parts | skip 1 | get item | str join " " { commit: $hash, message: $msg } } } # ─── playbook (extensions/playbooks/) ──────────────────────────────────────── def playbooks-root []: nothing -> string { let root = project-root [$root, "..", "..", "provisioning", "extensions", "playbooks"] | path join | path expand } export def "playbook list" []: nothing -> nothing { let root = playbooks-root if not ($root | path exists) { print "No extensions/playbooks/ directory found."; return } ^ls $root | where type == dir | get name | each { |d| let playbook_path = [$d, "playbook.ncl"] | path join let has_run = ([$d, "run.nu"] | path join | path exists) { name: ($d | path basename), has_run: $has_run, has_playbook: ($playbook_path | path exists) } } } export def "playbook describe" [name: string]: nothing -> nothing { let root = playbooks-root let playbook_path = [$root, $name, "playbook.ncl"] | path join if not ($playbook_path | path exists) { print $"Playbook not found: ($name)" print $"Expected at: ($playbook_path)" return } let r = do { ^nickel export $playbook_path } | complete if $r.exit_code != 0 { print $"Failed to load playbook: ($r.stderr | str trim)"; return } let p = $r.stdout | from json print $"name: ($p.name)" print $"description: ($p.description)" print $"version: ($p.version)" print "" print "preconditions:" $p.preconditions? | default [] | each { |c| print $" - ($c)" } print "" print "steps:" $p.steps? | default [] | enumerate | each { |s| print $" ($s.index + 1). ($s.item.id): ($s.item.description)" } if ($p.rollback_strategy? | default "none") != "none" { print $"\nrollback: ($p.rollback_strategy)" } } export def "playbook run" [name: string, --dry-run]: nothing -> nothing { let root = playbooks-root let run_path = [$root, $name, "run.nu"] | path join if not ($run_path | path exists) { print $"run.nu not found for playbook: ($name)" return } let ws = workspace-id if $dry_run { let dry_path = [$root, $name, "tests", "dry_run.nu"] | path join if ($dry_path | path exists) { let r = do { ^nu $dry_path --workspace $ws } | complete if $r.exit_code != 0 { error make { msg: $"dry-run failed: ($r.stderr | str trim)" } } print $r.stdout } else { print $"[dry-run] would execute: nu ($run_path) --workspace ($ws)" print "No dry_run.nu found — simulating step listing only." playbook describe $name } return } let r = do { ^nu $run_path --workspace $ws } | complete if $r.exit_code != 0 { error make { msg: $"playbook run failed: ($r.stderr | str trim)" } } print $r.stdout } export def "playbook history" []: nothing -> nothing { let ws = workspace-id let repo_path = $env.HOME | path join ".local/share/radicle/repos" $"state-($ws)" if not ($repo_path | path exists) { print $"Radicle state repo not found at ($repo_path)" return } let r = do { ^git -C $repo_path log --oneline --no-decorate --grep="playbook:" -20 } | complete if $r.exit_code != 0 { print $"git log failed: ($r.stderr | str trim)"; return } if ($r.stdout | is-empty) { print "No playbook audit entries found."; return } $r.stdout | lines | each { |l| let parts = $l | split row " " | enumerate let hash = $parts | where index == 0 | get item.0 let msg = $parts | skip 1 | get item | str join " " { commit: $hash, message: $msg } } } # ─── keeper (ADR-037 dual-mode signing) ────────────────────────────────────── export def "keeper status" []: nothing -> nothing { let ws = workspace-id if not (keeper-cli-available) { print "keeper-cli not found"; return } let r = do { ^keeper-cli --workspace $ws status } | complete if $r.exit_code != 0 { print $"keeper-cli unavailable: ($r.stderr | str trim)" return } print $r.stdout } export def "keeper policy" []: nothing -> nothing { let ws = workspace-id let policy_repo = $env.HOME | path join ".local/share/radicle/repos" $"policy-($ws)" if not ($policy_repo | path exists) { print $"Policy repo not cloned locally: policy-($ws)" print "Run: rad clone rad: policy-($ws)" return } let policy_path = [$policy_repo, "policy.ncl"] | path join if not ($policy_path | path exists) { print "policy.ncl not found in policy repo"; return } let r = do { ^nickel export $policy_path } | complete if $r.exit_code != 0 { print $"Failed to load policy: ($r.stderr | str trim)"; return } let p = $r.stdout | from json print $"workspace: ($p.workspace)" print $"version: ($p.version)" print "" print "auto_sign rules:" $p.auto_sign? | default [] | each { |rule| print $" op_type=($rule.op_type) target=($rule.target? | default '*') scope=($rule.scope? | default 'any')" } print "" print "require_manual rules:" $p.require_manual? | default [] | each { |rule| print $" op_type=($rule.op_type) target=($rule.target? | default '*')" } } export def "keeper switch" [mode: string]: nothing -> nothing { let valid_modes = ["auto", "operator-only"] if not ($valid_modes | any { |m| $m == $mode }) { error make { msg: $"Invalid mode '($mode)'. Valid modes: ($valid_modes | str join ', ')" } } let playbook = if $mode == "auto" { "switch_to_vm_ops" } else { "switch_to_operator_only" } print $"Delegating to playbook: ($playbook)" playbook run $playbook } # ─── governance (ADR-038 Radicle delegation) ───────────────────────────────── export def "governance delegations" []: nothing -> nothing { let ws = workspace-id let repos = ["policy", "desired", "state"] | each { |r| $"($r)-($ws)" } $repos | each { |repo_name| let r = do { ^rad id --repo $repo_name --json } | complete if $r.exit_code != 0 { { repo: $repo_name, error: ($r.stderr | str trim), delegates: [] } } else { let info = $r.stdout | from json { repo: $repo_name, threshold: ($info.threshold? | default 1), delegates: ($info.delegates? | default []) } } } } export def "governance signers" []: nothing -> nothing { let ws = workspace-id if not (nats-available) { print "nats CLI not found"; return } let r = do { ^nats auth account ls --json } | complete if $r.exit_code != 0 { print $"NATS auth query failed: ($r.stderr | str trim)" return } let accounts = $r.stdout | from json let ops_accounts = $accounts | where { |a| ($a.name? | default "") | str contains $ws } if ($ops_accounts | is-empty) { print $"No NATS accounts found for workspace: ($ws)"; return } $ops_accounts | each { |a| { account: $a.name, subject: ($a.subject? | default ""), signers: ($a.signing_keys? | default []) } } } # ─── help + main ────────────────────────────────────────────────────────────── # ─── registry (i sub-domain) ────────────────────────────────────────────────── const DEFAULT_REGISTRY = "reg.librecloud.online" def _find-capabilities-ncl []: nothing -> string { let ws_path = ($env | get -o PROVISIONING_WORKSPACE_PATH | default "") if ($ws_path | is-not-empty) { let candidates = (do { glob $"($ws_path)/infra/*/capabilities.ncl" } | default []) if ($candidates | is-not-empty) { return ($candidates | first) } } mut dir = $env.PWD for _ in 0..6 { let cur = $dir # immutable copy for closure capture in glob let candidates = (do { glob $"($cur)/infra/*/capabilities.ncl" } | default []) if ($candidates | is-not-empty) { return ($candidates | first) } let parent = ($dir | path dirname) if $parent == $dir { break } $dir = $parent } "" } export def "i resolve-registry" []: nothing -> string { let from_env = ($env | get -o PROVISIONING_REGISTRY | default "") if ($from_env | is-not-empty) { return $from_env } let caps = (_find-capabilities-ncl) if ($caps | is-not-empty) { let ip = ($env.NICKEL_IMPORT_PATH? | default ($env.PROVISIONING? | default "")) let ncl = (do { ^nickel export --import-path $ip $caps } | complete) if $ncl.exit_code == 0 { let data = ($ncl.stdout | from json) let reg_default = ($data | get -o provides.registries.default | default "") let registries = ($data | get -o provides.registries.registries | default []) if ($reg_default | is-not-empty) and ($registries | is-not-empty) { let entry = ($registries | where { |e| $e.id == $reg_default } | first | default null) if ($entry != null) and ($entry.endpoint? | default "" | is-not-empty) { return $entry.endpoint } } } } $DEFAULT_REGISTRY } export def "i list" [--live]: nothing -> nothing { let reg = (i resolve-registry) if not $live { print $"registry : ($reg)" print "Use --live to query the live catalog." return } let url = $"https://($reg)/v2/_catalog" let body = try { http get $url } catch { print $"Registry catalog unavailable at ($url)" return } let repos = ($body | get -o repositories | default []) let domains = ($repos | where { |r| $r | str starts-with "domains/" } | each { |r| let parts = ($r | str replace "domains/" "" | split row "/") { kind: "domain", participant: ($parts | first), id: ($parts | skip 1 | str join "/"), ref: $"($reg)/($r)" } }) let modes = ($repos | where { |r| $r | str starts-with "modes/" } | each { |r| let parts = ($r | str replace "modes/" "" | split row "/") { kind: "mode", participant: ($parts | first), id: ($parts | skip 1 | str join "/"), ref: $"($reg)/($r)" } }) let all = ($domains | append $modes) if ($all | is-empty) { print $"No domains/ or modes/ artifacts found in ($reg)" return } print $"registry: ($reg) artifacts: ($all | length)" print "" $all | each { |a| print $" ($a.kind) ($a.participant)/($a.id) → ($a.ref)" } null } def show-help [] { print "Provisioning — ontoref domain extension" print "" print "USAGE" print " ore provisioning [args]" print "" print "COMMANDS (all repo_kinds)" print " state Current FSM position across all dimensions" print " next Next valid transitions with blockers/catalysts" print " validate Check against ontological invariants" print " connections Upstream/downstream project dependency graph" print " gates Membrane status and opening conditions" print " capabilities Platform capabilities from manifest" print "" print "COMMANDS (DevWorkspace only)" print " card Workspace card: identity, clusters, status" print "" print "COMMANDS (Mixed/platform only)" print " backlog [--priority] Backlog items filtered by High|Medium|Low" print " backlog show Full detail of a backlog item" print " install [--mode] [--platform] Install provisioning platform services" print "" print "COMMANDS (DevWorkspace — ops contract, ADR-037)" print " ops list Pending ops queue depth for this workspace" print " ops describe Full op detail: JWT claims, scopes, expected_version" print " ops sign Operator signs a pending op via keeper-cli" print " ops history [] Applied ops from -state Radicle ledger" print "" print "COMMANDS (DevWorkspace — playbooks)" print " playbook list Available playbooks for this workspace" print " playbook describe Steps, params, preconditions of a playbook" print " playbook run Execute a playbook (use --dry-run first)" print " playbook history Past playbook executions from audit ledger" print "" print "COMMANDS (DevWorkspace — keeper daemon, ADR-037)" print " keeper status Keeper mode (auto|operator-only|down) + policy version" print " keeper policy Active keeper policy from policy- Radicle repo" print " keeper switch Switch keeper mode (delegates to switch_to_ playbook)" print "" print "COMMANDS (DevWorkspace — governance, ADR-038)" print " governance delegations Radicle delegation sets for policy/desired/state repos" print " governance signers Active NATS JWT signers and M-of-N quorum status" print "" print "COMMANDS (all repo_kinds — registry)" print " i resolve-registry Resolved OCI registry endpoint (env → capabilities.ncl → default)" print " i list Show resolved registry endpoint" print " i list --live Query _catalog and list live domains/ and modes/ artifacts" } def main [ ...args: string --priority: string = "" --mode: string = "solo" --platform: string = "" --dry-run --live ] { if ($args | is-empty) or ($args | first) == "help" { show-help; return } let sub = ($args | str join " ") match $sub { "state" => { state } "next" => { next } "connections" => { connections } "gates" => { gates } "card" => { card } "capabilities" => { capabilities } "install" => { if $dry_run { install --mode $mode --platform $platform --dry-run } else { install --mode $mode --platform $platform } } "backlog" => { backlog --priority $priority } "ops list" => { ops list } "ops history" => { ops history } "keeper status" => { keeper status } "keeper policy" => { keeper policy } "governance delegations" => { governance delegations } "governance signers" => { governance signers } "playbook list" => { playbook list } "playbook history" => { playbook history } _ if ($sub | str starts-with "backlog show ") => { backlog show ($args | get 2) } _ if ($sub | str starts-with "validate ") => { validate ($args | skip 1 | str join " ") } _ if ($sub | str starts-with "ops describe ") => { ops describe ($args | get 2) } _ if ($sub | str starts-with "ops sign ") => { ops sign ($args | get 2) } _ if ($sub | str starts-with "playbook describe ") => { playbook describe ($args | get 2) } _ if ($sub | str starts-with "playbook run ") => { if $dry_run { playbook run ($args | get 2) --dry-run } else { playbook run ($args | get 2) } } _ if ($sub | str starts-with "keeper switch ") => { keeper switch ($args | get 2) } _ if ($sub | str starts-with "ops history ") => { ops history --workspace ($args | get 2) } "i resolve-registry" => { i resolve-registry | print } "i list" => { if $live { i list --live } else { i list } } _ => { print $"Unknown command: ($sub)"; show-help } } }