Skip to content
Closed
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
21 changes: 21 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# CODEOWNERS — main only.
# Every path has an owner (no unowned files). Real owner + wildcard fallback.
# Default branch remains release/ccpe-v1.0; this file governs `main`.

# Fallback: everything owned by the maintainer.
* @G1DO

# Explicit ownership (same maintainer, explicit for audit clarity).
/docs/ @G1DO
/.github/ @G1DO
/backend_api/ @G1DO
/frontend/ @G1DO
/storage/ @G1DO
/ml_engine/ @G1DO
/optimizer/ @G1DO
/aws_collector/ @G1DO
/cloud_optimizer/ @G1DO
/ai_module/ @G1DO
/dashboard/ @G1DO
/data_generation/ @G1DO
/tests/ @G1DO
116 changes: 116 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# verify — sole CI gate for `main` (Starter §CI Gate job).
# Local parity: `poe verify` runs test → migrate → build →
# check:hygiene → diagrams (see pyproject.toml [tool.poe.tasks.verify]).
# CI mirrors the same task verbs; `gate` is the single aggregating check
# required in branch protection. No `|| true`, no `continue-on-error`.
# Triggers: PRs and pushes targeting `main` only, plus nightly on `main`
# and manual dispatch. Does not touch release/ccpe-v1.0.
name: verify

on:
pull_request:
branches: [main]
push:
branches: [main]
schedule:
# Nightly on main (02:17 UTC). Active once this workflow exists on the
# default branch; until then PR/push triggers still cover `main`.
- cron: "17 2 * * *"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: verify-${{ github.ref }}
cancel-in-progress: true

jobs:
# Full local gate: test, migrate, build, hygiene, diagrams.
# Hygiene covers `git diff --check`, compileall, frontend lint, stub-marker
# grep. Diagrams validates versioned `.mmd` sources via mmdc and rejects
# pasted ```mermaid blocks (see poe tasks check:hygiene, diagrams).
verify:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout (full history for diff-check)
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python (match Dockerfile 3.12)
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip

- name: Set up Node (frontend lint + mmdc)
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install Python deps + task runner
run: |
set -eu
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install poethepoet

- name: Install frontend deps (required by check:hygiene)
run: |
set -eu
npm ci --prefix frontend

- name: Install mermaid-cli (diagrams validation)
run: |
set -eu
npm install -g @mermaid-js/mermaid-cli
mmdc --version

- name: Run verify (test → migrate → build → check:hygiene → diagrams)
run: |
set -eu
poe verify

# Full-history secrets scan (gitleaks). Uses .gitleaks.toml allowlist for
# synthetic SYNTHETIC-001 placeholders only; real leaks fail closed.
secrets-history:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install gitleaks (pinned)
run: |
set -eu
GITLEAKS_VERSION="8.30.1"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" -o /tmp/gitleaks.tar.gz
tar -xzf /tmp/gitleaks.tar.gz -C /tmp
sudo mv /tmp/gitleaks /usr/local/bin/gitleaks
gitleaks version

- name: Gitleaks full-history scan
run: |
set -eu
gitleaks detect --source . --config .gitleaks.toml --verbose

# Single aggregating gate — the SOLE required status check in branch
# protection for `main`. Fails unless every required job succeeded.
gate:
runs-on: ubuntu-latest
needs: [verify, secrets-history]
if: always()
steps:
- name: Aggregate required checks
run: |
set -eu
echo "verify=${{ needs.verify.result }} secrets-history=${{ needs.secrets-history.result }}"
test "${{ needs.verify.result }}" = "success"
test "${{ needs.secrets-history.result }}" = "success"
echo "gate ok"
17 changes: 17 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Gitleaks config — Smart Cloud Optimizer (main).
# Full-history secrets scan runs in CI (verify.yml → secrets-history).
# Synthetic demo placeholders (SYNTHETIC-001 family) are not secrets and are
# allowlisted here so the history scan stays green without weakening rules.
# Fail-closed: no allowlisted real-key patterns, no generic skips.

title = "smart-cloud-optimizer"

[allowlist]
description = "Synthetic demo placeholders — never real credentials"
regexTarget = "match"
regexes = [
'''(?i)SYNTHETIC-001''',
'''(?i)synthetic-001''',
'''aws-SYNTHETIC-001''',
'''SYNTHETIC-001''',
]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ command-line experiment on a disposable database copy.
## Develop and maintain

