Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,23 @@ venv/
*.pyc
.pytest_cache/

# Packaged binaries/build artifacts - do not commit generated executables.
*.exe
*.msi
*.msix
*.appx
*.appxbundle
*.pyd
*.dll
*.so
*.dylib

# Editor
.idea/
*.swp

# Nova runtime artifacts
.runtime/
.nova/sessions/
agent.db
*.sqlite
Expand Down
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,33 @@

Self-bootstrapping agentic coding environment for **macOS**, **Linux**, and **Windows**, powered by the **Nova LLM** stack — Voss Runtime · Gates of Wonder · RSL · Nova Cortex · NVIDIA backend.

This repo does **not** build Nova; it validates paths to your already-built Nova slice and wires your dev shell (zsh / PowerShell), `AGENTS.md`, skills, and devcontainer.
This repo ships the **source** for the local Lawful Nova shell and validates paths to either the bundled local lawful slice or your own built Nova stack. It wires your dev shell (zsh / PowerShell), `AGENTS.md`, skills, and devcontainer.

No generated `.exe`, installer, model weights, checkpoints, `.venv`, or runtime databases are committed. Build artifacts stay local.

Clone → Run one command → Code with Nova.

## Windows-native status

Lawful Nova is Windows-native first. Docker is optional and is only needed later
for Linux parity, isolated CI, container deployment, or GPU/container proof
lanes.

The repo includes:

- `bin/nova.ps1` and `bin/nova.cmd` local CLI shims
- `nova/` source package for the local lawful runtime, CLI, and API
- `scripts/nova_productization_gate.py` for source-level readiness proof
- `setup/verify.ps1` for Windows environment checks

Core local checks:

```powershell
python -m pip install -r requirements-dev.txt
python -m pytest tests -q
python scripts\nova_productization_gate.py
```

