Skip to content
Merged
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
51 changes: 51 additions & 0 deletions .claude/hooks/statusline.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash
# Project statusline: extends user statusline with osac + ai-workflows sync status
# Opt out: set CLAUDE_REPO_STATUSLINE_DISABLED=1 in .claude/settings.local.json env

input=$(cat)

if [[ "${CLAUDE_REPO_STATUSLINE_DISABLED:-}" == "1" ]]; then
exit 0
fi

project_dir="${1:-}"

if command -v jq >/dev/null 2>&1 && [[ -f "${HOME}/.claude/settings.json" ]]; then
USER_STATUSLINE_CMD=$(jq -r '.statusLine.command // empty' "${HOME}/.claude/settings.json" 2>/dev/null)
if [[ -n "${USER_STATUSLINE_CMD}" ]]; then
printf "%s\n" "$input" | bash -c "${USER_STATUSLINE_CMD}"
fi
fi

GREEN='\033[32m'
YELLOW='\033[33m'
GRAY='\033[90m'
RESET='\033[0m'

log_info() { printf '%b%s%b' "$GREEN" "$1" "$RESET"; }
log_warning() { printf '%b%s%b' "$YELLOW" "$1" "$RESET"; }
log_muted() { printf '%b%s%b' "$GRAY" "$1" "$RESET"; }

repo_status() {
local dir="$1" name="$2"
[[ -d "$dir" ]] || { log_muted "$name: not found"; return; }

local branch behind
branch=$(git -C "$dir" branch --show-current 2>/dev/null) || branch="detached"
[[ -n "$branch" ]] || branch="detached"
behind=$(git -C "$dir" rev-list HEAD..origin/main --count 2>/dev/null) || { log_muted "$name: $branch ?"; return; }

if [[ "$behind" -eq 0 ]]; then
log_info "$name: $branch ✓"
else
log_warning "$name: $branch ↓${behind} behind"
fi
}

REPO_DIR="${project_dir:-$(printf '%s' "$input" | jq -r '.workspace.project_dir // empty' 2>/dev/null)}"
AI_DIR="${HOME}/.ai-workflows"

ws=$(repo_status "$REPO_DIR" "osac")
ai=$(repo_status "$AI_DIR" "ai-workflows")

printf '%b %b %b\n' "$ws" "${GRAY}|${RESET}" "$ai"
33 changes: 33 additions & 0 deletions .claude/hooks/update-ai-context.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# SessionStart hook: fetch+rebase ai-workflows so the AI agent
# always has the latest skills and workflow phases.

fetch_and_rebase() {
local dir="$1" name="$2"
[[ -d "$dir" ]] || return 0

if ! git -C "$dir" fetch origin -q 2>/dev/null; then
echo "$name: fetch failed"
return 0
fi

local head_before head_after
head_before="$(git -C "$dir" rev-parse HEAD)"

trap 'git -C "$dir" rebase --abort 2>/dev/null || true' INT TERM

if git -C "$dir" rebase origin/main --autostash -q >/dev/null 2>&1; then
head_after="$(git -C "$dir" rev-parse HEAD)"
if [[ "$head_before" == "$head_after" ]]; then
echo "$name: up to date"
else
echo "$name: updated"
fi
else
git -C "$dir" rebase --abort 2>/dev/null || true
echo "$name: rebase conflict, skipped"
fi
trap - INT TERM
}

fetch_and_rebase "${HOME}/.ai-workflows" "ai-workflows"
58 changes: 58 additions & 0 deletions .claude/rules/architecture-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Architecture Patterns

## Multi-tenancy

Tenant-scoped resources include tenant isolation metadata:
- `metadata.annotations["osac.openshift.io/tenant"]` for tenant scoping
- `metadata.annotations["osac.openshift.io/owner-reference"]` for resource hierarchy
- OPA policies enforce isolation at runtime
- Never skip tenant isolation metadata in new tenant-scoped resources
- Use annotations for owner references, not separate fields
- Provider-defined resources (NetworkClass, ExternalIPPool) are exempt — tenants do not interact with them directly

## Resource Hierarchy

