diff --git a/AINative_OKR_CASAN5/.dockerignore b/AINative_OKR_CASAN5/.dockerignore new file mode 100644 index 0000000..ccbb0de --- /dev/null +++ b/AINative_OKR_CASAN5/.dockerignore @@ -0,0 +1,14 @@ +**/node_modules/ +**/dist/ +*.db +*.sqlite +.git/ +.gitea/ +.specify/logs/ +docs/output/ +*.pem +.env +.env.* +backend/test/ +**/__pycache__/ +*.pyc diff --git a/AINative_OKR_CASAN5/.gitea/act-runner-config.yaml b/AINative_OKR_CASAN5/.gitea/act-runner-config.yaml new file mode 100644 index 0000000..9fb05b2 --- /dev/null +++ b/AINative_OKR_CASAN5/.gitea/act-runner-config.yaml @@ -0,0 +1,36 @@ +# act_runner configuration for CASAN CI on Oracle Cloud VPS (1 GB RAM) +# Deploy to: /opt/gitea/act-runner-config.yaml on the VPS. + +log: + level: info + +runner: + # Unique name shown in Gitea → Site Administration → Runners + name: "casan-runner-oracle" + + # Only 1 concurrent job — VPS has 1 GB RAM (Gitea ≈ 200 MB + job container ≈ 300 MB) + capacity: 1 + + # Map "ubuntu-latest" jobs to the catthehacker image that has git, curl, jq, + # openssl, Node.js 18, and Python3 pre-installed. The workflow steps upgrade + # Node.js to 20 via actions/setup-node and add python-is-python3. + labels: + - "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04" + + # How often the runner polls Gitea for new jobs + fetch_interval: 5s + fetch_timeout: 30s + + # Where to store the runner's working directory (inside the container) + # Default is /workspace — works fine. + +container: + network: bridge + + # --memory 512m: prevent OOM on 1 GB VPS (build runs in HOST daemon, not job container) + # -v socket: Docker-outside-of-Docker for the deploy job; lets docker CLI in the job + # container talk to the HOST daemon to build + run OKR images. + options: "--memory 512m --cpus 1.5 -v /var/run/docker.sock:/var/run/docker.sock" + + valid_volumes: + - "**" diff --git a/AINative_OKR_CASAN5/.gitea/okr-deploy-runbook.md b/AINative_OKR_CASAN5/.gitea/okr-deploy-runbook.md new file mode 100644 index 0000000..3389918 --- /dev/null +++ b/AINative_OKR_CASAN5/.gitea/okr-deploy-runbook.md @@ -0,0 +1,158 @@ +# OKR Web App — Deploy Runbook +# Oracle Cloud VPS: 161.33.139.73 + +## What this deploys + +| Container | Image | Port | Notes | +|---------------|--------------------|--------------|--------------------------------| +| okr-frontend | nginx:alpine | **80** (public) | React SPA, proxies /api/v1/* → backend | +| okr-backend | node:22-slim | 3001 (internal) | NestJS + Prisma + SQLite | + +Network: `okr-net` (bridge, separate from Gitea/Vault) +Volume: `okr-db` → mounted at `/data/okr.db` inside backend + +--- + +## Step 1 — Add JWT_SECRET to Gitea Secrets + +1. Open http://161.33.139.73:3000 → your repo → **Settings → Secrets → Actions** +2. Add: + - Name: `JWT_SECRET` + - Value: `$(openssl rand -hex 32)` ← run this locally to generate +3. Save. + +--- + +## Step 2 — Update act_runner config (Docker socket passthrough) + +The deploy job needs Docker CLI inside the job container to build and run images. +The updated `act-runner-config.yaml` already includes: + +```yaml +container: + options: "--memory 512m --cpus 1.5 -v /var/run/docker.sock:/var/run/docker.sock" +``` + +Apply the new config on the VPS: + +```bash +# Copy updated config +scp .gitea/act-runner-config.yaml ubuntu@161.33.139.73:/opt/gitea/act-runner-config.yaml + +# Restart act_runner to pick up the new config +ssh ubuntu@161.33.139.73 "cd /opt/gitea && docker compose restart act-runner" +``` + +> The `-v /var/run/docker.sock:...` flag passes the HOST Docker socket into each +> job container, enabling Docker-outside-of-Docker (DooD). The socket is already +> in `valid_volumes: ["**"]` so no extra ACL change is needed. + +--- + +## Step 3 — Push to main to trigger CI + deploy + +```bash +git add Dockerfile.backend Dockerfile.frontend nginx/ backend/entrypoint.sh \ + .dockerignore .gitea/ +git commit -m "feat: deploy OKR app to port 80/3001 via CI" +git push gitea main +``` + +Watch the pipeline: http://161.33.139.73:3000//casan5/actions + +Expected jobs: +1. **Frontend Tests** — 16 Vitest tests (~1 min) +2. **CASAN Security Gate** — harness + Vault signing (~3 min) +3. **Deploy OKR** — builds images + deploys (~5 min first time, ~2 min cached) + +After deploy: +- **App**: http://161.33.139.73 (login: `admin@okr.local` / `Password@123`) +- **Gitea**: http://161.33.139.73:3000 (unchanged) + +--- + +## Manual deploy (without CI) + +If you need to deploy manually from the VPS: + +```bash +ssh ubuntu@161.33.139.73 + +# Clone or pull repo +cd /opt/okr-app # or wherever you checked out the repo + +# Build images +docker build -t okr-backend:latest -f Dockerfile.backend . +docker build \ + --build-arg VITE_API_BASE_URL=/api/v1 \ + -t okr-frontend:latest \ + -f Dockerfile.frontend . + +# Network + volume +docker network create okr-net 2>/dev/null || true +docker volume create okr-db 2>/dev/null || true + +# Backend +docker rm -f okr-backend 2>/dev/null || true +docker run -d \ + --name okr-backend \ + --network okr-net \ + -e PORT=3001 \ + -e DATABASE_URL=file:/data/okr.db \ + -e JWT_SECRET="$(openssl rand -hex 32)" \ + -e FRONTEND_ORIGIN="http://161.33.139.73" \ + -e NODE_ENV=production \ + -v okr-db:/data \ + --restart unless-stopped \ + okr-backend:latest + +# Frontend (nginx) +docker rm -f okr-frontend 2>/dev/null || true +docker run -d \ + --name okr-frontend \ + --network okr-net \ + -p 80:80 \ + --restart unless-stopped \ + okr-frontend:latest + +# Check +sleep 15 +curl -sf http://localhost/ | grep -c "html" && echo "FRONTEND_UP" +docker logs okr-backend --tail 10 +``` + +--- + +## RAM budget (1 GB VPS) + +| Service | Idle RAM | +|----------------|-----------| +| Gitea | ~180 MB | +| act_runner | ~50 MB | +| Vault | ~60 MB | +| okr-backend | ~120 MB | +| okr-frontend | ~25 MB | +| **Total idle** | **~435 MB** ✅ | +| During CI build (Docker build on HOST) | +400 MB peak → ~835 MB ✅ | + +--- + +## Troubleshooting + +```bash +# Container status +docker ps -a | grep okr + +# Backend logs (shows DB init + startup) +docker logs okr-backend --tail 50 + +# Frontend logs +docker logs okr-frontend --tail 20 + +# Re-seed database (drops + recreates) +docker exec okr-backend sh -c "rm /data/okr.db && kill 1" +# container restarts → auto re-initialises + +# Check nginx proxy config +docker exec okr-frontend nginx -t +``` diff --git a/AINative_OKR_CASAN5/.gitea/vault-setup-runbook.md b/AINative_OKR_CASAN5/.gitea/vault-setup-runbook.md new file mode 100644 index 0000000..5f60e73 --- /dev/null +++ b/AINative_OKR_CASAN5/.gitea/vault-setup-runbook.md @@ -0,0 +1,225 @@ +# HashiCorp Vault Setup — CASAN H5 KMS +# Oracle Cloud VPS: 161.33.139.73 + +## What this achieves + +Before: private key = file on disk, `verify-audit-chain` → `anchor=unsigned` in CI +After: private key = Vault Transit (never leaves Vault), CI → `anchor=signed` + +--- + +## Step 1 — Add Vault to /opt/gitea/docker-compose.yml + +Add the `vault` service and a named volume. The key sections to add: + +```yaml +# ── Add to the top-level volumes section ───────────────────────── +volumes: + gitea-data: + act-runner-data: + vault-data: # ← ADD THIS + +# ── Add the vault service ───────────────────────────────────────── + vault: + image: hashicorp/vault:latest + container_name: vault + restart: unless-stopped + networks: + - gitea + ports: + - "8200:8200" # expose so you can init from outside + environment: + # Dev mode: auto-init, auto-unseal, in-memory storage. + # Root token is fixed — store it as a Gitea secret after setup. + VAULT_DEV_ROOT_TOKEN_ID: "${VAULT_ROOT_TOKEN}" + VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200" + VAULT_LOG_LEVEL: "warn" + cap_add: + - IPC_LOCK # required by Vault + command: server -dev +``` + +> Dev mode stores keys in RAM — keys survive container restarts because the token +> and key name are fixed, and Vault re-creates them on each start. +> For production: switch to `server` mode with `file` storage backend. + +--- + +## Step 2 — Add VAULT_ROOT_TOKEN to /opt/gitea/.env + +```bash +# Pick a strong token (or generate one) +echo "VAULT_ROOT_TOKEN=casan-vault-$(openssl rand -hex 16)" >> /opt/gitea/.env + +# Verify +grep VAULT_ROOT_TOKEN /opt/gitea/.env +``` + +Save this token — you'll need it for the Gitea secret in Step 5. + +--- + +## Step 3 — Start Vault + +```bash +cd /opt/gitea +docker compose pull vault +docker compose up -d vault + +# Wait ~5 seconds for Vault to boot, then check: +docker compose logs vault | tail -20 +# Look for: "Development mode should NOT be used in production installations!" +# and: "Root Token: " +``` + +Verify from outside: +```bash +curl http://161.33.139.73:8200/v1/sys/health +# Expected: {"initialized":true,"sealed":false,...} +``` + +--- + +## Step 4 — Enable Transit engine + create signing keys + +Run these from anywhere that can reach port 8200. +Replace `` with your `VAULT_ROOT_TOKEN` value. + +```bash +export VAULT_ADDR=http://161.33.139.73:8200 +export VAULT_TOKEN= + +# Enable Transit secrets engine +curl -sf -X POST \ + -H "X-Vault-Token: $VAULT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"type":"transit"}' \ + "$VAULT_ADDR/v1/sys/mounts/transit" + +# Create RSA-2048 key for policy bundle signing +curl -sf -X POST \ + -H "X-Vault-Token: $VAULT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"type":"rsa-2048","exportable":false}' \ + "$VAULT_ADDR/v1/transit/keys/casan-policy-key" + +# Create RSA-2048 key for audit chain signing +curl -sf -X POST \ + -H "X-Vault-Token: $VAULT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"type":"rsa-2048","exportable":false}' \ + "$VAULT_ADDR/v1/transit/keys/casan-audit-key" + +echo "Vault Transit keys created." +``` + +Verify keys exist: +```bash +curl -sf -H "X-Vault-Token: $VAULT_TOKEN" \ + "$VAULT_ADDR/v1/transit/keys/casan-policy-key" | python3 -c " +import sys,json; d=json.load(sys.stdin) +print('key type:', d['data']['type']) +print('exportable:', d['data']['exportable']) +" +# Expected: key type: rsa-2048 / exportable: False +``` + +--- + +## Step 5 — Add VAULT_TOKEN as Gitea Actions Secret + +1. Open Gitea: http://161.33.139.73:3000 +2. Go to your repo → Settings → Secrets → Actions +3. Add new secret: + - Name: `VAULT_TOKEN` + - Value: your `VAULT_ROOT_TOKEN` value +4. Save. + +The CI workflow references `${{ secrets.VAULT_TOKEN }}` — this injects the token +into the `security-gate` job without exposing it in logs. + +--- + +## Step 6 — Test Vault signing locally (optional) + +From macOS with the project checked out: + +```bash +export VAULT_ADDR=http://161.33.139.73:8200 +export VAULT_TOKEN= + +cd Output_CASAN5_REFINED/AINative_OKR_CASAN5 + +# Test sign-policy-bundle with Vault +bash .specify/scripts/bash/sign-policy-bundle.sh sign +# Expected: POLICY_BUNDLE_SIGNED ... key_backend=vault-kms + +# Test sign-audit-head +bash .specify/scripts/bash/sign-audit-head.sh +# Expected: SIGN_AUDIT_HEAD_OK ... anchor=vault-kms + +# Verify audit chain +bash .specify/scripts/bash/verify-audit-chain.sh +# Expected: AUDIT_CHAIN_VALID anchor=signed +``` + +--- + +## Step 7 — Push code to trigger CI with Vault + +```bash +git add .gitea/ .specify/scripts/bash/vault-kms.sh \ + .specify/scripts/bash/sign-audit-head.sh \ + .specify/scripts/bash/sign-policy-bundle.sh +git commit -m "feat(H5): Vault KMS for policy + audit chain signing" +git push gitea main +``` + +Watch the CI run: http://161.33.139.73:3000//casan5/actions + +Expected security-gate job output: +``` +VAULT_KMS_READY +... +SIGN_AUDIT_HEAD_OK head= anchor=vault-kms +POLICY_BUNDLE_SIGNED ... key_backend=vault-kms +AUDIT_CHAIN_VALID anchor=signed ← changed from "unsigned" +... +== verdict: PASS=7 FAIL=0 SKIP=1 == +``` + +--- + +## Architecture summary + +``` +CI Job (act_runner container) +│ +├── vault-kms.sh sign ──── POST /v1/transit/sign/casan-audit-key ──► Vault container +│ (key never leaves) +├── vault-kms.sh pubkey ──── GET /v1/transit/keys/casan-audit-key ──► export public key +│ as PEM for openssl verify +└── verify-audit-chain.sh ── openssl dgst -verify audit-public.pem + → AUDIT_CHAIN_VALID anchor=signed ✅ +``` + +Private key: +- Stored only inside Vault's in-memory transit engine +- Never written to disk as `.pem` +- Never visible in CI logs +- Access gated by `VAULT_TOKEN` Gitea secret + +--- + +## RAM footprint on VPS (1 GB) + +| Service | Idle RAM | +|---|---| +| Gitea | ~180 MB | +| act_runner | ~50 MB | +| **Vault** | **~60 MB** | +| Total idle | ~290 MB | +| During CI | +300 MB (job container) | +| **Peak** | **~590 MB** ✅ | + +Vault in dev mode uses ~60 MB — well within the 1 GB budget. diff --git a/AINative_OKR_CASAN5/.gitea/vps-setup-runbook.md b/AINative_OKR_CASAN5/.gitea/vps-setup-runbook.md new file mode 100644 index 0000000..8fbd8ec --- /dev/null +++ b/AINative_OKR_CASAN5/.gitea/vps-setup-runbook.md @@ -0,0 +1,222 @@ +# VPS CI/CD Setup Runbook +# Oracle Cloud Tokyo — 161.33.139.73 + +## Architecture +``` +VPS (Ubuntu 24.04, 1 GB RAM) +├── /opt/gitea/ +│ ├── docker-compose.yml ← add act-runner service here +│ ├── .env ← add RUNNER_REGISTRATION_TOKEN here +│ ├── act-runner-config.yaml ← copy from .gitea/act-runner-config.yaml +│ └── gitea-data/ +``` + +--- + +## Step 1 — Enable Gitea Actions + +SSH into the VPS: +```bash +ssh ubuntu@161.33.139.73 +``` + +Add the Actions env variable to the Gitea service in docker-compose.yml: +```yaml +# In the gitea service environment section, add: +- GITEA__actions__ENABLED=true +``` + +Restart Gitea: +```bash +cd /opt/gitea +docker compose restart gitea +``` + +Verify: Open http://161.33.139.73:3000 → Site Administration → Runners +You should see "Runners" menu item (confirming Actions is enabled). + +--- + +## Step 2 — Get the Runner Registration Token + +1. Log into Gitea as admin: http://161.33.139.73:3000 +2. Go to: Site Administration (⚙) → Runners → "Create new runner" +3. Copy the **Registration Token** shown (looks like: `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`) + +--- + +## Step 3 — Copy act_runner config to VPS + +From your local machine: +```bash +scp AINative_OKR_CASAN5/.gitea/act-runner-config.yaml \ + ubuntu@161.33.139.73:/opt/gitea/act-runner-config.yaml +``` + +--- + +## Step 4 — Add RUNNER_REGISTRATION_TOKEN to /opt/gitea/.env + +On the VPS: +```bash +# Replace with the token copied in Step 2 +echo "RUNNER_REGISTRATION_TOKEN=" >> /opt/gitea/.env +``` + +--- + +## Step 5 — Add act_runner service to docker-compose.yml + +Edit `/opt/gitea/docker-compose.yml` and add the `act-runner` service. + +**Typical Gitea docker-compose.yml after changes:** + +```yaml +version: "3" + +networks: + gitea: + external: false + +volumes: + gitea-data: + act-runner-data: + +services: + gitea: + image: gitea/gitea:latest + container_name: gitea + restart: always + networks: + - gitea + environment: + - USER_UID=1000 + - USER_GID=1000 + - GITEA__actions__ENABLED=true # ← ADD THIS LINE + ports: + - "3000:3000" + - "2222:22" + volumes: + - ./gitea-data:/data + + act-runner: + image: gitea/act_runner:latest + container_name: act-runner + restart: unless-stopped + networks: + - gitea # same network as gitea + depends_on: + - gitea + environment: + - GITEA_INSTANCE_URL=http://gitea:3000 # internal Docker hostname + - GITEA_RUNNER_REGISTRATION_TOKEN=${RUNNER_REGISTRATION_TOKEN} + - GITEA_RUNNER_NAME=casan-runner-oracle + - CONFIG_FILE=/config/act-runner-config.yaml + volumes: + - /var/run/docker.sock:/var/run/docker.sock # DinD for job containers + - ./act-runner-config.yaml:/config/act-runner-config.yaml:ro + - act-runner-data:/data +``` + +> If your current docker-compose.yml already has a `networks:` or `volumes:` section, +> merge them — don't add duplicate top-level keys. + +--- + +## Step 6 — Start act_runner + +```bash +cd /opt/gitea +docker compose pull act-runner +docker compose up -d act-runner +``` + +Check registration: +```bash +docker compose logs -f act-runner +# Look for: "runner registered" or "connected to Gitea" +``` + +In Gitea Web UI: Site Administration → Runners → you should see "casan-runner-oracle" with status **Online**. + +--- + +## Step 7 — Pull the catthehacker runner image (one-time) + +The first CI run will pull `catthehacker/ubuntu:act-22.04` (~2 GB). Pre-pull to avoid timeout: + +```bash +docker pull catthehacker/ubuntu:act-22.04 +``` + +This takes ~2-5 minutes depending on internet speed. + +--- + +## Step 8 — Create repo in Gitea and push the project + +On the VPS (or via Gitea web UI), create a new repository: +- URL: http://161.33.139.73:3000 +- Name: `casan5` (or any name) +- Make it public or private (your choice) + +On your local machine: +```bash +cd Output_CASAN5_REFINED/AINative_OKR_CASAN5 + +# Add Gitea as remote +git remote add gitea ssh://git@161.33.139.73:2222//casan5.git + +# Push +git push gitea main +``` + +--- + +## Step 9 — Verify CI triggered + +After push, go to: +http://161.33.139.73:3000//casan5/actions + +You should see a workflow run in progress. Click it to see live logs. + +Expected final result: +``` +✅ Frontend Tests (H3 gate) — 16/16 PASS +✅ CASAN Security Gate (H4/H5/H2) — PASS=7 SKIP=1 FAIL=0 +``` + +SKIP=1 is expected (Ollama is not on the CI server — this is the model-router/red-team group). +The gate exits 0 because FAIL=0. + +--- + +## Memory Monitoring + +```bash +# Watch RAM usage while CI runs +watch -n 2 'free -h && docker stats --no-stream' +``` + +If OOM occurs, reduce the job container memory limit in act-runner-config.yaml +or add swap: +```bash +sudo fallocate -l 1G /swapfile +sudo chmod 600 /swapfile +sudo mkswap /swapfile +sudo swapon /swapfile +echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab +``` + +--- + +## Troubleshooting + +| Symptom | Fix | +|---|---| +| Runner shows "Offline" | Check `docker compose logs act-runner`; verify token is correct | +| `GITEA_INSTANCE_URL` unreachable | Ensure gitea and act-runner are on the same Docker network | +| Job stuck "Waiting for runner" | Runner is busy (capacity=1); wait or increase capacity | +| `python: command not found` | The workflow's "Install test tools" step installs `python-is-python3` | +| OOM during npm ci | Switch to `npm ci -w frontend` (already done in workflow) or add swap | +| `catthehacker/ubuntu:act-22.04` pull fails | Run `docker pull` manually on VPS first | diff --git a/AINative_OKR_CASAN5/.gitea/workflows/ci.yml b/AINative_OKR_CASAN5/.gitea/workflows/ci.yml new file mode 100644 index 0000000..6d0f8ee --- /dev/null +++ b/AINative_OKR_CASAN5/.gitea/workflows/ci.yml @@ -0,0 +1,208 @@ +name: CASAN CI Gate + +# Runs on every push/PR to catch regressions (H3) and validate security controls (H4/H5). +on: + push: + branches: [main, develop, "feature/**"] + pull_request: + branches: [main] + +# Cancel in-flight runs of the same branch when a newer push arrives. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # ────────────────────────────────────────────────────────────────────────── + # Job 1 — Frontend unit tests (fast gate, ~1 min) + # ────────────────────────────────────────────────────────────────────────── + frontend-tests: + name: "Frontend Tests (H3 gate)" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node.js 20 + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "npm" + + - name: Install frontend dependencies + run: npm ci -w frontend + + - name: Run Vitest (16 tests) + run: npm test -w frontend + + # ────────────────────────────────────────────────────────────────────────── + # Job 2 — CASAN Security Gate + Vault KMS signing (H4/H5/H2/H6/H7) + # + # Steps: + # 1. Install tools + # 2. Run CASAN4 harness (35 tests) — rebuilds audit.jsonl from scratch + # 3. Run adversarial suite (44 tests) + # 4. Sign audit chain head via Vault KMS → anchor=signed (H5 improvement) + # 5. Sign policy bundle via Vault KMS → key_backend=vault-kms (H5) + # 6. security-gate.sh aggregate verdict + # PASS=7 SKIP=1(Ollama) FAIL=0 + # ────────────────────────────────────────────────────────────────────────── + security-gate: + name: "CASAN Security Gate + Vault KMS (H4/H5)" + runs-on: ubuntu-latest + env: + # Vault runs as a sibling service on the same Docker network. + # VAULT_TOKEN is stored as a Gitea Actions secret (Settings → Secrets). + # Without Vault, scripts fall back to local key file — anchor=unsigned. + VAULT_ADDR: "http://vault:8200" + VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node.js 20 + uses: actions/setup-node@v3 + with: + node-version: "20" + + - name: Install test tools + run: | + apt-get update -qq 2>/dev/null && \ + apt-get install -y -qq jq openssl python3 python-is-python3 uuid-runtime curl 2>/dev/null || true + command -v python >/dev/null 2>&1 || \ + ln -sf "$(command -v python3)" /usr/local/bin/python + echo "python: $(python --version)" + echo "jq: $(jq --version)" + echo "openssl: $(openssl version)" + + - name: Install frontend dependencies + run: npm ci -w frontend + + - name: Vault KMS — enable transit + pre-create keys + # Non-blocking: if Vault is unreachable, scripts fall back gracefully. + run: | + if curl -sf "$VAULT_ADDR/v1/sys/health" >/dev/null 2>&1; then + bash .specify/scripts/bash/vault-kms.sh enable-transit + bash .specify/scripts/bash/vault-kms.sh ensure-key casan-policy-key + bash .specify/scripts/bash/vault-kms.sh ensure-key casan-audit-key + echo "VAULT_KMS_READY" + else + echo "VAULT_KMS_SKIP (unreachable — will use local-file fallback)" + fi + + - name: Run CASAN4 harness tests (35 tests) + # This clears .specify/logs/ and rebuilds audit.jsonl from scratch. + run: bash .specify/tests/run-casan4-harness-tests.sh + + - name: Run adversarial harness tests (44 tests) + run: bash .specify/tests/adversarial-harness-tests.sh + + - name: Sign audit chain head via Vault KMS (H5) + # After harness tests rebuild audit.jsonl, sign the head so that + # verify-audit-chain.sh reports "anchor=signed" (not "anchor=unsigned"). + run: bash .specify/scripts/bash/sign-audit-head.sh + + - name: Sign policy bundle via Vault KMS (H5) + run: bash .specify/scripts/bash/sign-policy-bundle.sh sign + + - name: Verify audit chain (anchor=signed expected in CI) + run: bash .specify/scripts/bash/verify-audit-chain.sh + + - name: Security gate — aggregate verdict (PASS=7 SKIP=1 FAIL=0) + # Ollama-dependent checks auto-SKIP (not FAIL) when Ollama is not reachable. + # security-gate.sh re-runs harness + adversarial + verify + scan + frontend. + # Exit 0 only when FAIL=0. + run: bash .specify/scripts/bash/security-gate.sh + + - name: Upload test evidence + if: always() + uses: actions/upload-artifact@v3 + with: + name: casan-evidence-${{ github.run_number }} + path: | + docs/output/casan/evidence/harness-test-report.md + docs/output/casan/evidence/ + retention-days: 14 + + # ────────────────────────────────────────────────────────────────────────── + # Job 3 — Deploy OKR web app (main branch only) + # + # Architecture: + # okr-backend — NestJS + Prisma + SQLite, port 3001 (internal only) + # okr-frontend — nginx + React SPA, port 80 (public) + # nginx proxies /api/v1/* → okr-backend:3001 + # Both on Docker network "okr-net" (separate from gitea/vault network) + # + # Secrets required in Gitea Settings → Secrets → Actions: + # JWT_SECRET — random string for NestJS JWT signing + # ────────────────────────────────────────────────────────────────────────── + deploy-okr: + name: "Deploy OKR → port 80 (H3 CI gate)" + runs-on: ubuntu-latest + needs: [frontend-tests, security-gate] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + env: + JWT_SECRET: ${{ secrets.JWT_SECRET }} + FRONTEND_ORIGIN: "http://161.33.139.73" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Build backend image (node:22-slim — required for node:sqlite) + run: | + docker build \ + -t okr-backend:latest \ + -f Dockerfile.backend \ + . + + - name: Build frontend image (nginx + React SPA) + run: | + docker build \ + --build-arg VITE_API_BASE_URL=/api/v1 \ + -t okr-frontend:latest \ + -f Dockerfile.frontend \ + . + + - name: Create network + persistent volume + run: | + docker network create okr-net 2>/dev/null || true + docker volume create okr-db 2>/dev/null || true + + - name: Deploy backend (port 3001, internal only) + run: | + docker rm -f okr-backend 2>/dev/null || true + docker run -d \ + --name okr-backend \ + --network okr-net \ + -e PORT=3001 \ + -e DATABASE_URL=file:/data/okr.db \ + -e JWT_SECRET="${JWT_SECRET}" \ + -e FRONTEND_ORIGIN="${FRONTEND_ORIGIN}" \ + -e NODE_ENV=production \ + -v okr-db:/data \ + --restart unless-stopped \ + okr-backend:latest + + - name: Deploy frontend (port 80, public) + run: | + docker rm -f okr-frontend 2>/dev/null || true + docker run -d \ + --name okr-frontend \ + --network okr-net \ + -p 80:80 \ + --restart unless-stopped \ + okr-frontend:latest + + - name: Health check + run: | + echo "Waiting 20 s for containers to initialise..." + sleep 20 + if curl -sf http://localhost/ -o /dev/null; then + echo "DEPLOY_OK frontend=http://161.33.139.73" + else + echo "DEPLOY_WARN frontend check failed — dumping logs" + docker logs okr-frontend --tail 30 || true + docker logs okr-backend --tail 30 || true + fi diff --git a/AINative_OKR_CASAN5/.specify/scripts/bash/security-gate.sh b/AINative_OKR_CASAN5/.specify/scripts/bash/security-gate.sh index 6ce1f88..7b386b4 100755 --- a/AINative_OKR_CASAN5/.specify/scripts/bash/security-gate.sh +++ b/AINative_OKR_CASAN5/.specify/scripts/bash/security-gate.sh @@ -18,6 +18,9 @@ run() { # echo "== CASAN security gate ==" run "run-casan4 harness suite" bash "$ROOT/.specify/tests/run-casan4-harness-tests.sh" run "adversarial suite" bash "$ROOT/.specify/tests/adversarial-harness-tests.sh" +# Wave 5: sign audit head via Vault KMS (or local fallback) before verifying. +# This changes verify output from anchor=unsigned to anchor=signed when Vault is configured. +run "sign audit-chain head (KMS)" bash "$ROOT/.specify/scripts/bash/sign-audit-head.sh" run "audit hash-chain (signed)" bash "$ROOT/.specify/scripts/bash/verify-audit-chain.sh" run "tool-call audit (signed)" bash "$ROOT/.specify/scripts/bash/verify-tool-audit.sh" # Wave 3 additions diff --git a/AINative_OKR_CASAN5/.specify/scripts/bash/sign-audit-head.sh b/AINative_OKR_CASAN5/.specify/scripts/bash/sign-audit-head.sh new file mode 100755 index 0000000..936ee37 --- /dev/null +++ b/AINative_OKR_CASAN5/.specify/scripts/bash/sign-audit-head.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +set -euo pipefail + +# CASAN H5 — Sign the audit chain head hash via Vault KMS (or local key fallback). +# +# Called by CI after harness tests rebuild audit.jsonl, so that +# verify-audit-chain.sh produces "anchor=signed" (not "anchor=unsigned"). +# +# Usage: +# sign-audit-head.sh [audit-jsonl] +# +# Writes: +# /audit-head.txt — the head hash (plain text) +# /audit-head.sig — RSA signature of audit-head.txt +# +# After this script, verify-audit-chain.sh reports: +# AUDIT_CHAIN_VALID anchor=signed +# +# Environment (KMS path): +# VAULT_ADDR — e.g. http://vault:8200 +# VAULT_TOKEN — token with transit/sign/casan-audit-key capability + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +AUDIT_LOG="${1:-$PROJECT_ROOT/.specify/logs/audit/audit.jsonl}" +AUDIT_DIR="$(dirname "$AUDIT_LOG")" +HEAD_FILE="$AUDIT_DIR/audit-head.txt" +HEAD_SIG="$AUDIT_DIR/audit-head.sig" +AUDIT_PUB="$PROJECT_ROOT/.specify/level5/central-governance/audit-public.pem" + +if [[ ! -f "$AUDIT_LOG" ]]; then + echo "SIGN_AUDIT_HEAD_SKIP audit.jsonl not found" >&2 + exit 0 +fi + +# ── Compute the current chain head ──────────────────────────────────────── +HEAD_HASH="$(python - "$AUDIT_LOG" <<'PY' +import hashlib, json, sys +path = sys.argv[1] +previous = "" +with open(path, encoding="utf-8") as f: + for line in f: + if not line.strip(): + continue + record = json.loads(line) + core = "|".join([ + record.get("timestamp",""), record.get("trace_id",""), + record.get("action",""), record.get("actor",""), + record.get("risk_level",""), record.get("decision",""), + record.get("approval_status",""), record.get("approver",""), + record.get("input_hash",""), record.get("output_hash",""), + previous, + ]) + previous = hashlib.sha256(core.encode()).hexdigest() +print(previous) +PY +)" + +if [[ -z "$HEAD_HASH" ]]; then + echo "SIGN_AUDIT_HEAD_SKIP empty chain" >&2 + exit 0 +fi + +printf '%s' "$HEAD_HASH" > "$HEAD_FILE" + +# ── Sign the head file ──────────────────────────────────────────────────── +VAULT_KMS="$SCRIPT_DIR/vault-kms.sh" + +if [[ -n "${VAULT_ADDR:-}" && -n "${VAULT_TOKEN:-}" ]] && \ + curl -sf "$VAULT_ADDR/v1/sys/health" >/dev/null 2>&1; then + # KMS path — sign via Vault Transit, export public key + bash "$VAULT_KMS" enable-transit + bash "$VAULT_KMS" sign "$HEAD_FILE" "$HEAD_SIG" "casan-audit-key" + bash "$VAULT_KMS" pubkey "$AUDIT_PUB" "casan-audit-key" + echo "SIGN_AUDIT_HEAD_OK head=$HEAD_HASH anchor=vault-kms" +else + # Fallback — local key (dev environment without Vault) + # IMPORTANT: Do NOT generate a new key pair here. audit-public.pem is committed + # and shared by both audit.jsonl and tool-calls.jsonl verification. Generating a + # new key overwrites audit-public.pem and breaks tool-calls-head.sig verification. + AUDIT_PRIV="$PROJECT_ROOT/.specify/level5/central-governance/audit-private.pem" + if [[ ! -f "$AUDIT_PRIV" ]]; then + echo "SIGN_AUDIT_HEAD_SKIP no private key and VAULT_ADDR not set — verify will show anchor=unsigned" >&2 + exit 0 + fi + openssl dgst -sha256 -sign "$AUDIT_PRIV" -out "$HEAD_SIG" "$HEAD_FILE" + echo "SIGN_AUDIT_HEAD_OK head=$HEAD_HASH anchor=local-file" +fi diff --git a/AINative_OKR_CASAN5/.specify/scripts/bash/sign-policy-bundle.sh b/AINative_OKR_CASAN5/.specify/scripts/bash/sign-policy-bundle.sh index 224e41b..9be35eb 100755 --- a/AINative_OKR_CASAN5/.specify/scripts/bash/sign-policy-bundle.sh +++ b/AINative_OKR_CASAN5/.specify/scripts/bash/sign-policy-bundle.sh @@ -65,12 +65,24 @@ PY if [[ "$MODE" == "sign" ]]; then generate_manifest - if [[ ! -f "$PRIVATE_KEY" ]]; then - openssl genrsa -out "$PRIVATE_KEY" 2048 >/dev/null 2>&1 - openssl rsa -in "$PRIVATE_KEY" -pubout -out "$PUBLIC_KEY" >/dev/null 2>&1 + + if [[ -n "${VAULT_ADDR:-}" && -n "${VAULT_TOKEN:-}" ]] && \ + curl -sf "$VAULT_ADDR/v1/sys/health" >/dev/null 2>&1; then + # ── KMS path: sign via HashiCorp Vault Transit (key never stored on disk) ── + VAULT_KMS="$SCRIPT_DIR/vault-kms.sh" + bash "$VAULT_KMS" enable-transit + bash "$VAULT_KMS" sign "$MANIFEST" "$SIGNATURE" "casan-policy-key" + bash "$VAULT_KMS" pubkey "$PUBLIC_KEY" "casan-policy-key" + echo "POLICY_BUNDLE_SIGNED manifest=$MANIFEST signature=$SIGNATURE public_key=$PUBLIC_KEY key_backend=vault-kms" + else + # ── Fallback: local key file (dev / no Vault) ───────────────────────────── + if [[ ! -f "$PRIVATE_KEY" ]]; then + openssl genrsa -out "$PRIVATE_KEY" 2048 >/dev/null 2>&1 + openssl rsa -in "$PRIVATE_KEY" -pubout -out "$PUBLIC_KEY" >/dev/null 2>&1 + fi + openssl dgst -sha256 -sign "$PRIVATE_KEY" -out "$SIGNATURE" "$MANIFEST" + echo "POLICY_BUNDLE_SIGNED manifest=$MANIFEST signature=$SIGNATURE public_key=$PUBLIC_KEY key_backend=local-file" fi - openssl dgst -sha256 -sign "$PRIVATE_KEY" -out "$SIGNATURE" "$MANIFEST" - echo "POLICY_BUNDLE_SIGNED manifest=$MANIFEST signature=$SIGNATURE public_key=$PUBLIC_KEY" exit 0 fi diff --git a/AINative_OKR_CASAN5/.specify/scripts/bash/vault-kms.sh b/AINative_OKR_CASAN5/.specify/scripts/bash/vault-kms.sh new file mode 100755 index 0000000..78579ac --- /dev/null +++ b/AINative_OKR_CASAN5/.specify/scripts/bash/vault-kms.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# CASAN H5 — HashiCorp Vault KMS helper +# +# Provides Vault-backed signing operations as a drop-in replacement for +# direct openssl key-file usage. Scripts detect KMS availability via: +# [[ -n "${VAULT_ADDR:-}" && -n "${VAULT_TOKEN:-}" ]] +# +# Usage (direct): +# vault-kms.sh sign [key_name] +# vault-kms.sh verify [key_name] +# vault-kms.sh pubkey [key_name] +# vault-kms.sh status +# +# Usage (sourced): +# source vault-kms.sh +# vault_kms_sign [key_name] +# vault_kms_pubkey [key_name] +# vault_kms_status +# +# Environment: +# VAULT_ADDR — e.g. http://vault:8200 or http://161.33.139.73:8200 +# VAULT_TOKEN — root token or policy token with transit/sign/* capability + +set -euo pipefail + +: "${VAULT_ADDR:?VAULT_ADDR must be set}" +: "${VAULT_TOKEN:?VAULT_TOKEN must be set}" + +_VAULT_DEFAULT_KEY="casan-policy-key" +_VAULT_AUDIT_KEY="casan-audit-key" + +# ── Ensure required tools ────────────────────────────────────────────────── +_check_deps() { + for cmd in curl python3; do + command -v "$cmd" >/dev/null 2>&1 || { echo "vault-kms: required: $cmd" >&2; exit 1; } + done +} + +# ── Transit: ensure key exists ──────────────────────────────────────────── +vault_kms_ensure_key() { + local key="${1:-$_VAULT_DEFAULT_KEY}" + local status + status=$(curl -sf \ + -H "X-Vault-Token: $VAULT_TOKEN" \ + "$VAULT_ADDR/v1/transit/keys/$key" 2>/dev/null | \ + python3 -c "import sys,json; d=json.load(sys.stdin); print('ok' if 'data' in d else 'missing')" 2>/dev/null || echo "missing") + + if [[ "$status" != "ok" ]]; then + curl -sf -X POST \ + -H "X-Vault-Token: $VAULT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"type":"rsa-2048","exportable":false,"allow_plaintext_backup":false}' \ + "$VAULT_ADDR/v1/transit/keys/$key" >/dev/null + echo "vault-kms: created transit key: $key" >&2 + fi +} + +# ── Sign a file ─────────────────────────────────────────────────────────── +# Produces a DER binary signature compatible with: +# openssl dgst -sha256 -verify pub.pem -signature +vault_kms_sign() { + local file="$1" output="$2" key="${3:-$_VAULT_DEFAULT_KEY}" + _check_deps + vault_kms_ensure_key "$key" + + # Base64-encode the file content for the Vault API payload + local input_b64 + input_b64=$(base64 -w0 < "$file" 2>/dev/null || base64 < "$file") + + # Call Vault Transit sign — PKCS#1 v1.5 + SHA-256 (compatible with openssl verify) + local response + response=$(curl -sf -X POST \ + -H "X-Vault-Token: $VAULT_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"input\":\"$input_b64\",\"hash_algorithm\":\"sha2-256\",\"signature_algorithm\":\"pkcs1v15\",\"prehashed\":false}" \ + "$VAULT_ADDR/v1/transit/sign/$key") || { + echo "vault-kms: sign request failed (key=$key)" >&2; exit 1 + } + + # Extract signature and strip the "vault:v1:" prefix → raw base64 DER bytes + local sig_b64 + sig_b64=$(printf '%s' "$response" | python3 -c " +import sys, json +d = json.load(sys.stdin) +sig = d['data']['signature'] +# vault:v1: → keep only base64 part +print(sig.split(':')[-1]) +") + + # Decode base64 → binary DER file (identical format to openssl -sign output) + printf '%s' "$sig_b64" | base64 -d > "$output" + echo "vault-kms: signed $file → $output (key=$key anchor=vault-kms)" >&2 +} + +# ── Export public key ───────────────────────────────────────────────────── +# Writes the RSA public key as PEM so openssl dgst -verify still works. +vault_kms_pubkey() { + local output="$1" key="${2:-$_VAULT_DEFAULT_KEY}" + _check_deps + vault_kms_ensure_key "$key" + + local response + response=$(curl -sf \ + -H "X-Vault-Token: $VAULT_TOKEN" \ + "$VAULT_ADDR/v1/transit/keys/$key") || { + echo "vault-kms: pubkey fetch failed (key=$key)" >&2; exit 1 + } + + python3 - "$output" </dev/null +} + +# ── Connectivity check ──────────────────────────────────────────────────── +vault_kms_status() { + if curl -sf "$VAULT_ADDR/v1/sys/health" >/dev/null 2>&1; then + echo "VAULT_KMS_AVAILABLE addr=$VAULT_ADDR" + return 0 + else + echo "VAULT_KMS_UNAVAILABLE addr=${VAULT_ADDR:-unset}" + return 1 + fi +} + +# ── Enable transit engine (idempotent) ──────────────────────────────────── +vault_kms_enable_transit() { + curl -sf -X POST \ + -H "X-Vault-Token: $VAULT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"type":"transit"}' \ + "$VAULT_ADDR/v1/sys/mounts/transit" >/dev/null 2>&1 || true +} + +# ── CLI entrypoint ──────────────────────────────────────────────────────── +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + CMD="${1:-status}" + shift || true + case "$CMD" in + sign) vault_kms_sign "$@" ;; + pubkey) vault_kms_pubkey "$@" ;; + verify) vault_kms_verify "$@" ;; + status) vault_kms_status ;; + enable-transit) vault_kms_enable_transit ;; + ensure-key) vault_kms_ensure_key "${1:-}" ;; + *) + echo "Usage: vault-kms.sh {sign|pubkey|verify|status|enable-transit|ensure-key}" >&2 + exit 64 + ;; + esac +fi diff --git a/AINative_OKR_CASAN5/Dockerfile.backend b/AINative_OKR_CASAN5/Dockerfile.backend new file mode 100644 index 0000000..62855ff --- /dev/null +++ b/AINative_OKR_CASAN5/Dockerfile.backend @@ -0,0 +1,55 @@ +# ─── Stage 1: Build ────────────────────────────────────────────────────────── +FROM node:22-slim AS builder + +WORKDIR /app + +RUN apt-get update -qq && apt-get install -y -qq openssl python3 && rm -rf /var/lib/apt/lists/* + +COPY package.json package-lock.json ./ +COPY backend/package.json ./backend/ +COPY frontend/package.json ./frontend/ +COPY backend/prisma ./backend/prisma + +RUN npm ci + +COPY backend/src ./backend/src +COPY backend/tsconfig*.json ./backend/ + +# Generate Prisma client + compile TypeScript +RUN cd backend && npx prisma generate && npx tsc -p tsconfig.build.json + +# ─── Stage 2: Runtime ──────────────────────────────────────────────────────── +FROM node:22-slim AS runtime + +# node:sqlite (setup-sqlite.mjs) requires Node 22 — this image satisfies that. + +WORKDIR /app + +RUN apt-get update -qq && apt-get install -y -qq openssl && rm -rf /var/lib/apt/lists/* + +COPY package.json package-lock.json ./ +COPY backend/package.json ./backend/ +COPY frontend/package.json ./frontend/ +COPY backend/prisma ./backend/prisma + +# All deps (including devDeps) so that tsx (seed) and prisma CLI are available. +RUN npm ci --ignore-scripts + +# Generate Prisma client in runtime image (CWD resolves schema at backend/prisma/schema.prisma) +RUN cd backend && npx prisma generate + +COPY --from=builder /app/backend/dist ./backend/dist +COPY backend/scripts ./backend/scripts +COPY backend/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +WORKDIR /app/backend + +VOLUME ["/data"] +EXPOSE 3001 + +ENV PORT=3001 +ENV DATABASE_URL=file:/data/okr.db +ENV NODE_ENV=production + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/AINative_OKR_CASAN5/Dockerfile.frontend b/AINative_OKR_CASAN5/Dockerfile.frontend new file mode 100644 index 0000000..efd80ea --- /dev/null +++ b/AINative_OKR_CASAN5/Dockerfile.frontend @@ -0,0 +1,28 @@ +# ─── Stage 1: Build ────────────────────────────────────────────────────────── +FROM node:20-alpine AS builder + +WORKDIR /app + +# VITE_API_BASE_URL is baked into the bundle at build time. +# Use a relative path so the image works with any hostname/IP — nginx +# running on the same host proxies /api/v1/* to the backend container. +ARG VITE_API_BASE_URL=/api/v1 +ENV VITE_API_BASE_URL=$VITE_API_BASE_URL + +COPY package.json package-lock.json ./ +COPY frontend/package.json ./frontend/ +COPY backend/package.json ./backend/ + +RUN npm ci -w frontend + +COPY frontend ./frontend + +RUN npm run build -w frontend + +# ─── Stage 2: nginx runtime ────────────────────────────────────────────────── +FROM nginx:alpine AS runtime + +COPY --from=builder /app/frontend/dist /usr/share/nginx/html +COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 diff --git a/AINative_OKR_CASAN5/backend/entrypoint.sh b/AINative_OKR_CASAN5/backend/entrypoint.sh new file mode 100644 index 0000000..e9d20c4 --- /dev/null +++ b/AINative_OKR_CASAN5/backend/entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# OKR backend container entrypoint. +# - First run: creates SQLite schema via setup-sqlite.mjs + seeds initial data. +# - Subsequent runs: DB already exists, skip init. +# WORKDIR expected: /app/backend (set in Dockerfile) +set -e + +# Hoisted node_modules/.bin (workspace root) must be in PATH for tsx + prisma CLI +export PATH="/app/node_modules/.bin:$PATH" + +mkdir -p /data + +if [ ! -f "/data/okr.db" ]; then + echo "[OKR] First run — initializing database at /data/okr.db" + node scripts/setup-sqlite.mjs + npx prisma db seed + echo "[OKR] Database initialized" +fi + +echo "[OKR] Starting backend on port ${PORT:-3001}" +exec node dist/main.js diff --git a/AINative_OKR_CASAN5/nginx/nginx.conf b/AINative_OKR_CASAN5/nginx/nginx.conf new file mode 100644 index 0000000..8ce41b1 --- /dev/null +++ b/AINative_OKR_CASAN5/nginx/nginx.conf @@ -0,0 +1,25 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Proxy /api/v1/* → NestJS backend (same Docker network: okr-net) + location /api/v1/ { + proxy_pass http://okr-backend:3001/api/v1/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 30s; + # Pass cookies (HttpOnly JWT) through the proxy + proxy_pass_header Set-Cookie; + } + + # React SPA — React Router requires all non-asset paths to serve index.html + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/AINative_OKR_CASAN5/package-lock.json b/AINative_OKR_CASAN5/package-lock.json index f1b25a0..0208980 100644 --- a/AINative_OKR_CASAN5/package-lock.json +++ b/AINative_OKR_CASAN5/package-lock.json @@ -50,17 +50,30 @@ "zod": "^3.25.67" }, "devDependencies": { + "@testing-library/dom": "^10.0.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.5.2", "@types/node": "^24.0.8", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^4.6.0", "autoprefixer": "^10.4.21", + "jsdom": "^26.1.0", "postcss": "^8.5.6", "tailwindcss": "^3.4.17", "typescript": "^5.8.3", - "vite": "^5.4.19" + "vite": "^5.4.19", + "vitest": "^3.2.4" } }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@ainative-okr/backend": { "resolved": "backend", "link": true @@ -82,6 +95,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -316,6 +350,16 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", @@ -374,6 +418,121 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -1600,6 +1759,105 @@ "react": "^18 || ^19" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tokenizer/inflate": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", @@ -1624,6 +1882,13 @@ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "license": "MIT" }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1690,6 +1955,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -1707,6 +1983,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1905,6 +2188,121 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/expect": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", + "integrity": "sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.6.tgz", + "integrity": "sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.6.tgz", + "integrity": "sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.6.tgz", + "integrity": "sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.6", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.6.tgz", + "integrity": "sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.6.tgz", + "integrity": "sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.6.tgz", + "integrity": "sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -2014,6 +2412,16 @@ "dev": true, "license": "MIT" }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -2027,6 +2435,16 @@ "dev": true, "license": "MIT" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2325,6 +2743,16 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2385,6 +2813,23 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2401,6 +2846,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2647,6 +3102,13 @@ "node": ">= 0.10" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2660,6 +3122,20 @@ "node": ">=4" } }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -2667,6 +3143,57 @@ "dev": true, "license": "MIT" }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2684,6 +3211,23 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deepmerge-ts": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", @@ -2725,6 +3269,16 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", @@ -2776,6 +3330,13 @@ "dev": true, "license": "MIT" }, + "node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -2861,6 +3422,19 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2879,6 +3453,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", @@ -2964,6 +3545,16 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2973,6 +3564,16 @@ "node": ">= 0.6" } }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", @@ -3546,6 +4147,19 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -3566,6 +4180,30 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -3611,6 +4249,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -3708,6 +4356,13 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/iterare": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", @@ -3733,6 +4388,107 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -3894,6 +4650,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3904,6 +4667,26 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -4012,6 +4795,16 @@ "node": ">= 0.6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -4230,6 +5023,13 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "dev": true, + "license": "MIT" + }, "node_modules/nypm": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.8.tgz", @@ -4314,6 +5114,19 @@ "wrappy": "1" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4352,6 +5165,16 @@ "dev": true, "license": "MIT" }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -4574,6 +5397,34 @@ "dev": true, "license": "MIT" }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prisma": { "version": "6.19.3", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.3.tgz", @@ -4622,6 +5473,16 @@ "node": ">=10" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -4751,6 +5612,13 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -4830,6 +5698,20 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", @@ -4930,6 +5812,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4989,6 +5878,19 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -5145,6 +6047,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -5161,6 +6070,13 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -5170,6 +6086,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -5213,6 +6136,39 @@ "node": ">=8" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/strtok3": { "version": "10.3.5", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", @@ -5336,6 +6292,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "3.4.19", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", @@ -5433,6 +6396,13 @@ "node": ">=0.8" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", @@ -5473,6 +6443,56 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5513,6 +6533,19 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -5747,6 +6780,29 @@ } } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -6177,12 +7233,155 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/vitest": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.6.tgz", + "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.6", + "@vitest/mocker": "3.2.6", + "@vitest/pretty-format": "^3.2.6", + "@vitest/runner": "3.2.6", + "@vitest/snapshot": "3.2.6", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.6", + "@vitest/ui": "3.2.6", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest/node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -6193,6 +7392,23 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -6208,6 +7424,45 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",