- [Development checks](docs/development/README.md): Python tests, frontend lint/build, and contract verification.
- [Architecture](docs/architecture/README.md): components, identity, persistence, and engine boundaries.
- [Architecture](docs/architecture/README.md): components, identity, persistence, and engine boundaries — versioned `.mmd` diagrams (C1/C2/C3, sequence, state, deployment, ER) are indexed there.
- [API contracts](docs/api/http.md) and [configuration](docs/reference/configuration.md): generated reference and active inputs.
- [Local operations](docs/operations/local-runtime.md): diagnostics, data preservation, and recovery.
- [Security](docs/security/README.md) and [vulnerability reporting](SECURITY.md): current trust limits and reporting route.
Expand Down
65 changes: 25 additions & 40 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,31 @@
The application on `main` uses a TypeScript/Next.js browser UI and a Python
FastAPI backend. The optional Streamlit UI calls the Python engines directly.

```mermaid
flowchart LR
Browser["Browser"]
NextUI["Next.js UI<br/>(port 3000)"]

subgraph BE["FastAPI backend (port 8000)"]
API["FastAPI routers<br/>(trust-based user_id)"]
Storage["storage facade<br/>(shared data API)"]
Optimizer["optimizer<br/>(PuLP right-sizing + rules)"]
ML["ml_engine<br/>(forecasts / anomalies)"]
AImod["ai_module<br/>(Gemini advisor)"]
Collector["aws_collector<br/>(boto3 pipeline)"]
end

DB[("SQLite DB<br/>data/cloud_optimizer.db")]
Synthetic["data_generation.synthetic<br/>(demo seed)"]
AWS["AWS<br/>(Cost Explorer / CloudWatch via boto3)"]
Gemini["Google Gemini<br/>(AI recommendations)"]

Browser --> NextUI
NextUI -->|"HTTP / JSON"| API
API --> Storage
API --> Optimizer
API --> ML
API --> AImod
API --> Collector
Optimizer --> Storage
ML --> Storage
AImod --> Storage
Collector --> Storage
Storage --> DB
API -->|"route-specific SQL"| DB

Collector -. "real mode" .-> AWS
AImod -. "AI calls" .-> Gemini
Synthetic -. "demo mode" .-> Storage

classDef ext stroke-dasharray:5 5,stroke:#888,color:#444;
class AWS,Gemini,Synthetic ext;
```
## Diagram sources (versioned `.mmd` files)

Change diagrams and text together: every behavior change in the same commit
must update the affected diagram file(s) below plus this index.

| Level | Source file | Renders |
| --- | --- | --- |
| C1 system context | [c1-system-context.mmd](c1-system-context.mmd) | Users, the optimizer system, AWS, Gemini |
| C2 containers | [c2-containers.mmd](c2-containers.mmd) | Next.js (:3000), FastAPI (:8000), Streamlit (:8501), SQLite, externals |
| C3 components | [c3-components.mmd](c3-components.mmd) | FastAPI routers, engines, storage facade, SQLite |
| Sequence | [sequence.mmd](sequence.mmd) | Connection save/sync, forecast request |
| State | [state.mmd](state.mmd) | Connection/sync and recommendation lifecycle |
| Deployment | [deployment.mmd](deployment.mmd) | Compose services, network, named volume, ports |
| Data model | [data-model.md](data-model.md) | How to regenerate the authoritative DDL from `storage/db.py` |
| ER | [er-diagram.mmd](er-diagram.mmd) | 30 tables, keys + notable columns (DDL is authority; inventory↔metrics links are logical) |

Cross-cutting choices: [decisions](../decisions/README.md) (currently N/A with
reason; add dated notes there for new hard-to-reverse decisions).

## Overview

Render the versioned sources above with `poe diagrams` (CI validates every
`.mmd` with `mmdc`). Pasted `mermaid` blocks are not used — the canonical
diagrams are the `.mmd` files indexed above (C1/C2/C3, sequence, state,
deployment, ER).

## Boundaries

Expand Down
12 changes: 12 additions & 0 deletions docs/architecture/c1-system-context.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
%% C1 — System context (Smart Cloud Optimizer on main).
%% Actors and external systems only; no internal containers.
%% Sources: docs/architecture/README.md, docker-compose.yml, backend_api/main.py
flowchart LR
user["User<br/>(browser)"]
system["Smart Cloud Optimizer<br/>(Next.js UI + FastAPI backend + SQLite)"]
aws["AWS<br/>(Cost Explorer / CloudWatch / service APIs via boto3)"]
gemini["Google Gemini<br/>(AI recommendations via GOOGLE_MODEL)"]

user -->|"uses (HTTP, Next.js :3000 / FastAPI :8000)"| system
system -->|"collects costs / inventory / metrics<br/>(live sync, stored keys or role creds)"| aws
system -->|"questionnaire prompt -> advice JSON<br/>(requires GOOGLE_API_KEY)"| gemini
30 changes: 30 additions & 0 deletions docs/architecture/c2-containers.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
%% C2 — Containers (deployable units on main).
%% Sources: docs/architecture/README.md, docker-compose.yml, Dockerfile,
%% backend_api/main.py, dashboard/app.py, storage/db.py
flowchart LR
browser["Browser<br/>(user agent)"]