```text
Cluster Resources:
ClusterOrder → provisions OpenShift clusters via Hosted Control Planes

Compute Resources:
ComputeInstance → KubeVirt VM, attached to Subnets + SecurityGroups

Networking Resources:
NetworkClass (platform-defined, read-only for tenants)
└── VirtualNetwork (tenant L2 network with CIDR)
├── Subnet (CIDR range within VirtualNetwork)
├── SecurityGroup (firewall rules scoped to VirtualNetwork)
└── NATGateway (SNATs egress traffic through an ExternalIP)

External IP Resources:
ExternalIPPool (platform-defined, external IP ranges)
├── ExternalIP (allocated from pool)
└── ExternalIPAttachment (binds ExternalIP to ComputeInstance)

Tenant Resources:
Tenant → namespace and resource isolation
```

Parent-child relationships use owner reference annotations (`osac.openshift.io/owner-reference`).

## Service Stack (fulfillment-service)

- PostgreSQL for persistent storage
- gRPC with grpc-gateway for REST/JSON support
- Controller-runtime for Kubernetes integration
- OPA for authorization policies
- Prometheus for metrics

## Integration Testing (fulfillment-service)

- Kind clusters (named "fulfillment-service-it")
- TLS with SNI routing via Envoy Gateway
- Keycloak for authentication
- Requires `/etc/hosts` entries:
- `127.0.0.1 keycloak.keycloak.svc.cluster.local`
- `127.0.0.1 fulfillment-api.osac.svc.cluster.local`
- `127.0.0.1 fulfillment-internal-api.osac.svc.cluster.local`
- Use `IT_KEEP_KIND=true` to preserve cluster for debugging
- Clean up with: `kind delete cluster --name fulfillment-service-it`
20 changes: 20 additions & 0 deletions .claude/rules/cli-ux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# CLI UX Guidelines

The OSAC CLI (`fulfillment-service/internal/cmd/cli/`) is a tenant-facing tool for managing infrastructure resources. It is NOT a Kubernetes admin tool - end users should not need to know k8s.

## Design Inspiration

- **Primary reference**: kubectl - OSAC APIs are modeled after k8s, so the CLI naturally follows kubectl patterns
- **Per-feature reference**: when implementing new commands, also look at cloud CLIs (AWS, GCP, Rafay, VCD) for inspiration on how they solved the same problem
- The goal is not to copy any single CLI, but to use kubectl as the default and evaluate alternatives per-feature

## Existing Verbs

`create`, `get`, `describe`, `edit`, `delete`, `annotate`, `label`, `login`, `logout`, `lookup`, `console`, `whoami`, `version`

## When Adding New CLI Commands

- Check if kubectl has an equivalent command and follow its UX patterns
- If kubectl doesn't have one, survey cloud CLIs (az, gcloud, aws) for the closest equivalent
- Keep commands non-interactive and scriptable by default
- All new commands must work without assuming k8s knowledge from the user
3 changes: 3 additions & 0 deletions .claude/rules/networking-design-alignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Networking Design Alignment

When working on networking-related tasks (Connectivity&Fabric component, networking resources like VirtualNetwork/Subnet/SecurityGroup/ExternalIP/NATGateway/NetworkClass, NICs, ports, interfaces, network attachments, VLANs, fabric/K8s managers, dispatcher, MetalLB, DNS, CIDR, DNAT/SNAT, peering, metering/QoS), read `.design/context/networking-decisions.md` before planning, implementing, or reviewing. For deep context, read the full designs linked from that file.
17 changes: 17 additions & 0 deletions .claude/rules/request-path-tracing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Request Path Tracing

When designing or implementing features triggered by external requests (API endpoints, CLI commands), trace the request path from the user-facing entry point to the handler before writing code. List each layer the request passes through (REST gateway, gRPC interceptors, middleware, server). Any layer that transforms, filters, or routes the request is an affected component - even if its code doesn't change.

A layer that silently drops or ignores the feature's input is a bug that unit tests at the handler level won't catch.

## OSAC Example