[![macOS](https://img.shields.io/badge/macOS-13%2B-black?logo=apple)](https://apple.com)
[![Linux](https://img.shields.io/badge/Linux-Ubuntu%2022.04%2B-orange?logo=linux)](https://ubuntu.com)
[![Windows](https://img.shields.io/badge/Windows-10%2B-0078D6?logo=windows)](https://microsoft.com/windows)
Expand Down
5 changes: 5 additions & 0 deletions bin/nova.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@echo off
setlocal
set "SCRIPT_DIR=%~dp0"
powershell -ExecutionPolicy Bypass -File "%SCRIPT_DIR%nova.ps1" %*
exit /b %ERRORLEVEL%
20 changes: 20 additions & 0 deletions bin/nova.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Repo-local Nova CLI shim for the Lawful Nova slice.
param(
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$Args
)

$ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$ShellRoot = Split-Path -Parent $ScriptRoot
$ProjectRoot = Split-Path -Parent $ShellRoot

$Python = Join-Path $ProjectRoot ".venv\Scripts\python.exe"
if (-not (Test-Path $Python)) {
$Python = Join-Path $ShellRoot ".venv\Scripts\python.exe"
}
if (-not (Test-Path $Python)) {
$Python = "python"
}

& $Python -m nova.cli @Args
exit $LASTEXITCODE
3 changes: 2 additions & 1 deletion config/nova/nova-stack.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Lawful Nova LLM Slice",
"version": "1.0.0",
"description": "Voss Runtime + Gates of Wonder + RSL + Nova Cortex + NVIDIA backend",
"description": "Local Lawful Nova slice: Voss receipt runtime + Gates of Wonder presentation + RSL policy + Nova Cortex, with optional NVIDIA backend",
"stack": {
"voss_runtime": {
"path": "${NOVA_VOSS_RUNTIME_PATH}",
Expand All @@ -22,6 +22,7 @@
"nvidia_backend": {
"endpoint": "${NOVA_MEGATRON_ENDPOINT}",
"gpu_device": "${NOVA_GPU_DEVICE}",
"required_for_local_lawful_slice": false,
"description": "NVIDIA Megatron / NIM GPU compute layer"
}
},
Expand Down
21 changes: 16 additions & 5 deletions config/novarc.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,23 @@ $env:LAWFUL_NOVA_REPO_ROOT = ""

$env:NOVA_PORT = "8080"
$env:NOVA_API_URL = "http://localhost:$($env:NOVA_PORT)"
$env:NOVA_CLI = "nova"
if ($env:LAWFUL_NOVA_REPO_ROOT -and (Test-Path (Join-Path $env:LAWFUL_NOVA_REPO_ROOT "bin\nova.ps1"))) {
$env:NOVA_CLI = Join-Path $env:LAWFUL_NOVA_REPO_ROOT "bin\nova.ps1"
} else {
$env:NOVA_CLI = "nova"
}

$env:NOVA_VOSS_RUNTIME_PATH = ""
$env:NOVA_CORTEX_PATH = ""
$env:NOVA_GOW_CONFIG = ""
$env:NOVA_RSL_PATH = ""
if ($env:LAWFUL_NOVA_REPO_ROOT -and (Test-Path (Join-Path $env:LAWFUL_NOVA_REPO_ROOT "nova"))) {
$env:NOVA_VOSS_RUNTIME_PATH = Join-Path $env:LAWFUL_NOVA_REPO_ROOT "nova"
$env:NOVA_CORTEX_PATH = Join-Path $env:LAWFUL_NOVA_REPO_ROOT "nova"
$env:NOVA_RSL_PATH = Join-Path $env:LAWFUL_NOVA_REPO_ROOT "nova"
$env:NOVA_GOW_CONFIG = Join-Path $env:LAWFUL_NOVA_REPO_ROOT "config\nova\nova-stack.json"
} else {
$env:NOVA_VOSS_RUNTIME_PATH = ""
$env:NOVA_CORTEX_PATH = ""
$env:NOVA_GOW_CONFIG = ""
$env:NOVA_RSL_PATH = ""
}
$env:NOVA_SLICE_CONFIG = "$env:USERPROFILE\.nova\nova-stack.json"

$env:NOVA_GPU_DEVICE = "0"
Expand Down
1 change: 1 addition & 0 deletions nova/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Lawful Nova LLM runtime package."""
64 changes: 64 additions & 0 deletions nova/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""HTTP compatibility surface for the local Lawful Nova slice."""

from __future__ import annotations

import os
import json
from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel, Field

from nova.lawful_llm import LawfulLLM


class ChatRequest(BaseModel):
prompt: str = Field(min_length=1)
tenant_id: str = "local"
capability: str = "observe"


app = FastAPI(title="Local Lawful Nova API", version="0.1.0")


@app.get("/health")
def health() -> dict[str, str]:
return {"status": "ok", "service": "nova_local_api"}


@app.post("/v1/chat")
def chat(request: ChatRequest) -> dict[str, Any]:
llm = LawfulLLM(operator_session_id="nova-local-api", signing_secret="local-api-secret")
turn = llm.ask(
request.prompt,
tenant_id=request.tenant_id,
capability=request.capability,
)
return {
"text": turn.text,
"decision": turn.voss_runtime["decision"],
"receipt": turn.receipt,
"chain": _receipt_chain(turn.receipt),
"receipt_verified": llm.verify_receipt(turn.receipt),
}


def _receipt_chain(receipt: dict[str, Any]) -> dict[str, Any]:
payload = json.loads(str(receipt["payload"]))
return {
"identity": payload["identity"],
"trace": payload["trace"],
"authority_boundary": payload["authority_boundary"],
"reproducibility": payload["reproducibility"],
}


def main() -> None:
import uvicorn

port = int(os.environ.get("NOVA_PORT", "8080"))
uvicorn.run("nova.api:app", host="127.0.0.1", port=port, log_level="info")


if __name__ == "__main__":
main()
127 changes: 127 additions & 0 deletions nova/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"""Repo-local Nova CLI for the Lawful Nova runtime slice."""

from __future__ import annotations

import argparse
import json
import sys
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any
from urllib.error import URLError
from urllib.request import Request, urlopen

from nova.lawful_llm import LawfulLLM


@dataclass(frozen=True)
class Check:
status: str
detail: str = ""


def _http_health(url: str) -> Check:
try:
request = Request(url.rstrip("/") + "/health", headers={"Accept": "application/json"})
with urlopen(request, timeout=2) as response:
body = response.read().decode("utf-8", errors="replace")
return Check(status="ok", detail=body)
except (OSError, URLError) as exc:
return Check(status="warn", detail=str(exc))


def collect_health() -> dict[str, Any]:
direct_status = "ok"
direct_detail = ""
try:
llm = LawfulLLM(operator_session_id="nova-local-cli", signing_secret="local-dev-secret")
turn = llm.ask("observe lawful nova health", tenant_id="local", capability="observe")
direct_detail = turn.voss_runtime["decision"]
except Exception as exc: # pragma: no cover - defensive diagnostic
direct_status = "fail"
direct_detail = str(exc)

return {
"service": "nova_local_cli",
"repo_root": str(Path.cwd()),
"direct_lawful_llm": asdict(Check(status=direct_status, detail=direct_detail)),
"lawful_brain_api": asdict(_http_health("http://127.0.0.1:8791")),
"operator_kernel_api": asdict(_http_health("http://127.0.0.1:8790")),
}


def _print(payload: dict[str, Any], *, as_json: bool) -> None:
if as_json:
print(json.dumps(payload, sort_keys=True))
return
for key, value in payload.items():
if isinstance(value, dict):
print(f"{key}: {value.get('status')} {value.get('detail', '')}".rstrip())
else:
print(f"{key}: {value}")


def health_command(args: argparse.Namespace) -> int:
payload = collect_health()
_print(payload, as_json=args.json)
return 0 if payload["direct_lawful_llm"]["status"] == "ok" else 1


def ask_command(args: argparse.Namespace) -> int:
llm = LawfulLLM(operator_session_id="nova-local-cli", signing_secret="local-dev-secret")
turn = llm.ask(
args.prompt,
tenant_id=args.tenant,
capability=args.capability,
)
payload = {
"text": turn.text,
"receipt_verified": llm.verify_receipt(turn.receipt),
"decision": turn.voss_runtime["decision"],
}
_print(payload, as_json=args.json)
return 0


def serve_command(args: argparse.Namespace) -> int:
from nova.api import main

main()
return 0


def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="nova", description="Lawful Nova local CLI")
sub = parser.add_subparsers(dest="command", required=True)

health = sub.add_parser("health", help="Check local Lawful Nova readiness")
health.add_argument("--json", action="store_true", help="Emit machine-readable JSON")
health.set_defaults(func=health_command)

chat = sub.add_parser("chat", help="Ask the local Lawful Nova slice")
chat.add_argument("prompt", nargs="?", default="observe lawful nova")
chat.add_argument("--tenant", default="local")
chat.add_argument("--capability", default="observe")
chat.add_argument("--json", action="store_true")
chat.set_defaults(func=ask_command)

run = sub.add_parser("run", help="Run a one-shot local Lawful Nova prompt")
run.add_argument("prompt")
run.add_argument("--tenant", default="local")
run.add_argument("--capability", default="observe")
run.add_argument("--json", action="store_true")
run.set_defaults(func=ask_command)

serve = sub.add_parser("serve", help="Start the local Lawful Nova /health API")
serve.set_defaults(func=serve_command)
return parser


def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
return int(args.func(args))


if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
11 changes: 11 additions & 0 deletions nova/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Governance exceptions for the lawful Nova runtime."""

from __future__ import annotations


class GovernanceViolationError(Exception):
"""Raised when RSL, admission, or receipt checks fail."""

def __init__(self, message: str, *, code: str = "GOVERNANCE-VIOLATION") -> None:
super().__init__(message)
self.code = code
5 changes: 5 additions & 0 deletions nova/governance/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Nova governance primitives."""

from nova.governance import ledger, proof_gate, seams

__all__ = ["ledger", "proof_gate", "seams"]
25 changes: 25 additions & 0 deletions nova/governance/ledger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Append-only governance event ledger for lawful Nova turns."""

from __future__ import annotations

import json
import os
from pathlib import Path
from typing import Any


def ledger_path() -> Path | None:
raw = os.environ.get("NOVA_GOVERNANCE_LEDGER_PATH", "").strip()
if not raw:
return None
return Path(raw)


def append_jsonl(record: dict[str, Any]) -> Path | None:
path = ledger_path()
if path is None:
return None
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(record, ensure_ascii=True, sort_keys=True) + "\n")
return path
Loading