Metadata-Version: 2.4
Name: hybrisight
Version: 0.1.2
Summary: Read-only cloud and infrastructure efficiency snapshot engine
Author-email: Shawn <shawnthompson66@gmail.com>
License-Expression: Apache-2.0
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.12.5
Requires-Dist: pydantic>=2.8.0
Requires-Dist: PyYAML>=6.0.2
Requires-Dist: boto3>=1.35.0
Requires-Dist: azure-identity>=1.17.1
Requires-Dist: azure-mgmt-resource>=23.1.1
Requires-Dist: azure-mgmt-subscription>=3.1.1
Requires-Dist: azure-mgmt-storage>=21.2.1
Requires-Dist: azure-mgmt-compute>=33.0.0
Requires-Dist: azure-mgmt-network>=28.0.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: rich>=13.7.0
Requires-Dist: alembic>=1.14.0
Provides-Extra: server
Requires-Dist: fastapi>=0.115.0; extra == "server"
Requires-Dist: uvicorn>=0.30.6; extra == "server"
Requires-Dist: sqlalchemy>=2.0.36; extra == "server"
Requires-Dist: python-multipart>=0.0.12; extra == "server"
Requires-Dist: itsdangerous>=2.2.0; extra == "server"
Requires-Dist: weasyprint>=62.3; extra == "server"
Requires-Dist: segno>=1.6.1; extra == "server"
Provides-Extra: dev
Requires-Dist: pytest>=8.3.2; extra == "dev"
Requires-Dist: httpx>=0.27.2; extra == "dev"
Requires-Dist: pyflakes>=3.2.0; extra == "dev"
Requires-Dist: fastapi>=0.115.0; extra == "dev"
Requires-Dist: uvicorn>=0.30.6; extra == "dev"
Requires-Dist: sqlalchemy>=2.0.36; extra == "dev"
Requires-Dist: python-multipart>=0.0.12; extra == "dev"
Requires-Dist: itsdangerous>=2.2.0; extra == "dev"
Requires-Dist: weasyprint>=62.3; extra == "dev"
Requires-Dist: segno>=1.6.1; extra == "dev"
Provides-Extra: prod
Requires-Dist: psycopg2-binary>=2.9.9; extra == "prod"
Dynamic: license-file

# HybriSight

HybriSight is a productized cloud governance assessment for UK SMEs.
It provides non-intrusive, read-only cloud risk and cost clarity through a fixed-fee **Baseline Assessment** and a tracked **90-Day Stabilisation Roadmap**.

HybriSight does not perform penetration testing, exploitation, port scanning, or offensive actions.

## Contents