The fulfillment-service REST gateway uses grpc-gateway with `DefaultHeaderMatcher`, which only forwards permanent HTTP headers and `Grpc-Metadata-*` prefixed headers to gRPC metadata. Custom HTTP headers are silently dropped. When a feature relies on a custom header reaching the gRPC server, the gateway's header matcher configuration must be checked and updated.

Request path for REST API calls:

```text
HTTP client → REST gateway (grpc-gateway mux) → gRPC interceptors → server handler
```

Check `fulfillment-service/internal/cmd/service/start/restgateway/start_rest_gateway_cmd.go` for gateway configuration when adding new headers.
43 changes: 43 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"permissions": {
"allow": [
"Bash(date:*)",
"Bash(echo:*)",
"Bash(cat:*)",
"Bash(ls:*)",
"Bash(mkdir:*)",
"Bash(wc:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(sort:*)",
"Bash(grep:*)",
"Bash(tr:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git status:*)",
"Bash(git log:*)",
"Bash(git diff:*)",
"Bash(git tag:*)",
"Bash(jira issue list:*)",
"Bash(jira issue view:*)"
]
},
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}/.claude/hooks/update-ai-context.sh\"",
"timeout": 20
}
]
}
]
},
"statusLine": {
Comment thread
eranco74 marked this conversation as resolved.
Resolved
"type": "command",
"command": "input=$(cat); command -v jq >/dev/null || exit 0; dir=$(printf '%s' \"$input\" | jq -r '.workspace.project_dir'); [ -n \"$dir\" ] || exit 0; printf '%s' \"$input\" | bash \"$dir/.claude/hooks/statusline.sh\" \"$dir\""
},
"enabledPlugins": {}
}
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# AI workflows and skills (installed by tools/bootstrap.sh)
skills/
.ai-workflows/
.claude/skills
Comment thread
sk-ilya marked this conversation as resolved.
Resolved
.claude/commands
.claude/agents
.cursor/
.gemini/

# Binaries for programs and plugins
*.exe
*.exe~
Expand Down
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# OSAC Mono-Repo

OSAC (Open Sovereign AI Cloud) is a fulfillment system for provisioning Kubernetes clusters, compute instances, bare-metal hosts, and networking. This mono-repo contains six components, each with its own `CLAUDE.md`/`AGENTS.md` — **read the component's docs before making changes in it**.

## Components

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


| Component | Description |
|-----------|-------------|
| `fulfillment-service/` | gRPC/REST API server + `osac` CLI (PostgreSQL, OPA) |
| `osac-operator/` | Kubernetes operator for CRDs (ClusterOrder, ComputeInstance, Tenant, networking) |
| `osac-aap/` | Ansible playbooks for infrastructure provisioning |
| `osac-installer/` | Helm-based three-phase deployment orchestrator |
| `bare-metal-fulfillment-operator/` | Kubernetes operator for bare-metal host pools |
| `osac-csi-driver/` | CSI meta-driver aggregating vendor storage drivers |

## External Repos

Clone as siblings for cross-repo workflows:

