From 6695730a92b6d9c5ce8bcd68fbee10e289874a62 Mon Sep 17 00:00:00 2001 From: knabben Date: Sat, 25 Apr 2026 20:44:05 -0300 Subject: [PATCH 1/2] docs: initialize project constitution to v1.0.0 Establishes five governing principles for the Observatio ClusterAPI monitoring platform: Observability-First, Real-Time Visibility, CAPI Resource Model Compliance, AI-Augmented Troubleshooting, and Test-Driven Quality. Includes technology stack constraints and development workflow gates. Co-Authored-By: Claude Sonnet 4.6 --- .specify/memory/constitution.md | 120 ++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 .specify/memory/constitution.md diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md new file mode 100644 index 0000000..95223aa --- /dev/null +++ b/.specify/memory/constitution.md @@ -0,0 +1,120 @@ + + +# Observātiō Constitution + +## Core Principles + +### I. Observability-First + +Every cluster component MUST expose structured health and status data. Metrics, conditions, +events, and logs MUST be collected across all infrastructure layers — from the management +cluster down to individual Machines. Data that cannot be observed cannot be managed. + +- All new API handlers MUST include structured logging with request context. +- Cluster, MachineDeployment, and Machine resources MUST expose condition-level health status. +- Error states MUST propagate to the dashboard with actionable, human-readable descriptions. + +### II. Real-Time Visibility + +Data presented to operators MUST reflect the current cluster state without requiring a manual +page refresh. WebSocket streaming is the primary delivery mechanism for live data. + +- UI components displaying cluster state MUST consume WebSocket events, not poll REST endpoints. +- Watchers for Cluster, Machine, and MachineDeployment MUST emit delta updates on every change. +- Dashboard render latency from server event to UI update MUST stay below 2 seconds under + normal operating load. + +### III. ClusterAPI Resource Model Compliance + +All features and domain models MUST align with the ClusterAPI resource hierarchy: +Cluster → MachineDeployment → Machine, with optional ClusterClass support. +No proprietary infrastructure-specific abstractions are permitted at the core domain layer. + +- Models in `webserver/internal/infra/models/` MUST map directly to CAPI CRDs. +- Infrastructure provider details (vSphere, AWS, etc.) MUST be treated as opaque annotations, + not first-class types in the core domain. +- CAPI resource watchers MUST handle both creation and deletion lifecycle events. + +### IV. AI-Augmented Troubleshooting + +The platform MUST provide AI-assisted diagnosis for cluster failure scenarios. LLM integration +MUST be grounded in actual cluster state — it MUST NOT generate advice disconnected from the +specific resource conditions currently observed. + +- AI analysis MUST receive structured condition data from the affected resource before generating + output; generic prompts without cluster context are not permitted. +- LLM tool definitions MUST be scoped to specific cluster resources to reduce hallucination risk. +- AI-generated recommendations MUST be presented as operator suggestions, never as automated + remediation actions. +- Conversation history MUST be scoped to a single troubleshooting session and MUST NOT persist + across sessions without explicit user consent. + +### V. Test-Driven Quality + +All backend and frontend changes MUST be accompanied by tests. Tests MUST be written before or +alongside implementation — not as an afterthought appended after the feature is complete. + +- Backend Go packages MUST maintain `_test.go` coverage for all exported functions and handlers. +- Frontend components containing business logic MUST include Jest tests. +- Integration tests for cluster watchers and processors MUST use CAPI fake clients that faithfully + represent the real API contract — mocks that diverge from actual CRD behaviour are not permitted. +- The full test suite (`make run-tests-backend` and `make run-tests-frontend`) MUST pass before + any PR is merged. + +## Technology Stack + +**Backend**: Go 1.23+ with CAPI controller-runtime client +**Frontend**: Next.js 15, React 19, TypeScript 5, Mantine UI 7, TailwindCSS 4, XYFlow 12 +**Real-time transport**: WebSocket — backend connection pool + frontend `react-use-websocket` +**AI integration**: Anthropic Claude API via `webserver/internal/infra/llm` +**Build tooling**: GNU Make — canonical targets: `build`, `run-backend`, `run-frontend`, +`run-tests-backend`, `run-tests-frontend` +**Frontend package manager**: pnpm + +No new runtime dependency may be introduced without updating this section and providing +justification in the feature plan's Complexity Tracking table. + +## Development Workflow + +1. Features MUST be developed on a dedicated branch following the naming convention + `###-feature-name` (e.g., `001-cluster-health-dashboard`). +2. The **Constitution Check** gate in `plan.md` MUST be completed and pass before Phase 0 + research begins; re-check MUST occur after Phase 1 design. +3. Backend and frontend components are developed and tested independently but MUST integrate + successfully before a feature is marked complete. +4. `make build` MUST succeed before a pull request is opened. +5. All automated tests MUST pass before merge; test failures block merging with no exceptions. + +## Governance + +This constitution supersedes all other project practices and guidelines. In case of conflict, +the constitution wins. + +Amendments require: +1. A documented rationale explaining why the change is necessary. +2. A semantic version bump: MAJOR for breaking governance changes or principle removals, + MINOR for new principles or materially expanded guidance, PATCH for clarifications. +3. An updated `Last Amended` date. +4. A migration plan when the amendment invalidates existing features or workflows. + +All feature plans MUST include a **Constitution Check** section that explicitly validates +compliance with each of the five principles. Any violation MUST be justified in the +Complexity Tracking table before the plan is approved. + +**Version**: 1.0.0 | **Ratified**: 2026-04-25 | **Last Amended**: 2026-04-25 From 5c81ba79ce1b97ec00d0cd92a38ba71415ac428b Mon Sep 17 00:00:00 2001 From: knabben Date: Sat, 25 Apr 2026 21:02:18 -0300 Subject: [PATCH 2/2] feat(build): add unified build system with prerequisite validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/check-prereqs.sh: validates go ≥1.24, node ≥22, pnpm with actionable error messages; supports --go and --node scope flags - Makefile: adds check-prereqs, build-frontend, build-backend as independent targets; fixes npm→pnpm, upgrades asset copy to cp -r, adds test target running both backend and frontend suites Closes specs/001-unified-build-script Co-Authored-By: Claude Sonnet 4.6 --- Makefile | 98 ++++++++++++--------- scripts/check-prereqs.sh | 111 ++++++++++++++++++++++++ specs/001-unified-build-script/tasks.md | 86 ++++++++++++++++++ 3 files changed, 256 insertions(+), 39 deletions(-) create mode 100644 scripts/check-prereqs.sh create mode 100644 specs/001-unified-build-script/tasks.md diff --git a/Makefile b/Makefile index 80a4426..7f86f33 100644 --- a/Makefile +++ b/Makefile @@ -15,8 +15,11 @@ SHELL := /usr/bin/env bash .DEFAULT_GOAL := help -BINARY_PATH="${PWD}/output/observatio" -BUILD_PATH="../webserver/internal/web/handlers/build/" +BINARY_PATH := $(PWD)/output/observatio +FRONTEND_DIR := front +FRONTEND_OUT := $(FRONTEND_DIR)/output +EMBED_TARGET := webserver/internal/web/handlers/build +PREREQS_SCRIPT := scripts/check-prereqs.sh ## -------------------------------------- ## Help @@ -24,7 +27,16 @@ BUILD_PATH="../webserver/internal/web/handlers/build/" ##@ help: help: ## Display this help - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +## -------------------------------------- +## Prerequisites +## -------------------------------------- +##@ prereqs: + +.PHONY: check-prereqs +check-prereqs: ## Validate all required tools (go ≥1.24, node ≥22, pnpm) + @bash $(PREREQS_SCRIPT) ## -------------------------------------- ## Linters @@ -32,57 +44,65 @@ help: ## Display this help ##@ lint: .PHONY: lint-backend -lint-backend: ## Lint codebase +lint-backend: ## Lint backend codebase docker run --rm -v $(PWD)/webserver:/app -w /app golangci/golangci-lint golangci-lint run -v --fix .PHONY: lint-frontend -lint-frontend: ## Lint codebase - cd front; \ - npm run lint - +lint-frontend: ## Lint frontend codebase + cd $(FRONTEND_DIR) && pnpm run lint ## -------------------------------------- -## Development targets +## Development ## -------------------------------------- -##@ run-backend: +##@ development: .PHONY: run-backend -run-backend: ## Build the binary using local golang - pushd webserver; \ - go run . $(what) --dev; \ - popd; - -.PHONY: run-tests-backend -run-tests-backend: ## Run backend tests - pushd webserver; \ - go test ./... -v -cover ;\ - popd; +run-backend: ## Run backend server in development mode (no static hosting) + pushd webserver && go run . $(what) --dev && popd .PHONY: run-frontend -run-frontend: ## Run the frontend locally - pushd front; \ - pnpm run dev; \ - popd; +run-frontend: ## Run frontend dev server (http://localhost:3000) + pushd $(FRONTEND_DIR) && pnpm run dev && popd + +## -------------------------------------- +## Tests +## -------------------------------------- +##@ test: + +.PHONY: run-tests-backend +run-tests-backend: ## Run backend test suite + pushd webserver && go test ./... -v -cover && popd .PHONY: run-tests-frontend -run-tests-frontend: ## Run frontend tests - pushd front; \ - npm run test; \ - popd; +run-tests-frontend: ## Run frontend test suite + pushd $(FRONTEND_DIR) && pnpm run test && popd + +.PHONY: test +test: run-tests-backend run-tests-frontend ## Run all tests (backend + frontend) ## -------------------------------------- -## Building +## Build — independent stages ## -------------------------------------- ##@ build: -.PHONY: build -build: ## Build the full stack - pushd front; \ - npm run build; \ - find ${BUILD_PATH} ! -name 'index.html' ! -name 'build' -type "f,d" -exec rm -fr {} +; \ - mv output/* ${BUILD_PATH} ; \ - popd; - pushd webserver; \ - CGO_ENABLED=0 go build -o ${BINARY_PATH} .; \ - popd; +.PHONY: build-frontend +build-frontend: ## Build frontend bundle independently (requires node ≥22 + pnpm) + @bash $(PREREQS_SCRIPT) --node + pushd $(FRONTEND_DIR) && pnpm install --frozen-lockfile && pnpm run build && popd +.PHONY: build-backend +build-backend: ## Build backend binary independently (requires go ≥1.24; run build-frontend first for full embed) + @bash $(PREREQS_SCRIPT) --go + mkdir -p output + pushd webserver && CGO_ENABLED=0 go build -o $(BINARY_PATH) . && popd + +.PHONY: build +build: check-prereqs build-frontend ## Build full stack — validates prereqs, builds frontend, copies assets, compiles binary + @echo "[build] Copying frontend assets to embed target..." + rm -rf $(EMBED_TARGET) + mkdir -p $(EMBED_TARGET) + cp -r $(FRONTEND_OUT)/. $(EMBED_TARGET)/ + @echo "[build] Compiling backend binary..." + mkdir -p output + pushd webserver && CGO_ENABLED=0 go build -o $(BINARY_PATH) . && popd + @echo "[build] Done — binary at $(BINARY_PATH)" diff --git a/scripts/check-prereqs.sh b/scripts/check-prereqs.sh new file mode 100644 index 0000000..2d5f8b7 --- /dev/null +++ b/scripts/check-prereqs.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# Validates required tools before any build step runs. +# Usage: +# check-prereqs.sh — check all tools +# check-prereqs.sh --go — check Go only +# check-prereqs.sh --node — check Node + pnpm only +# +# Exit 0 if all requested tools pass; non-zero otherwise. + +set -euo pipefail + +MIN_GO_MAJOR=1 +MIN_GO_MINOR=24 +MIN_NODE_MAJOR=22 + +CHECK_GO=false +CHECK_NODE=false + +# If no flags given, check everything +if [[ $# -eq 0 ]]; then + CHECK_GO=true + CHECK_NODE=true +else + for arg in "$@"; do + case "$arg" in + --go) CHECK_GO=true ;; + --node) CHECK_NODE=true ;; + *) echo "[check] Unknown flag: $arg" >&2; exit 1 ;; + esac + done +fi + +FAILED=false + +# --- version helpers --- + +version_gte() { + # Returns 0 (true) if $1 >= $2 using sort -V + printf '%s\n%s\n' "$2" "$1" | sort -V -C +} + +# --- Go --- +check_go() { + if ! command -v go >/dev/null 2>&1; then + echo "[check] go MISSING — install from https://go.dev/doc/install" + return 1 + fi + + local raw + raw=$(go version 2>&1) + # "go version go1.24.1 linux/amd64" → "1.24.1" + local ver + ver=$(echo "$raw" | grep -oE 'go[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1 | sed 's/go//') + + local required="${MIN_GO_MAJOR}.${MIN_GO_MINOR}" + if version_gte "$ver" "$required"; then + echo "[check] go ${ver} ✓" + else + echo "[check] go ${ver} ✗ (need ≥ ${required} — update at https://go.dev/doc/install)" + return 1 + fi +} + +# --- Node --- +check_node() { + if ! command -v node >/dev/null 2>&1; then + echo "[check] node MISSING — install Node ${MIN_NODE_MAJOR} LTS from https://nodejs.org or via nvm" + return 1 + fi + + local ver + ver=$(node --version 2>&1 | sed 's/v//') + local major + major=$(echo "$ver" | cut -d. -f1) + + if [[ "$major" -ge "$MIN_NODE_MAJOR" ]]; then + echo "[check] node ${ver} ✓" + else + echo "[check] node ${ver} ✗ (need ≥ ${MIN_NODE_MAJOR} — https://nodejs.org)" + return 1 + fi +} + +# --- pnpm --- +check_pnpm() { + if ! command -v pnpm >/dev/null 2>&1; then + echo "[check] pnpm MISSING — run: npm install -g pnpm" + return 1 + fi + local ver + ver=$(pnpm --version 2>&1) + echo "[check] pnpm ${ver} ✓" +} + +# --- run requested checks --- +if $CHECK_GO; then + check_go || FAILED=true +fi + +if $CHECK_NODE; then + check_node || FAILED=true + check_pnpm || FAILED=true +fi + +if $FAILED; then + echo "" + echo "[check] One or more prerequisites are missing or outdated. Resolve the issues above and retry." + exit 1 +fi + +echo "[check] All prerequisites satisfied." diff --git a/specs/001-unified-build-script/tasks.md b/specs/001-unified-build-script/tasks.md new file mode 100644 index 0000000..88d2327 --- /dev/null +++ b/specs/001-unified-build-script/tasks.md @@ -0,0 +1,86 @@ +--- +description: "Task list for Unified Build System" +--- + +# Tasks: Unified Build System + +**Input**: Design documents from `specs/001-unified-build-script/` +**Prerequisites**: plan.md ✅, spec.md ✅, research.md ✅, data-model.md ✅, quickstart.md ✅ + +## Format: `[ID] [P?] [Story?] Description` + +--- + +## Phase 1: Setup + +- [X] T001 Verify/create `scripts/` directory at project root +- [X] T002 Verify `webserver/internal/web/handlers/build/` embed target directory exists + +--- + +## Phase 2: Foundational — Prerequisite Validation Script + +**Purpose**: Shared script called by every Makefile target to validate tools before work begins. + +- [X] T003 Create `scripts/check-prereqs.sh` — validates go ≥1.24, node ≥22, pnpm presence with actionable error messages and per-scope filtering (--go, --node flags) + +--- + +## Phase 3: User Story 1 — Validate Development Environment (Priority: P1) 🎯 MVP + +**Goal**: `make check-prereqs` reports all tool issues before any build step runs. + +**Independent Test**: Run `make check-prereqs` with Go uninstalled (or PATH removed); verify +non-zero exit and message naming the missing tool. + +### Implementation for User Story 1 + +- [X] T004 [US1] Add `check-prereqs` target to `Makefile` that calls `scripts/check-prereqs.sh` for all required tools +- [X] T005 [US1] Make `scripts/check-prereqs.sh` executable (`chmod +x`) + +**Checkpoint**: `make check-prereqs` exits 0 on valid setup, non-zero with tool-specific message on failure. + +--- + +## Phase 4: User Stories 2 & 3 — Independent Backend and Frontend Builds (Priority: P1) + +**Goal**: `make build-backend` and `make build-frontend` work independently without the other side present. + +**Independent Test (backend)**: Remove `front/output/`; `make build-backend` still completes and produces a binary stub. +**Independent Test (frontend)**: `make build-frontend` runs with no Go toolchain on PATH and produces `front/output/`. + +### Implementation + +- [X] T006 [P] [US2] Add `build-backend` target to `Makefile` — runs go prereq check then `CGO_ENABLED=0 go build` +- [X] T007 [P] [US3] Add `build-frontend` target to `Makefile` — runs node/pnpm prereq check, `pnpm install --frozen-lockfile`, then `pnpm run build` + +**Checkpoint**: Each target runs independently and exits non-zero on its own failure. + +--- + +## Phase 5: User Story 4 — Unified Production Binary (Priority: P2) + +**Goal**: `make build` produces a single binary with embedded frontend in one command. + +**Independent Test**: Run `make build` on clean checkout; `output/observatio` exists and +starts serving port 8080 with `--dev=false`. + +### Implementation + +- [X] T008 [US4] Fix `build` target in `Makefile` — gate on `check-prereqs`, call `build-frontend`, copy assets cleanly, call `build-backend` +- [X] T009 [US4] Replace asset copy in `Makefile` with clean `rm -rf` + `cp -r` of `front/output/` into `webserver/internal/web/handlers/build/` + +**Checkpoint**: `make build` produces `output/observatio`; binary serves dashboard. + +--- + +## Phase 6: Polish + +- [X] T010 Add `test` target to `Makefile` that runs both `run-tests-backend` and `run-tests-frontend` + +--- + +## Dependencies & Execution Order + +- Phase 1 → Phase 2 → Phase 3 → Phase 4 (T006/T007 parallel) → Phase 5 → Phase 6 +- T006 and T007 can run in parallel (different Makefile targets, no file conflicts)