ontoref/domains/provisioning/commands.nu
Jesús Pérez 82a358f18d
Some checks failed
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (push) Has been cancelled
feat: #[onto_mcp_tool] catalog, OCI credential vault layer, validate ADR-018 mode hierarchy
ontoref-derive: #[onto_mcp_tool] attribute macro registers MCP tool unit-structs in
  the catalog at link time via inventory::submit!; annotated item is emitted unchanged,
  ToolBase/AsyncTool impls stay on the struct. All 34 tools migrated from manual wiring
  (net +5: ontoref_list_projects, ontoref_search, ontoref_describe,
  ontoref_list_ontology_extensions, ontoref_get_ontology_extension).

  validate modes (ADR-018): reads level_hierarchy from workflow.ncl and checks every
  .ncl mode for level declared, strategy declared, delegate chain coherent, compose
  extends valid. mode resolve <id> shows which hierarchy level handles a mode and why.
  --self-test generates synthetic fixtures in a temp dir for CI smoke-testing.

  validate run-cargo: two-step Cargo.toml resolution — workspace layout first
  (crates/<check.crate>/Cargo.toml), single-crate fallback by package name or repo
  basename. Lets the same ADR constraint shape apply to workspace and single-crate repos.

  ontology/schemas/manifest.ncl: registry_topology_type contract — multi-registry
  coordination, push targets, participant scopes, per-namespace capability.

  reflection/requirements/base.ncl: oras ≥1.2.0, cosign ≥2.0.0, sops ≥3.9.0, age
  ≥1.1.0, restic declared as Hard/Soft requirements with version_min, check_cmd, and
  install_hint (ADR-017 toolchain surface).

  ADR-019: per-file recipient routing for tenant isolation without multi-vault. Schema
  additions: sops.recipient_groups + sops.recipient_rules in ontoref-project.ncl.
  secrets-bootstrap generates .sops.yaml from project.ncl in declarative mode. Three
  new secrets-audit checks: recipient-routing-coherent, recipient-routing-coverage,
  no-multi-vault. Adoption templates: single-team/, multi-tenant/, agent-first/.
  Integration templates: domain-producer/, mode-producer/, mode-consumer/.

  UI: project_picker surfaces registry badge (⟳ participant) and vault badge
  (⛁ vault_id · N, green=declarative / amber=legacy) per project card. Expanded panel
  adds collapsible Registry section with namespace, endpoint, and push/pull capability.
  manage.html gains Runtime Services card — MCP and GraphQL toggleable without restart
  via HTMX POST /ui/manage/services/{service}/toggle.

  describe.nu: capabilities JSON includes registry_topology and vault_state per project.
  sync.nu: drift check extended to detect //! absence on newly registered crates.
  qa.ncl: six entries — credential-vault-best-practice (layered data-flow diagram),
  credential-vault-templates (paths A/B/C), credential-vault-troubleshooting (15 named
  errors), integration-what-and-why (ADR-042 OCI federation), integration-how-to-implement,
  integration-troubleshooting.

  on+re: core.ncl + manifest.ncl updated to reflect OCI, MCP, and mode-hierarchy nodes.
  Deleted stale presentation assets (2026-02 slides + voice notes).
2026-05-12 04:46:15 +01:00

678 lines
30 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 <command> [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:<rid> 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:<rid> 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 <command> [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 <decision> 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 <id> 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 <id> Full op detail: JWT claims, scopes, expected_version"
print " ops sign <id> Operator signs a pending op via keeper-cli"
print " ops history [<workspace>] Applied ops from <workspace>-state Radicle ledger"
print ""
print "COMMANDS (DevWorkspace — playbooks)"
print " playbook list Available playbooks for this workspace"
print " playbook describe <name> Steps, params, preconditions of a playbook"
print " playbook run <name> 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-<workspace> Radicle repo"
print " keeper switch <mode> Switch keeper mode (delegates to switch_to_<mode> 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 }
}
}