| Repo | Description |
|------|-------------|
| [osac-test-infra](https://github.com/osac-project/osac-test-infra) | E2E pytest tests against the fulfillment-service gRPC API |
| [osac-ui](https://github.com/osac-project/osac-ui) | Web console (React, PatternFly 6) |
| [enhancement-proposals](https://github.com/osac-project/enhancement-proposals) | PRDs and design documents (two-stage EP flow) |
| [docs](https://github.com/osac-project/docs) | Architecture docs and guides |

## Cross-Component Changes

A feature spanning multiple components lands in a single branch and PR. Apply changes in dependency order: fulfillment-service (proto) → osac-operator (CRDs, controllers) → osac-aap (playbooks) → osac-installer (RBAC, Helm). For deployment coordination (image tags, submodules), see `osac-installer/AGENTS.md`.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
54 changes: 54 additions & 0 deletions tools/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Install AI workflow skills for this repo.
#
# Usage: tools/bootstrap.sh
#
# This script:
# 1. Clones or updates flightctl/ai-workflows (prefers ~/.ai-workflows)
# 2. Installs workflows (bugfix, implement, prd, design, e2e)
#
# Re-run anytime to update to latest main.
set -euo pipefail

SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
PROJECT_ROOT="$(realpath "${SCRIPT_DIR}/..")"

AI_WORKFLOWS_REPO="flightctl/ai-workflows"
AI_WORKFLOWS_DIR=""

# --- ai-workflows (flightctl) ---

if [[ -d "${HOME}/.ai-workflows" ]]; then
AI_WORKFLOWS_DIR="$(readlink -f "${HOME}/.ai-workflows")"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Repo files matching bootstrap:\n'
fd -a 'bootstrap\.sh$' . | sed 's#^\./##'

printf '\nGit diff/stat if available:\n'
git diff --stat || true

printf '\ntools/bootstrap.sh outline and relevant lines:\n'
if [ -f tools/bootstrap.sh ]; then
  wc -l tools/bootstrap.sh
  sed -n '1,60p' tools/bootstrap.sh | cat -n
  printf '\nOccurrences of readlink/realpath in tools/bootstrap.sh:\n'
  rg -n 'readlink|realpath|AI_WORKFLOWS_DIR|AI_PROJECTS_DIR' tools/bootstrap.sh || true
fi

printf '\nSystem command availability/docs:\n'
command -v realpath || true
command -v readlink || true
( [ -x "$(command -v realpath)" ] && realpath --version 2>&1 | head -5 ) || true
( [ -x "$(command -v readlink)" ] && readlink --help 2>/dev/null | sed -n '1,12p' ) || true

Repository: osac-project/osac

Length of output: 4823


Use realpath here for consistency.

tools/bootstrap.sh already resolves SCRIPT_DIR and PROJECT_ROOT with realpath; use the same command for AI_WORKFLOWS_DIR instead of mixing in readlink -f.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/bootstrap.sh` at line 22, Update the AI_WORKFLOWS_DIR assignment in
bootstrap.sh to use realpath instead of readlink -f, matching the existing
SCRIPT_DIR and PROJECT_ROOT path-resolution commands.

echo "Updating ai-workflows (${AI_WORKFLOWS_DIR})..."
if ! (cd "$AI_WORKFLOWS_DIR" && git fetch origin -q); then
echo " Fetch failed for ai-workflows. Skipping update."
elif ! (cd "$AI_WORKFLOWS_DIR" && git rebase origin/main --autostash -q); then
(cd "$AI_WORKFLOWS_DIR" && git rebase --abort 2>/dev/null || true)
echo " Rebase failed for ai-workflows. Resolve manually: cd $AI_WORKFLOWS_DIR && git rebase origin/main"
else
echo " ai-workflows up to date"
fi
elif [[ -d "${PROJECT_ROOT}/.ai-workflows" ]]; then
AI_WORKFLOWS_DIR="${PROJECT_ROOT}/.ai-workflows"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's pretty much the same code as in lines 22-31, only with a different path.

echo "Updating ai-workflows (.ai-workflows)..."
if ! (cd "$AI_WORKFLOWS_DIR" && git fetch origin -q); then
echo " Fetch failed. Skipping update."
elif ! (cd "$AI_WORKFLOWS_DIR" && git rebase origin/main --autostash -q); then
(cd "$AI_WORKFLOWS_DIR" && git rebase --abort 2>/dev/null || true)
echo " Rebase failed. Resolve manually: cd $AI_WORKFLOWS_DIR && git rebase origin/main"
else
echo " ai-workflows up to date"
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
else
AI_WORKFLOWS_DIR="${PROJECT_ROOT}/.ai-workflows"
echo "Cloning ai-workflows..."
git clone "https://github.com/${AI_WORKFLOWS_REPO}.git" "${AI_WORKFLOWS_DIR}"
fi

echo "Installing ai-workflows skills..."
AI_WORKFLOWS="bugfix,implement,prd,design,e2e"
"${AI_WORKFLOWS_DIR}/install.sh" all --project "${PROJECT_ROOT}" --workflows "${AI_WORKFLOWS}"

echo ""
echo "AI workflows installed."
Loading