ontoref/reflection/modules/opmode.nu

210 lines
8 KiB
Text
Raw Normal View History

#!/usr/bin/env nu
# reflection/modules/opmode.nu — operational mode detection, transition, and preflight.
#
# Mode is auto-detected on every invocation by comparing:
# desired = config.ncl mode field (what the project is configured for)
# actual = runtime connectivity probe (what is reachable right now)
#
# .ontoref/mode.lock stores { mode, since } across invocations.
# state.ncl operational-mode dimension tracks the architectural intent.
#
# Command levels:
# local → no service checks. Never blocks on connectivity.
# service → daemon optional. Degrades gracefully if unreachable.
# committed → daemon + DB required. Fail-fast if unavailable.
use env.nu *
use store.nu [daemon-available]
use ../nulib/shared.nu [project-root]
feat: domain extension system, VCS abstraction, personal/provisioning domains, web subpages Domain extension system (ADR-012): bash-layer dispatch activates repo_kind-conditional CLI domains. install.nu copies domains/ tree; short_alias wrappers generated (personal, prov). ore help and describe capabilities domain-aware. personal domain (PersonalOntology): career skills/talks/publications/positioning, CFP pipeline (Watching→Delivered), opportunities lifecycle, content pipeline, Sessionize integration. Daemon pages: /career, /personal. provisioning domain (DevWorkspace/Mixed): FSM state, next transitions, connections graph, gates, workspace card, capabilities, backlog. Daemon page: /provisioning. VCS abstraction layer (ADR-013): reflection/modules/vcs.nu — uniform jj/git API via filesystem detection (.jj/ vs .git/). opmode.nu and git-event.nu migrated off ^git. reflection/bin/jjw.nu — jj + ontoref + Radicle agent workspace lifecycle. jjw-ncl-merge.nu registered as jj merge tool for .ontology/ NCL conflicts. init-repo.nu for new_project mode. jj/rad not in ontoref requirements — belong in orchestration project manifests. 'Framework RepoKind: ontology/schemas/manifest.ncl gains 'Framework variant; ontoref self-identifies as framework — no domain activates for the protocol itself. Web presence: personal.html and provisioning.html domain subpages. index.html gains "Project Types — Domain Extensions" section with type cards and subpage links. Nav compacted (Arch/Prov labels, solid backdrop-filter background). on+re: vcs-abstraction (adrs: adr-013) and agent-workspace-orchestration Practice nodes; 21 manifest capabilities; state.ncl catalysts updated.
2026-04-07 23:08:29 +01:00
use ./vcs.nu
# ── Internal helpers ──────────────────────────────────────────────────────────
def mode-lock-path []: nothing -> string {
let root = (project-root)
$"($root)/.ontoref/mode.lock"
}
def read-lock []: nothing -> record<mode: string, since: string> {
let p = (mode-lock-path)
if not ($p | path exists) { return { mode: "unknown", since: "" } }
try { open $p | from json } catch { { mode: "unknown", since: "" } }
}
def write-lock [mode: string]: nothing -> nothing {
let p = (mode-lock-path)
{ mode: $mode, since: (date now | format date "%Y-%m-%dT%H:%M:%SZ") } | to json | save -f $p
}
def desired-mode []: nothing -> string {
let root = (project-root)
let cfg = $"($root)/.ontoref/config.ncl"
if not ($cfg | path exists) { return "local" }
let r = (do { ^nickel export --format json $cfg } | complete)
if $r.exit_code != 0 { return "local" }
try { $r.stdout | from json | get -o mode | default "local" | into string } catch { "local" }
}
def probe-actual [desired: string]: nothing -> string {
if $desired == "local" { return "local" }
if (daemon-available) { return "daemon" } else { return "local" }
}
# ── On-enter / on-exit actions ────────────────────────────────────────────────
def on-enter-daemon []: nothing -> nothing {
# Push ontology projection to daemon DB
let r = (do { ^nu -c $"use ($env.ONTOREF_ROOT)/reflection/modules/store.nu *; store sync-push" } | complete)
if $r.exit_code == 0 {
print $" (ansi green)sync(ansi reset) ontology pushed to daemon"
} else {
print $" (ansi yellow)sync(ansi reset) push failed — daemon up but export error"
}
# Update hooks to active mode
(install-hooks "daemon")
}
def on-exit-daemon []: nothing -> nothing {
(install-hooks "local")
}
def on-enter-local []: nothing -> nothing {
(install-hooks "local")
}
def install-hooks [mode: string]: nothing -> nothing {
feat: domain extension system, VCS abstraction, personal/provisioning domains, web subpages Domain extension system (ADR-012): bash-layer dispatch activates repo_kind-conditional CLI domains. install.nu copies domains/ tree; short_alias wrappers generated (personal, prov). ore help and describe capabilities domain-aware. personal domain (PersonalOntology): career skills/talks/publications/positioning, CFP pipeline (Watching→Delivered), opportunities lifecycle, content pipeline, Sessionize integration. Daemon pages: /career, /personal. provisioning domain (DevWorkspace/Mixed): FSM state, next transitions, connections graph, gates, workspace card, capabilities, backlog. Daemon page: /provisioning. VCS abstraction layer (ADR-013): reflection/modules/vcs.nu — uniform jj/git API via filesystem detection (.jj/ vs .git/). opmode.nu and git-event.nu migrated off ^git. reflection/bin/jjw.nu — jj + ontoref + Radicle agent workspace lifecycle. jjw-ncl-merge.nu registered as jj merge tool for .ontology/ NCL conflicts. init-repo.nu for new_project mode. jj/rad not in ontoref requirements — belong in orchestration project manifests. 'Framework RepoKind: ontology/schemas/manifest.ncl gains 'Framework variant; ontoref self-identifies as framework — no domain activates for the protocol itself. Web presence: personal.html and provisioning.html domain subpages. index.html gains "Project Types — Domain Extensions" section with type cards and subpage links. Nav compacted (Arch/Prov labels, solid backdrop-filter background). on+re: vcs-abstraction (adrs: adr-013) and agent-workspace-orchestration Practice nodes; 21 manifest capabilities; state.ncl catalysts updated.
2026-04-07 23:08:29 +01:00
match (vcs detect) {
"jj" => {
# jj has no hook system — hooks are dispatched by the jjw wrapper instead.
# The jjw wrapper calls `on-vcs-event` from git-event.nu after relevant operations.
print " advisory: jj repo detected — skipping hook installation (no jj hook system)"
},
"git" => {
let git_dir = (
do { ^git rev-parse --git-dir } | complete
| if $in.exit_code == 0 { $in.stdout | str trim } else { "" }
)
if ($git_dir | is-empty) { return }
feat: domain extension system, VCS abstraction, personal/provisioning domains, web subpages Domain extension system (ADR-012): bash-layer dispatch activates repo_kind-conditional CLI domains. install.nu copies domains/ tree; short_alias wrappers generated (personal, prov). ore help and describe capabilities domain-aware. personal domain (PersonalOntology): career skills/talks/publications/positioning, CFP pipeline (Watching→Delivered), opportunities lifecycle, content pipeline, Sessionize integration. Daemon pages: /career, /personal. provisioning domain (DevWorkspace/Mixed): FSM state, next transitions, connections graph, gates, workspace card, capabilities, backlog. Daemon page: /provisioning. VCS abstraction layer (ADR-013): reflection/modules/vcs.nu — uniform jj/git API via filesystem detection (.jj/ vs .git/). opmode.nu and git-event.nu migrated off ^git. reflection/bin/jjw.nu — jj + ontoref + Radicle agent workspace lifecycle. jjw-ncl-merge.nu registered as jj merge tool for .ontology/ NCL conflicts. init-repo.nu for new_project mode. jj/rad not in ontoref requirements — belong in orchestration project manifests. 'Framework RepoKind: ontology/schemas/manifest.ncl gains 'Framework variant; ontoref self-identifies as framework — no domain activates for the protocol itself. Web presence: personal.html and provisioning.html domain subpages. index.html gains "Project Types — Domain Extensions" section with type cards and subpage links. Nav compacted (Arch/Prov labels, solid backdrop-filter background). on+re: vcs-abstraction (adrs: adr-013) and agent-workspace-orchestration Practice nodes; 21 manifest capabilities; state.ncl catalysts updated.
2026-04-07 23:08:29 +01:00
let hook_body = if $mode == "daemon" {
$"#!/usr/bin/env bash\nnu \"($env.ONTOREF_ROOT)/reflection/hooks/git-event.nu\" \"$1\" 2>/dev/null || true\n"
} else {
"#!/usr/bin/env bash\n# ontoref local mode — no-op\nexit 0\n"
}
feat: domain extension system, VCS abstraction, personal/provisioning domains, web subpages Domain extension system (ADR-012): bash-layer dispatch activates repo_kind-conditional CLI domains. install.nu copies domains/ tree; short_alias wrappers generated (personal, prov). ore help and describe capabilities domain-aware. personal domain (PersonalOntology): career skills/talks/publications/positioning, CFP pipeline (Watching→Delivered), opportunities lifecycle, content pipeline, Sessionize integration. Daemon pages: /career, /personal. provisioning domain (DevWorkspace/Mixed): FSM state, next transitions, connections graph, gates, workspace card, capabilities, backlog. Daemon page: /provisioning. VCS abstraction layer (ADR-013): reflection/modules/vcs.nu — uniform jj/git API via filesystem detection (.jj/ vs .git/). opmode.nu and git-event.nu migrated off ^git. reflection/bin/jjw.nu — jj + ontoref + Radicle agent workspace lifecycle. jjw-ncl-merge.nu registered as jj merge tool for .ontology/ NCL conflicts. init-repo.nu for new_project mode. jj/rad not in ontoref requirements — belong in orchestration project manifests. 'Framework RepoKind: ontology/schemas/manifest.ncl gains 'Framework variant; ontoref self-identifies as framework — no domain activates for the protocol itself. Web presence: personal.html and provisioning.html domain subpages. index.html gains "Project Types — Domain Extensions" section with type cards and subpage links. Nav compacted (Arch/Prov labels, solid backdrop-filter background). on+re: vcs-abstraction (adrs: adr-013) and agent-workspace-orchestration Practice nodes; 21 manifest capabilities; state.ncl catalysts updated.
2026-04-07 23:08:29 +01:00
for hook in ["post-merge" "post-checkout"] {
let path = $"($git_dir)/hooks/($hook)"
$hook_body | save -f $path
do { ^chmod +x $path } | complete | null
}
},
_ => {} # no-op — no VCS detected
}
}
# ── Notification helpers ──────────────────────────────────────────────────────
def notify-mode-change [from: string, to: string]: nothing -> nothing {
let implications = match $to {
"daemon" => "Push-based sync active. Hooks will push ontology on git merge/checkout.",
"local" => "File-only mode. Hooks are no-ops. DB projection may be stale.",
_ => "",
}
print ""
print $" (ansi cyan_bold)Mode changed(ansi reset): (ansi yellow)($from)(ansi reset) → (ansi green)($to)(ansi reset)"
if ($implications | is-not-empty) {
print $" (ansi dark_gray)($implications)(ansi reset)"
}
print ""
# Emit NATS event if available (best-effort)
let nats_script = $"($env.ONTOREF_ROOT)/reflection/modules/nats.nu"
if ($nats_script | path exists) {
do {
^nu -c $"use ($nats_script) *; nats-emit 'mode.changed' { from: '($from)', to: '($to)' }"
} | complete | null
}
}
# ── Public API ────────────────────────────────────────────────────────────────
# Detect actual operational mode and trigger transition if changed.
# Returns { desired, actual, previous, changed }.
export def "opmode detect" []: nothing -> record {
let desired = (desired-mode)
let actual = (probe-actual $desired)
let previous = (read-lock).mode
if $actual != $previous {
match [$previous $actual] {
["daemon" "local"] => { on-exit-daemon },
[_ "daemon"] => { on-enter-daemon },
[_ "local"] => { on-enter-local },
}
write-lock $actual
if $previous != "unknown" {
notify-mode-change $previous $actual
}
}
{ desired: $desired, actual: $actual, previous: $previous, changed: ($actual != $previous) }
}
# Show current mode status without triggering transitions.
export def "opmode status" []: nothing -> nothing {
let desired = (desired-mode)
let lock = (read-lock)
let actual = (probe-actual $desired)
let drift_tag = if ($desired != $actual) { $" (ansi yellow)⚠ drift(ansi reset)" } else { "" }
let since_tag = if ($lock.since | is-not-empty) { $" since ($lock.since)" } else { "" }
let actual_line = $"(ansi cyan)($actual)(ansi reset)($drift_tag)"
let locked_line = $"($lock.mode)($since_tag)"
print ""
print $" (ansi white_bold)Operational Mode(ansi reset)"
print $" desired: (ansi cyan)($desired)(ansi reset)"
print $" actual: ($actual_line)"
print $" locked: ($locked_line)"
print ""
}
# Pre-flight check for dispatcher. Levels: local | service | committed.
# local → no-op (always passes)
# service → warn if daemon unreachable, continues
# committed → fail-fast if daemon or required services unreachable
export def "opmode preflight" [
level: string, # local | service | committed
--require-db = false, # also check DB reachability
--require-nats = false, # also check NATS reachability
]: nothing -> nothing {
match $level {
"local" => { return },
"service" => {
if not (daemon-available) {
print $" (ansi yellow)WARN(ansi reset) daemon unreachable — running in degraded mode"
}
},
"committed" => {
if not (daemon-available) {
error make { msg: "daemon unreachable — this command requires an active daemon. Check ONTOREF_DAEMON_URL." }
}
if $require_db {
let root = (project-root)
let cfg_path = $"($root)/.ontoref/config.ncl"
if ($cfg_path | path exists) {
let r = (do { ^nickel export --format json $cfg_path } | complete)
let db_enabled = if $r.exit_code == 0 {
try { $r.stdout | from json | get -o db.enabled | default false } catch { false }
} else { false }
if not $db_enabled {
error make { msg: "DB not enabled in .ontoref/config.ncl — this command requires db. Set db.enabled = true." }
}
}
}
},
_ => {
error make { msg: $"unknown preflight level: ($level). Use: local | service | committed" }
},
}
}