subgraph frontend["Frontend container (:3000)"]
nextui["Next.js UI<br/>(routes, Plotly charts, session helper)"]
end

subgraph backend["Backend container (:8000)"]
fastapi["FastAPI app<br/>(routers + engines + storage facade)"]
end

subgraph legacy["Legacy container (:8501, profile full, optional)"]
streamlit["Streamlit UI<br/>(dashboard/app.py, direct Python calls)"]
end

db[("SQLite DB<br/>(data/cloud_optimizer.db<br/>named volume optimizer-data)")]
aws["AWS<br/>(Cost Explorer / CloudWatch via boto3)"]
gemini["Google Gemini<br/>(AI recommendations)"]

browser -->|"HTTP (pages + API calls)"| nextui
nextui -->|"HTTP / JSON<br/>(NEXT_PUBLIC_API_BASE_URL=http://localhost:8000)"| fastapi
browser -->|"HTTP (optional legacy UI)"| streamlit
fastapi -->|"SQL via storage facade<br/>(some routes use direct SQL)"| db
streamlit -->|"direct Python calls + storage"| db
fastapi -->|"sync via CollectorRunner<br/>(background thread)"| aws
aws -->|"inventory / metrics / costs"| fastapi
fastapi -->|"prompt -> JSON"| gemini
62 changes: 62 additions & 0 deletions docs/architecture/c3-components.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
%% C3 — Components inside the FastAPI backend on main.
%% Sources: backend_api/main.py, backend_api/routers/*,
%% storage/db.py, optimizer/engine.py, ml_engine/forecaster.py,
%% ai_module/recommender.py, aws_collector/runner.py,
%% docs/architecture/README.md, docs/architecture/engines/*
flowchart TB
nextui["Next.js UI<br/>(:3000, HTTP client)"]
streamlit["Streamlit UI<br/>(:8501, direct Python calls)"]

subgraph api["FastAPI routers (backend_api/routers/)"]
auth["auth"]
conn["connections<br/>(test / save / sync)"]
costs["costs + dashboard"]
forecast["forecast<br/>(+ compare)"]
recs["recommendations"]
onboard["on_boarding<br/>(Gemini questionnaire)"]
settings["settings<br/>(runtime_settings.json)"]
end

subgraph engines["Engines"]
collector["aws_collector<br/>(CollectorRunner + service collectors)"]
opt["optimizer<br/>(PuLP LP + rules)"]
ml["ml_engine<br/>(forecast / anomaly)"]
aimod["ai_module<br/>(prompt + Gemini client)"]
end

storage["storage facade<br/>(schema, connections, data APIs)"]
db[("SQLite DB<br/>(data/cloud_optimizer.db)")]
aws["AWS APIs"]
gemini["Google Gemini"]
settingsfile[("runtime_settings.json<br/>(per-user prefs, backend only)")]

nextui --> auth
nextui --> conn
nextui --> costs
nextui --> forecast
nextui --> recs
nextui --> onboard
nextui --> settings

conn --> collector
conn --> storage
costs --> storage
forecast --> ml
recs --> opt
recs --> storage
onboard --> aimod
settings --> settingsfile

collector --> storage
opt --> storage
ml --> storage
aimod --> storage
storage --> db
forecast -->|"route-specific direct SQL"| db

collector -->|"boto3"| aws
aimod -->|"generate_content"| gemini
streamlit --> storage
streamlit --> opt
streamlit --> ml
streamlit --> aimod
28 changes: 28 additions & 0 deletions docs/architecture/deployment.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
%% Deployment — local Compose stack on main.
%% Sources: docker-compose.yml, Dockerfile, .dockerignore,
%% docs/operations/local-runtime.md, docs/architecture/README.md
flowchart TB
subgraph host["Host (loopback only)"]
b1["http://localhost:3000<br/>(frontend)"]
b2["http://localhost:8000<br/>(backend)"]
b3["http://localhost:8501<br/>(streamlit, profile full)"]
end

subgraph net["Compose network: optimizer-net (bridge)"]
fe["frontend service<br/>(optimizer-frontend, :3000)"]
be["backend service<br/>(optimizer-backend, :8000, appuser uid 10001)"]
st["streamlit service<br/>(same image, profile full, :8501)"]
end

vol[("named volume optimizer-data<br/>(seeded from image /app/data<br/>persists cloud_optimizer.db + WAL)")]

b1 --> fe
b2 --> be
b3 --> st
fe -->|"depends_on healthy backend<br/>NEXT_PUBLIC_API_BASE_URL=http://localhost:8000"| be
st -->|"depends_on healthy backend"| be
be --> vol
st --> vol

note["Notes: ports bind 127.0.0.1 on host. backend + streamlit share the image.<br/>runtime_settings.json is NOT persisted (recreated on container rebuild).<br/>No host bind mount on /app/data (keeps appuser-writable ownership)."]
be --- note
Loading
Loading