Metadata-Version: 2.4
Name: hybrisight
Version: 0.1.0
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: fastapi>=0.115.0
Requires-Dist: uvicorn>=0.30.6
Requires-Dist: sqlalchemy>=2.0.36
Requires-Dist: python-multipart>=0.0.12
Requires-Dist: itsdangerous>=2.2.0
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: weasyprint>=62.3
Requires-Dist: segno>=1.6.1
Requires-Dist: rich>=13.7.0
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"
Provides-Extra: prod
Requires-Dist: psycopg2-binary>=2.9.9; extra == "prod"
Dynamic: license-file

# HybriSight

HybriSight is a non-intrusive, read-only cloud audit engine for UK SMEs.
It is designed for the fixed-fee consulting offer: **Cloud & Infrastructure Efficiency Snapshot**.

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: `admin@hybrisight.local`
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)
- [PyPI User Install E2E](docs/release/pypi-user-install-e2e.md)
- [GitLab -> GitHub -> PyPI Bridge](docs/release/gitlab-github-mirror-pypi-bridge.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.

Production and hosted operations docs:
- [AWS App Runner Production Runbook](docs/ops/aws-apprunner-production-runbook.md)
- [Database Migration Strategy](docs/ops/database-migration-strategy.md)
- [Kubernetes Stage Helm Runbook](docs/ops/kubernetes-stage-helm.md) (default host `hybrisight-stage.agapii.org`, image `registry.agapii.org/agapii/hybrisight/hybrisight-stage:stage-fix`, pull policy `Always`, uses `hybrisight-stage-pull-secret`)
- [Deployment Blueprint](docs/blueprints/deployment.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)
- [PyPI Release Workflow](docs/release/pypi-release-workflow.md)
- [GitLab -> GitHub -> PyPI Bridge](docs/release/gitlab-github-mirror-pypi-bridge.md)

## CLI Installation

HybriSight CLI is distributed via the private GitLab Package Registry. Install using:

```bash
pip install hybrisight \
  --extra-index-url https://gitlab.agapii.org/api/v4/projects/<PROJECT_ID>/packages/pypi/simple
```

### 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 | Set to the full `pip install ... --extra-index-url ...` command |
| `HYBRISIGHT_CLI_PYPI_URL` | Portal CLI page — "View on registry" link | Set to your GitLab project packages URL |
| `HYBRISIGHT_CLI_PYPI_JSON_URL` | Portal CLI page — fetches latest version | Omit if using `HYBRISIGHT_CLI_LATEST_VERSION` instead |
| `HYBRISIGHT_CLI_LATEST_VERSION` | Portal CLI page — pins "Latest Release" display | Set this to skip the HTTP version fetch (recommended 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 = "pip install hybrisight --extra-index-url https://gitlab.agapii.org/api/v4/projects/<PROJECT_ID>/packages/pypi/simple"
HYBRISIGHT_CLI_PYPI_URL        = "https://gitlab.agapii.org/<group>/<project>/-/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 admin@hybrisight.local` |

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)

- 26 high-impact rules in `rules/`
- 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.

## Baseline Request Intake (Approval Queue)

Public access is consultancy-led:

1. Prospect submits `/request-baseline` (company, contact email, providers, spend band, objective).
2. Request is stored as `pending` in the baseline queue.
3. Internal ops reviews queue at `/internal/ops` and qualifies/rejects.
4. Tenant creation remains engagement-gated (no automatic tenant creation from public request).

This keeps access controlled while preserving an engineer-friendly intake path.

Controlled onboarding via `/signup` remains available for explicit onboarding workflows.
Signup now requires the requester to explicitly accept the current Terms of Service and Privacy Policy before the onboarding request is stored.
Each onboarding 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.