- [Quick Start](#quick-start)
- [Local Development](#local-development)
- [Production Deployment](#production-deployment)
- [What HybriSight Produces](#what-hybrisight-produces)
- [Analysis Categories](#analysis-categories)
- [Architecture at a Glance](#architecture-at-a-glance)
- [Repository Layout Note](#repository-layout-note)
- [CLI Commands](#cli-commands)
- [Rule Pack (MVP)](#rule-pack-mvp)
- [Tests](#tests)
- [Documentation](#documentation)
- [License](#license)

## What HybriSight Produces

- Technical JSON findings
- Executive HTML report
- PDF-ready summary JSON structure
- Web dashboard views via findings upload
- Tenant-admin delete controls for uploaded artifacts (UI + API)
- Signature verification status for uploaded artifacts
- Deterministic snapshot scoring (`risk_index`, `posture_score`, `confidence_adjusted_score`)

## Latest UI/UX Updates (2026-02-22)

- Executive dashboard KPI polish:
  - qualitative score bands (for example `Weak`, `Critical Exposure`)
  - cleaner labels (`Coverage`, `Confidence-Adjusted Score`)
  - board-friendly money formatting (for example `GBP 8,750`)
- Top findings polish:
  - deduplicated Top 5 display
  - urgency tags (`Immediate`, `Planned`, `Strategic`)
  - cleaner finding-card layout and detail links
- Roadmap polish:
  - interactive `0-30 / 31-60 / 61-90` phase switching
  - cumulative projection row (`Now -> 30d -> 60d -> 90d`)
  - compact posture trajectory curve (SVG, print-friendly) with optional dashed Risk Index overlay
  - phase outcome blocks with projected risk/posture/waste deltas
  - workflow-style roadmap cards with state transition and step lanes
- Portal docs/trust page polish:
  - Delivery Options, Upload, Security/Data Handling, Terms, Privacy, About, Support, Contact pages now share a cleaner guidance-card style

## Analysis Categories

- Security Posture
- Cost Efficiency
- Operational Resilience
- Infrastructure Scalability / Operations
- Deterministic Posture Scoring Model (Risk Index, Posture Score, Confidence-Adjusted Score)

## Architecture at a Glance

- Provider collectors: AWS and Azure metadata-only collectors
- Rule engine: independent YAML-configured checks mapped to stable IDs
- Canonical finding schema: shared across providers
- Reporting: technical + executive outputs
- **Hybrid Frontend:** Python serves the SPA shell and security/session bootstrap, while **React/TypeScript** renders both public and authenticated portal pages.

Detailed architecture and flow are in [`docs/reference/architecture.md`](docs/reference/architecture.md).

## Repository Layout Note

If you see `hybrisight/hybrisight`, this is intentional Python project layout:
- Outer `hybrisight/` is the repository root (docs, tests, `pyproject.toml`, CI files).
- Inner `hybrisight/` is the Python package module (import path `hybrisight.*`).

So this is not two apps; it is one repo containing one importable package.

## Default Operating Model (Commercial)

- Public landing: `/`
- Authenticated portal workspace: `/app`
- Sample executive brief page: `/sample-executive-brief`

HybriSight default delivery model is:
- Hosted SaaS portal operated by HybriSight
- Client runs CLI in ephemeral runtime to generate signed artifacts
- Client uploads artifact to SaaS portal for scoring, reporting, and roadmap output

Assessment outputs are intentionally split:
- `Foundation Review` for low-signal / early-stage environments where must-do controls matter more than a board-grade governance narrative.
- `Governance Brief` for richer environments with enough signal density to support execution-led roadmap and stabilisation planning.

Internal Ops `Delivery Pipeline` tracks the commercial gate for both paths:
- delivery mode (`consultant_led` or `client_run`)
- access state (`not_provisioned` or `provisioned`)
- fixed fee
- mirrored invoice status
- optional payment-required-before-release policy

This is a HybriSight-native product split. It is not positioned as an AWS or Azure Well-Architected review.

Fallback path:
- Cloud-native export upload (no CLI) for clients unable to run snapshot commands.

Data source policy:
- Preferred: HybriSight CLI collection -> signed canonical `ScanResult` JSON.
- Fallback: approved native cloud exports only (partial coverage depending on source type).
- Rejected: unsupported or arbitrary cloud dumps, PDFs, spreadsheets outside the approved list, and mismatched export formats.
- Portal upload UI now keeps the CLI artifact as the default client path and tucks approved native-export fallback behind an explicit alternate route instead of exposing the full policy matrix in the main flow.
- The authenticated CLI page keeps installation as the primary client action and routes approved native-export fallback back into the Upload workspace instead of linking clients into product-architecture pages.
- Duplicate artifacts are blocked by default; the upload UI now surfaces the existing assessment and requires an explicit operator override before importing the same artifact twice.
- Native export constraints:
  - Azure Advisor portal exports are CSV (API is JSON).
  - PDF exports are not supported for native ingestion.
  - AWS IAM Credential Report is CSV.
  - AWS IAM Authorization Details export is JSON/YAML and should be selected explicitly.
  - AWS Trusted Advisor `.xls/.xlsx` is not ingested directly; use CSV/JSON exports or CLI path.

Exception-only path:
- Self-hosted/on-prem web deployment is supported for regulated edge cases, but is not the default delivery model.

## Quick Start

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install '.[dev]'
```

```bash
export HYBRISIGHT_WEB_SESSION_SECRET=$(openssl rand -hex 32)
export HYBRISIGHT_WEB_COMMAND_SIGNING_KEY=$(openssl rand -hex 32)
# Optional logging controls (production defaults shown)
export HYBRISIGHT_WEB_LOG_FORMAT=json
export HYBRISIGHT_WEB_LOG_LEVEL=INFO
# Optional upload size limit (default: 30 MiB)
# export HYBRISIGHT_WEB_MAX_UPLOAD_BYTES=31457280
# Optional: write rotating file logs in addition to stdout
# export HYBRISIGHT_WEB_LOG_FILE=/var/log/hybrisight/web.log
hybrisight-web
```

If your shell PATH is ambiguous or you want to guarantee the repository virtualenv is used, run the entrypoint explicitly:

```bash
/Users/shawn/Documents/hybrisight/.venv/bin/hybrisight-web
```

The HTML now includes `hybrisight-ui-*` meta tags showing the active entrypoint, hashed JS bundle, and resolved `frontend/dist` path. Those are always present for runtime diagnosis. The visible build badge is debug-only and appears only when `HYBRISIGHT_WEB_SHOW_BUILD_INFO=true`.

Run a demo scan and report:

```bash
hybrisight scan aws --demo --output hybrisight-findings.json
hybrisight report --input hybrisight-findings.json --format html --output hybrisight-report.html
```

Add explicit continuity metadata when a client has multiple accounts/subscriptions or parallel reassessment tracks:

```bash
hybrisight scan aws \
  --profile prod \
  --engagement-label "Q2 2026 Pilot Landing Zone" \
  --engagement-key "pilot-landing-zone-q2-2026" \
  --scope-label "AWS Production Platform Accounts" \
  --scope-fingerprint "aws:accounts=111111111111,222222222222:env=prod" \
  --series-label "Monthly Landing Zone Remediation" \
  --series-key "pilot-landing-zone-monthly" \
  --series-cadence monthly \
  --output hybrisight-findings.json
```

Validate read-only credentials before a real scan:

```bash
hybrisight doctor aws --profile hybrisight --regions all
hybrisight doctor azure --subscription <SUBSCRIPTION_ID>
```

Key docs:
- [Getting Started](docs/ops/getting-started.md)
- [AWS Read-Only Access Setup](docs/access-setup/aws-readonly-access.md)
- [Azure Read-Only Access Setup](docs/access-setup/azure-readonly-access.md)
- [Credential Handling Policy](docs/access-setup/credential-handling.md)
- [Tenant Signing Keys](docs/product/tenant-signing-keys.md)
- [Client Onboarding Guide](docs/product/client-onboarding-guide.md)
- [Client Journey Orchestration](docs/product/client-journey-orchestration.md)
- [Client Journey Orchestration Schema](docs/product/client-journey-orchestration-schema.md)
- [Continuity Implementation Plan](docs/product/continuity-implementation-plan.md)
- [Client Trend and Re-Assessment Continuity](docs/product/client-trend-and-continuity.md)
- [Portal Shell UX](docs/product/portal-shell-ux.md)
- [Native Export Guides](docs/product/native-export-guides.md)
- [Journey Analysis](docs/product/journey-analysis.md)
- [Foundation Review](docs/product/foundation-review.md)
- [Engagements Workflow](docs/product/engagements-workflow.md)
- [Production Readiness Checklist](docs/ops/production-readiness.md)

## Local Development

Frontend local run:

```bash
cd frontend
npm ci && npm run build  
npm run dev
```

Optional browser performance guardrails:

```bash
cd frontend
npm run test:e2e -- perf-route-transitions.spec.ts
```

Optional tuning:
- `E2E_PUBLIC_ROUTE_BUDGET_MS` sets the landing/public route-transition budget in milliseconds.
- `E2E_APP_ROUTE_BUDGET_MS` sets the authenticated in-app route-transition budget in milliseconds.
- `RUN_E2E=1 ./scripts/verify-local.sh` includes the Playwright suite, including the route-performance guard.

Backend and portal local run:

```bash
export HYBRISIGHT_WEB_SESSION_SECRET=$(openssl rand -hex 32)
export HYBRISIGHT_WEB_COMMAND_SIGNING_KEY=$(openssl rand -hex 32)
export HYBRISIGHT_WEB_LOG_FORMAT=text
# Optional upload size limit (default: 30 MiB)
# export HYBRISIGHT_WEB_MAX_UPLOAD_BYTES=31457280
hybrisight-web
```

If the shell is not activated or you want to avoid PATH confusion, use:

```bash
/Users/shawn/Documents/hybrisight/.venv/bin/hybrisight-web
```

Canonical schema note:
- HybriSight now tracks a current schema revision (`schema_revisions`) and supports an explicit migration command.
- Use `hybrisight-web migrate` before starting persistent environments when the database is behind the current revision.
- Local/dev SQLite now auto-migrates by default when schema bootstrap is allowed, so the repository-root `hybrisight_web.db` can be reused without running `migrate` manually.
- Empty hosted databases may bootstrap on first start when `HYBRISIGHT_WEB_AUTO_MIGRATE=true`, but non-empty hosted databases should be migrated explicitly.
- For the default local SQLite path, the reset command is:

```bash
rm -f /Users/shawn/Documents/hybrisight/hybrisight_web.db
/Users/shawn/Documents/hybrisight/.venv/bin/hybrisight-web
```

- For an explicit migration run, use:

```bash
/Users/shawn/Documents/hybrisight/.venv/bin/hybrisight-web migrate
```

- The reset command above will wipe local dev data in that database. That is still acceptable for local/dev, but no longer the intended hosted upgrade path.
- When `HYBRISIGHT_WEB_DATABASE_URL` is unset, local SQLite now defaults to the repository-root [hybrisight_web.db](/Users/shawn/Documents/hybrisight/hybrisight_web.db), not the current working directory.
- `HYBRISIGHT_WEB_ADMIN_EMAIL` must be a single bootstrap admin email, not a comma-separated list. Use `HYBRISIGHT_INTERNAL_OPS_EMAILS` for allowlists.

UI Creds for local run:
username: `shawnthompson66@gmail.com`
pwd: `admin123`

Local/dev docs:
- [Getting Started](docs/ops/getting-started.md)
- [Testing](docs/ops/testing.md)
- [Troubleshooting](docs/troubleshooting/README.md)
- [TypeScript Frontend README](frontend/README.md)
- [Feature Tracker](docs/ops/feature-tracker.md)
- [Registry Install E2E](docs/release/registry-user-install-e2e.md)

## Production Deployment

> **Database**: SQLite is the default for local development only. **PostgreSQL 16** is the recommended production database — pre-configured in `docker-compose.yml` (`HYBRISIGHT_WEB_DATABASE_URL=postgresql+psycopg2://...`). Set `HYBRISIGHT_WEB_DATABASE_URL` to a PostgreSQL connection string in any hosted environment.

> **API Reference**: The REST API is self-documented via FastAPI. When the server is running, visit `/docs` (Swagger UI) or `/redoc` (ReDoc) for interactive API reference.

**Current production:** Hetzner CX23 VPS (€4.67/mo — $5 budget tier) running Docker Compose behind Caddy (auto TLS via Let's Encrypt). See [`deployment-cheapest-2026.md`](docs/client/deployment-cheapest-2026.md) for the full strategy.

Bootstrap infrastructure via Terraform:
```bash
cd infra/vps/terraform
export TF_VAR_hcloud_token="<HETZNER_API_TOKEN>"
terraform init && terraform apply
```

This creates the server + SSH key + firewall. Then one-time setup:
```bash
scp infra/vps/docker-compose.yml infra/vps/Caddyfile root@<IP>:/opt/hybrisight/
ssh root@<IP> "bash /opt/hybrisight/setup.sh"
# Edit /opt/hybrisight/.env with secrets, then:
ssh root@<IP> "cd /opt/hybrisight && docker compose up -d"
```

CI/CD via GitLab CI (`.gitlab-ci.yml`): builds Docker image → pushes to `registry.agapii.org` → SSH deploys to VPS via `infra/vps/deploy.sh` with health check and rollback. Tag a release with `git tag v0.1.0 && git push origin v0.1.0` to trigger the full pipeline.

Deployment and operations docs:
- [Deployment Blueprint — Hetzner VPS](docs/client/deployment-cheapest-2026.md)
- [Database Migration Strategy](docs/ops/database-migration-strategy.md)
- [Compliance-Friendly Workflows](docs/product/compliance-friendly-workflows.md)
- [Security and Data Handling](docs/product/security-and-data-handling.md)
- [Dual Delivery Options](docs/product/dual-delivery-options.md)
- [Tool Contract](docs/reference/tool-contract.md)
- [Hybrid Architecture](docs/reference/hybrid-architecture.md)
- [GitLab Registry Release Workflow](docs/release/gitlab-registry-release-workflow.md)

Legacy deployment docs (superseded, kept for reference):
- [AWS App Runner (previous production path)](docs/ops/aws-apprunner-production-runbook.md)
- [Kubernetes Stage Helm (previous stage path)](docs/ops/kubernetes-stage-helm.md)

## CLI Installation

HybriSight CLI is distributed exclusively via the private GitLab Package Registry.

### For clients / end users (CLI only)

**Recommended — one-line bootstrap (macOS / Linux / WSL):** The script auto-installs `pipx` if missing, then installs the CLI in an isolated environment.

```bash
curl -sSL https://hybrisight.agapii.org/install.sh | bash
```

**Windows (PowerShell):**

```powershell
powershell -c "irm https://hybrisight.agapii.org/install.ps1 | iex"
```

The bootstrap scripts are published to the same GitLab Package Registry as the PyPI package (`hybrisight-cli-install` generic package, versioned per release).

**Manual install via pipx:**

```bash
# Install pipx first (if not already installed)
python3 -m pip install --user pipx
# Ensure ~/.local/bin is on your PATH
export PATH="$PATH:$HOME/.local/bin"

# Install HybriSight CLI
pipx install hybrisight \
  --index-url https://gitlab.agapii.org/api/v4/projects/28/packages/pypi/simple
```

**Fallback — direct pip install (no isolation):**

```bash
python3 -m pip install hybrisight \
  --index-url https://gitlab.agapii.org/api/v4/projects/28/packages/pypi/simple
```

All methods install only the scan CLI and cloud provider dependencies — no web server, no database, no PDF renderer.

### For operators (portal server)

```bash
python3 -m pip install "hybrisight[server]" \
  --index-url https://gitlab.agapii.org/api/v4/projects/28/packages/pypi/simple
```

The `[server]` extra adds: `fastapi`, `uvicorn`, `sqlalchemy`, `weasyprint`, and supporting libraries needed to run the `hybrisight-web` portal.

### GitLab CI/CD Variables

> **Where to set these**: GitLab project → **Settings → CI/CD → Variables** → *Add variable* (mark as *Masked* for secrets, *Protected* if only needed on protected branches).

The portal's CLI page and release pipeline are driven entirely by env vars. Set these in **GitLab Settings > CI/CD > Variables** for your deployed environment:

| Variable | Where used | Notes |
|---|---|---|
| `HYBRISIGHT_CLI_INSTALL_COMMAND` | Portal CLI page — install command shown to clients | e.g. `pipx install hybrisight --index-url <registry-url>` |
| `HYBRISIGHT_CLI_INSTALL_SCRIPT_URL` | Portal CLI page — bootstrap script link | URL to `scripts/install.sh` served from your domain |
| `HYBRISIGHT_CLI_REGISTRY_URL` | Portal CLI page — "View Registry" link | Set to your GitLab project packages URL |
| `HYBRISIGHT_CLI_LATEST_VERSION` | Portal CLI page — pins "Latest Release" display | Set this to skip any HTTP version fetch (required for private registry) |
| `HYBRISIGHT_CLI_VERSION` | Portal CLI page — overrides installed/supported version display | Optional; defaults to installed package version |
| `HYBRISIGHT_WEB_DATABASE_URL` | Web app — database connection | `postgresql+psycopg2://user:pass@host/db` for production |
| `HYBRISIGHT_WEB_SESSION_SECRET` | Web app — session signing | Generate with `openssl rand -hex 32`; mark as **Masked** |
| `HYBRISIGHT_SMTP_HOST` | Email notifications | SMTP server hostname; leave unset to disable notifications |
| `HYBRISIGHT_SMTP_PORT` | Email notifications | Default `587` |
| `HYBRISIGHT_SMTP_USER` | Email notifications | SMTP username; mark as **Masked** |
| `HYBRISIGHT_SMTP_PASSWORD` | Email notifications | SMTP password; mark as **Masked** |
| `HYBRISIGHT_SMTP_FROM` | Email notifications | Sender address |
| `HYBRISIGHT_OPS_EMAIL` | Internal ops notifications | Recipient for ops alerts |

For a GitLab-hosted deployment, the minimum set to configure is:

```bash
# GitLab Settings > CI/CD > Variables
HYBRISIGHT_CLI_INSTALL_COMMAND    = "pipx install hybrisight --index-url https://gitlab.agapii.org/api/v4/projects/28/packages/pypi/simple"
HYBRISIGHT_CLI_INSTALL_SCRIPT_URL = "https://hybrisight.agapii.org/install.sh"
HYBRISIGHT_CLI_REGISTRY_URL       = "https://gitlab.agapii.org/agapii/hybrisight/-/packages"
HYBRISIGHT_CLI_LATEST_VERSION  = "0.1.0"          # update on each release
HYBRISIGHT_WEB_DATABASE_URL    = "postgresql+psycopg2://hybrisight:...@postgres:5432/hybrisight_db"
HYBRISIGHT_WEB_SESSION_SECRET  = "<openssl rand -hex 32>"  # Masked
```

## CLI Commands

| Command | Purpose | Example |
|---|---|---|
| `hybrisight scan` | Collect read-only cloud metadata and evaluate rules | `hybrisight scan aws --profile prod --output hybrisight-findings.json` |
| `hybrisight signing init` | Generate tenant-scoped Ed25519 keypair for artifact signing | `hybrisight signing init` |
| `hybrisight signing register` | Register tenant public key for portal signature verification | `hybrisight signing register --portal https://hybrisight.agapii.org --api-key <API_KEY> --public-key-file ~/.config/hybrisight/signing_ed25519_public.pem` |
| `hybrisight doctor` | Validate identity and required read-only API permissions before scan | `hybrisight doctor aws --profile hybrisight --regions all` |
| `hybrisight report --format html` | Generate executive HTML report | `hybrisight report --input hybrisight-findings.json --format html --output hybrisight-report.html` |
| `hybrisight report --format pdf` | Generate PDF report in one command | `hybrisight report --input hybrisight-findings.json --format pdf --output hybrisight-report.pdf` |
| `hybrisight report --format pdf-json` | Generate PDF-ready structured JSON | `hybrisight report --input hybrisight-findings.json --format pdf-json --output hybrisight-pdf-summary.json` |
| `hybrisight upload` | Upload artifact to portal using short-lived token exchange | `hybrisight upload --artifact hybrisight-findings.json --portal http://localhost:8080 --api-key <API_KEY>` |
| `hybrisight auth login` | Bootstrap and store portal defaults for CLI upload | `hybrisight auth login --portal http://localhost:8080 --email shawnthompson66@gmail.com` |

CLI scan output now prints a polished operator summary with `Scan Summary`, `Signal Coverage`, `Impact`, `Operator Notes`, and `Next Step` blocks. Collection-quality metadata is also persisted into `collection_diagnostics.summary` so the portal/report can later distinguish canonical CLI runs with full vs partial signal completeness.
CLI artifacts can now also embed explicit reassessment continuity metadata, and the Upload page can override engagement/scope/series metadata at ingestion time when a consultancy operator needs to pin an upload to a specific reassessment track.
`hybrisight upload` now supports the same continuity override fields:

```bash
hybrisight upload \
  --artifact hybrisight-findings.json \
  --portal https://hybrisight.agapii.org \
  --engagement-label "Q2 2026 Pilot Landing Zone" \
  --engagement-key "pilot-landing-zone-q2-2026" \
  --scope-label "AWS Production Platform Accounts" \
  --scope-fingerprint "aws:accounts=111111111111,222222222222:env=prod" \
  --series-label "Monthly Landing Zone Remediation" \
  --series-key "pilot-landing-zone-monthly" \
  --series-cadence monthly
```

For Workflow C / ephemeral runner uploads, those override fields are now included in the signed scan-job command payload and must match at upload time.
CLI scan output now also prints:
- `Recommended Output: Foundation Review`
- or `Recommended Output: Governance Brief`
- `Use Case: Must-do controls and readiness`
- or `Use Case: Board-grade governance and stabilisation planning`

This recommendation is persisted into `collection_diagnostics.recommended_output` using the same HybriSight-native classification logic as the portal.

Portal/report now also classify uploads into:
- `Foundation Review`
- `Governance Brief`

based on signal density and environment maturity. This avoids presenting thin environments as full board-grade governance outputs.

Assessment Library and Internal Ops `Ingestion` now surface the same classification so operators can distinguish:
- input path quality
- collection signal quality
- assessment type (`Foundation Review` vs `Governance Brief`)

Tenant signing-key verification workflow:
- API keys are only used to authenticate CLI registration calls.
- Public signing keys can now be registered either:
  - via CLI: `hybrisight signing register ...`
  - or manually in the portal under `Account & Settings -> Tenant Administration -> Tenant Signing Keys`
- Uploads show `Unverified` when the artifact signature `key_id` does not match an active public key registered for that tenant.
- Dashboard/report integrity warnings now surface the uploaded artifact `key_id` so mismatches can be diagnosed directly in the UI.
The authenticated dashboard summary and governance brief snapshot now surface that same collection-quality classification after upload.

Execution Board remediation guides resolve in this order: exact provider/finding playbook, provider-safe provider/theme fallback, then generated fallback guidance. Workspace/bootstrap responses expose guide coverage so Internal Ops can see when curated playbooks are still missing.

Execution Board workload is intentionally grouped. HybriSight distinguishes:
- raw observations: every repeated failing control observation in the uploaded artifact
- unique rule triggers: distinct failing rules/themes after roadmap grouping
- execution tickets: grouped remediation work items seeded into the board

This means a report can show thousands of raw observations while the board shows a much smaller number of tickets. Closing a ticket is not proof by itself that all underlying observations are gone; remediation must be applied across the affected scope and then validated on re-run.

## Rule Pack (MVP)

- 57 rules in `rules/` (AWS, Azure, multi-cloud) covering security, resilience, cost, and operations
- Provider-specific and multi-cloud checks
- Rule IDs examples: `AWS.IAM.001`, `AZURE.NETWORK.001`, `MULTI.COST.001`

## Tests

Backend (Python):

```bash
.venv/bin/pytest -q
```

Frontend checks:

```bash
npm run -C frontend lint
npm run -C frontend test:unit
npm run -C frontend test:routes
npm run -C frontend build
```

Frontend browser tests:

```bash
# Mocked UI/browser suite (default)
npm run -C frontend test:e2e

# Real-endpoint integration lane
HYBRISIGHT_E2E_BASE_URL=http://127.0.0.1:8080 npm run -C frontend test:e2e:integration
```

Current real-endpoint browser coverage includes:
- login/session bootstrap
- logout/session clear
- tenant-user admin create/delete
- internal-ops/root-tenant engagement create/state-update flow
- non-ops tenant users denied from engagement management
- seeded non-root tenant admin denied from Internal Ops and Delivery Pipeline
- API key lifecycle smoke
- artifact upload smoke
- board hold/reactivate/bootstrap flow
- active-dashboard delete flow
- internal ops shell access for the root/internal-ops admin

CLI coverage is separate and test-driven via `pytest`, primarily in:
- `tests/test_cli.py`
- `tests/test_engine.py`
- `tests/test_collectors.py`
- `tests/test_rules.py`
- `tests/test_signing.py`

Commercial workflow coverage:
- internal-ops/root-tenant `Delivery Pipeline` UI on top of the `engagements` API for baseline/stabilisation/retainer tracking
- optional Stripe invoice-link creation when `HYBRISIGHT_STRIPE_SECRET_KEY` is configured
- report release workflow with release state, delivery format/url, and release timestamp
- external billing mirror fields for Stripe/Xero/QuickBooks metadata
- timeline events for create/state/billing changes

Frontend (TypeScript + Playwright):

```bash
npm run -C frontend lint
npm run -C frontend test:run
npm run -C frontend build
npm run -C frontend test:e2e
```

One-command local verification (recommended before push):

```bash
./scripts/verify-local.sh
# Full run including e2e:
RUN_E2E=1 ./scripts/verify-local.sh
```

Optional local hook enforcement:

```bash
git config core.hooksPath .githooks
```

CSS budget guardrail:

```bash
./ci/check_css_budgets.sh
```

Line budget guardrails (default max 300 lines; legacy oversize files are frozen until split, including Python tests):

```bash
bash ci/check_python_budgets.sh
npm run -C frontend lint:budgets
```

Python dead-code guardrail:

```bash
./ci/check_python_lint.sh
```

CONTEXT history sync guardrail (CI):

```bash
./ci/check_context_sync.sh
```

## Delivery Playbook

- [Playbook Index](docs/playbook/README.md)
- [Consultancy Runbook Index](docs/playbook/consultancy/README.md)
- [Methodology Operating Model](docs/playbook/methodology-operating-model.md)
- [Software Operating Runbook](docs/playbook/software-operating-runbook.md)
- [Consultancy Delivery Runbook](docs/playbook/consultancy-delivery-runbook.md)
- [Perfect Delivery Checklist](docs/playbook/perfect-delivery-checklist.md)
- [Methodology Blueprint](docs/blueprints/methodology-blueprint.md)
- [Software Blueprint](docs/blueprints/software-blueprint.md)
- [Consultancy Blueprint](docs/blueprints/consultancy-blueprint.md)
- [Client Engagement Template](docs/blueprints/client-engagement-template.md)

### Automation Scripts

- `scripts/playbook/init_client_workspace.sh`
- `scripts/playbook/generate_quarterly_review_pack.sh`


### Internal Ops Control Plane (SaaS Internal)

- [Internal Ops Quick Start](docs/ops/internal-ops-quick-start.md)
Hidden internal route for platform operations and lockstep monitoring:
- Route: `/internal/ops`
- Access gates:
  - `HYBRISIGHT_INTERNAL_OPS_EMAILS` allowlist
  - Optional `HYBRISIGHT_INTERNAL_OPS_KEY` (header enforcement controlled by `HYBRISIGHT_INTERNAL_OPS_KEY_ENFORCED`)
- Lifecycle controls:
  - suspend/reactivate tenant access with reason, timestamp, and audit/compliance events
- Log controls:
  - filter and review platform audit logs by event/path/tenant/user
  - purge old audit logs with explicit `PURGE` confirmation (compliance logs excluded)
- Upload quality controls:
  - `Ingestion` tab rolls up canonical CLI vs approved fallback vs identity-only imports across recent uploads
  - `Ingestion` tab now supports provider/time-window filtering for faster review during pilot/prod operations
  - latest upload rows now surface collection-signal quality (`Full`, `Partial`, `Limited`, `Demo`) alongside the ingestion path
- Delivery Pipeline now warns when the latest client baseline matched by tenant name is fallback input or less than full signal completeness.
  - Delivery Pipeline records now support explicit client-tenant linkage so latest-baseline warnings do not rely on name matching for new records.
  - Existing Delivery Pipeline records can now be retro-linked to a client tenant from `Billing Mirror`, so older records do not need to be recreated to gain deterministic baseline warnings.

Lockstep contract:
- `governance/lockstep.json`
- Validate with:
```bash
python scripts/playbook/validate_lockstep.py
```

Pilot/prod ruleset baseline:
- `governance/ruleset-pilot-prod-v1.json`
- Validate with:
```bash
python3 scripts/playbook/validate_ruleset_baseline.py
```

## Documentation

- [`docs/README.md`](docs/README.md)
- [`docs/reference/architecture.md`](docs/reference/architecture.md)
- [`docs/reference/rename-to-hybrisight.md`](docs/reference/rename-to-hybrisight.md)
- [`docs/product/compliance-friendly-workflows.md`](docs/product/compliance-friendly-workflows.md)
- [`docs/product/dual-delivery-options.md`](docs/product/dual-delivery-options.md)
- [`docs/reference/scoring-model.md`](docs/reference/scoring-model.md)
- [`docs/product/security-and-data-handling.md`](docs/product/security-and-data-handling.md)
- [`docs/reference/tool-contract.md`](docs/reference/tool-contract.md)
- [`docs/ops/getting-started.md`](docs/ops/getting-started.md)
- [`docs/reference/hybrid-architecture.md`](docs/reference/hybrid-architecture.md)
- [`docs/blueprints/deployment.md`](docs/blueprints/deployment.md)
- [`docs/reference/read-only-permissions.md`](docs/reference/read-only-permissions.md)
- [`docs/access-setup/aws-readonly-access.md`](docs/access-setup/aws-readonly-access.md)
- [`docs/access-setup/azure-readonly-access.md`](docs/access-setup/azure-readonly-access.md)
- [`docs/access-setup/credential-handling.md`](docs/access-setup/credential-handling.md)
- [`docs/product/tenant-signing-keys.md`](docs/product/tenant-signing-keys.md)
- [`docs/release/pypi-release-workflow.md`](docs/release/pypi-release-workflow.md)
- [`docs/release/pypi-user-install-e2e.md`](docs/release/pypi-user-install-e2e.md)
- [`docs/ops/testing.md`](docs/ops/testing.md)
- [`docs/ops/feature-tracker.md`](docs/ops/feature-tracker.md)
- [`docs/product/concerns-and-answers.md`](docs/product/concerns-and-answers.md)
- [`docs/product/ai-insights-and-trend-analysis.md`](docs/product/ai-insights-and-trend-analysis.md)
- [`docs/ops/frontend-modernization-plan.md`](docs/ops/frontend-modernization-plan.md)
- [`docs/ops/ui-retention-and-purge.md`](docs/ops/ui-retention-and-purge.md)
- [`CONTRIBUTING.md`](CONTRIBUTING.md)


## TypeScript Frontend (Primary Portal UI)

The React + TypeScript app in `frontend/` is now the primary authenticated portal UI.

Cutover status:
- `/app` serves SPA shell + Vite-built assets from `frontend/dist`
- authenticated convenience routes redirect into SPA state:
  - `/upload` -> `/app?page=upload`
  - `/admin/api-keys` -> `/app?page=api-keys`
  - `/dashboard/<id>` -> `/app?page=dashboard&upload=<id>`
  - `/dashboard/<id>/finding/<idx>` -> `/app?page=finding&upload=<id>&finding=<idx>`
- report route is SPA-native (`/app?page=report&upload=<id>`)
- `/dashboard/<id>/export/html` and `/dashboard/<id>/export/pdf` redirect into the SPA report route with export actions
- `/dashboard/<id>/export/pdf-json` remains backend-native JSON download
- public/auth/legal/help routes use the standalone public entrypoint, not `/app?page=...`

Run locally:

```bash
cd frontend
npm install
npm run dev
```

Quality checks:

```bash
cd frontend
npm run lint
npm run test:unit
npm run test:routes
npm run test:run
npm run build
npm run test:e2e
```

## License

Apache License 2.0. See [`LICENSE`](LICENSE).

## Safety Guardrail

HybriSight is advisory only. Keep credentials read-only and run in approved environments.

## Tenant Onboarding and Isolation

Each client account operates inside a single tenant boundary.

How onboarding works:
1. Internal ops provisions a tenant and first tenant admin at `/internal/ops` using `Create Tenant + Admin`.
2. Tenant admin signs in and manages uploads, API keys, report access, and tenant users for that tenant only.
3. Client users (`tenant_admin`, `analyst`, `viewer`) are tenant-bound and cannot access other tenants' data.
4. Assessment numbering in UI is tenant-local (`Assessment #`); immutable global `upload_id` remains internal for audit/support traceability.

Tenant user management (tenant admin only):
- `GET /api/v1/account/users` (list users in current tenant)
- `POST /api/v1/account/users` (create user in current tenant)
- `POST /api/v1/account/users/{id}/role` (change role in current tenant)
- `POST /api/v1/account/users/{id}/delete` (delete user in current tenant)
- Guardrails:
  - cross-tenant target user access returns `404`
  - cannot use admin endpoint to modify/delete self
  - cannot delete or demote the last `tenant_admin`

Isolation guarantees:
- Upload reads/writes are filtered by `tenant_id`.
- API keys are scoped to the owning tenant.
- Artifact files are stored under tenant-scoped paths.
- Dashboard, report views, and deletion actions enforce tenant checks.

## Signup & Self-Serve Flow

Portal access is self-serve — no ops approval needed.

1. User downloads the free CLI via `curl -sSL https://hybrisight.agapii.org/install.sh | bash` — no account needed, runs locally.
2. CLI scan produces a signed HTML report with risk scoring, cost analysis, and findings.
3. To upload for portal access (report, compliance mapping, share links, trend tracking), users sign up at `/signup`.
4. `/signup` immediately creates Tenant + User + Engagement (baseline) + API key and logs the user in.
5. Unpaid baseline engagements block report release until payment is received (Stripe invoice). Share tokens bypass payment.

Pricing bands (dual-track):
- Free CLI: £0 (self-serve, no account required)
- Single Report: £350 (one-time portal upload)
- Pro Monthly: £300/mo (unlimited portal uploads)
- Business Monthly: £600/mo (unlimited + priority support)
- Baseline Assessment: from GBP 3,500 (assisted delivery, contact)
- 30-Day Stabilisation: from GBP 8,500 (assisted delivery, contact)
- Ongoing Governance: from GBP 2,500/quarter (assisted delivery, contact)

The public journey separates self-serve from assisted delivery:
- `/cli` is the entry point — free CLI install and quick start.
- `/signup` creates a portal account immediately — no wait.
- `/request-baseline` is for Single Report upload or Baseline Assessment (contact).
- `/contact-feedback` is for Stabilisation, Governance, procurement, support, and feedback.

Each signup request records:
- accepted terms version
- acceptance timestamp
- acceptance IP address

Internal Ops surfaces that acceptance evidence alongside the onboarding request so qualification/approval decisions are auditable.

### Internal Ops Visibility

Internal Ops is intentionally hidden unless internal controls are configured.
The navbar link appears only for allowlisted internal tenant admins in the root/default tenant:
- `HYBRISIGHT_INTERNAL_OPS_EMAILS` includes logged-in email
- logged-in user tenant slug matches:
  - `HYBRISIGHT_INTERNAL_OPS_ROOT_TENANT_SLUG` (if set), else
  - `HYBRISIGHT_WEB_DEFAULT_TENANT_SLUG` (default: `default`)
- local/dev fallback: if insecure bootstrap defaults are enabled and no allowlist is set, bootstrap admin is auto-allowlisted
- optional `HYBRISIGHT_INTERNAL_OPS_KEY` can be enforced on route access when `HYBRISIGHT_INTERNAL_OPS_KEY_ENFORCED=true`

### Onboarding Notifications (Optional)

Self-signup onboarding can send notifications via SMTP:
- request notifications to ops (`HYBRISIGHT_ONBOARDING_NOTIFY_EMAILS` or internal ops allowlist)
- approval/rejection notifications to the requesting client admin email

Optional terms publication setting:
- `HYBRISIGHT_TERMS_VERSION` (defaults to `2026-03-22`)
  - controls the published signup acceptance version stored on each onboarding request

Baseline request intake uses the same SMTP path:
- request notifications to ops (`HYBRISIGHT_ONBOARDING_NOTIFY_EMAILS` or internal ops allowlist)
- submission receipt notification to the requesting baseline contact email
- qualification/rejection notifications to the requesting baseline contact email
- if email delivery fails, the request is still stored but the baseline intake page now shows an inline warning and Internal Ops records the delivery status/error

Contact & Feedback now uses a professional public form and the same SMTP foundation:
- ops routing notifications use `HYBRISIGHT_CONTACT_NOTIFY_EMAILS` when set
- if `HYBRISIGHT_CONTACT_NOTIFY_EMAILS` is unset, the form falls back to `HYBRISIGHT_ONBOARDING_NOTIFY_EMAILS` and then `HYBRISIGHT_INTERNAL_OPS_EMAILS`
- requester receipt notifications are sent back to the submitted work email
- the form only shows success when the ops delivery completed; requester receipt failure is surfaced as a warning
- every contact submission is also persisted in HybriSight and surfaced in Internal Ops as a review queue, so support/commercial messages are not dependent on mailbox state alone

Internal Ops baseline triage now also surfaces the submitter name, role/title, free-text context, and the recorded ops/requester notification delivery status so qualification decisions are not made from a bare email row.

Client Journey orchestration is now available inside Internal Ops `Client Journeys`:
- canonical journey/stage/step schema
- persisted `ClientJourney` records and related stage/step/event rows
- Internal Ops APIs:
  - `GET /api/v1/internal/journeys`
  - `GET /api/v1/internal/journeys/{journey_id}`
  - `POST /api/v1/internal/journeys`
  - `POST /api/v1/internal/journeys/{journey_id}`
  - `POST /api/v1/internal/journeys/{journey_id}/steps/{step_id}`
- Internal Ops operators can now:
  - create a journey from baseline, onboarding, contact, or manual intake
  - switch delivery mode between `consultant_led` and `client_controlled_read_only`
  - update journey notes/blockers
  - complete or block individual scripted steps without leaving Operations

For local setup, copy [`.env.example`](/Users/shawn/Documents/hybrisight/.env.example) to `.env` and fill in real values locally.
`hybrisight-web` now auto-loads a local `.env` file from the current working directory or parent project directory when those variables are not already exported in the shell.
