diff --git a/openspec/config.yaml b/openspec/config.yaml new file mode 100644 index 0000000..9af5f3f --- /dev/null +++ b/openspec/config.yaml @@ -0,0 +1,61 @@ +schema: spec-driven + +context: | + Componente: Golden Path "rest-api/python" (Coralware Cove IDP) + Tipo: Shape de aplicación tenant — FastAPI stateless puro (sin BD) + Repo Gitea generado: idp-templates/template-rest-api-python + Audiencia consumidora: tenants externos en tier free-trial / startup + Gobernanza: openspec interno de Coralware — NO se sincroniza al repo + Gitea ni llega al repo del cliente. + + Stack del shape: + - Python 3.12 + FastAPI + Uvicorn + - Sin persistencia (BD opcional via web-backend/python si se requiere) + - Helm chart minimal (Deployment + Service + Ingress (networking.k8s.io/v1, NGINX) + NetworkPolicy) + - CI: Gitea Actions invocando reusable workflow ADR-038 + helm-deploy + + Diferencias con web-backend/python: + - SIN alembic, SIN claim-mariadb.yaml, SIN Job pre-upgrade + - SIN testcontainers (tests sin Docker) + - SIN ServiceMonitor por default (tier free-trial sin Prometheus) + - Tests apuntan a probar el endpoint, no a cubrir un dominio rico + - Helm chart sin hooks; rollout directo + + Convenciones obligatorias del shape: + - Endpoints fijos: /health, /docs, /openapi.json, /metrics + - /health retorna {status: "ok"} (sin chequeo de DB) + - Labels: app.kubernetes.io/name=, idp.coralware.cloud/tier, + idp.coralware.cloud/tenant-id, idp.coralware.cloud/golden-path=rest-api, + idp.coralware.cloud/golden-path-lang=python + - SecurityContext: runAsNonRoot=true, runAsUser=1001, readOnlyRootFilesystem=true, + drop ALL caps, allowPrivilegeEscalation=false + - Imagen: registry.coralsafety.com/tenant-/: + - Pipeline ADR-038: black → ruff → bandit → pytest --cov 80 → buildkit → trivy + - Coverage threshold del shape: 80% + + ADRs relacionados: + - ADR-027 — Golden Paths Fase 5 + - ADR-029 — API standards (R-007 correlation_id) + - ADR-038 — CI estándar reusable workflow Python + - ADR-053 — Anatomía del shape web-backend/python (referencia) + + Tier por default: + - free-trial: 1 réplica, sin add-ons, namespace tenant--apps + - startup+: puede escalar réplicas hasta tope del tier + +rules: + proposal: + - Incluir sección "Non-goals" — qué NO entrega el shape + - Si el cambio agrega persistencia, valorar promover a web-backend/python + en lugar de complicar este shape + - Referenciar ADRs y golden paths cuando aplique + design: + - Documentar variables de entorno consumidas (LOG_LEVEL, CORRELATION_ID_HEADER, + KEYCLOAK_ISSUER opcional) + - Comportamiento fail-safe: errores de upstream no crashean la app + - Si el cambio toca el flujo CI/CD, actualizar flowchart Mermaid de specs/main.md + ANTES de tocar el workflow YAML + tasks: + - shapeVersion en coralware-shape.yaml sigue semver + - Mantener exclusión de openspec/ en el sync a Gitea + - Validar render server-side de placeholders por repo-provisioner diff --git a/openspec/specs/main.md b/openspec/specs/main.md new file mode 100644 index 0000000..34854fa --- /dev/null +++ b/openspec/specs/main.md @@ -0,0 +1,153 @@ +# Spec — Golden Path `rest-api/python` + +**Versión del shape:** 1.0.0 +**Status:** Activo (free-trial entry point) +**ADR de referencia:** ADR-027 (Golden Paths Fase 5), ADR-053 (anatomía +del shape web-backend/python — referencia para decisiones de empaque), +**ADR-060 Fase 1 (`GATEWAY-API-MIGRATION`): ingress = Traefik Proxy + Gateway +API (`HTTPRoute`), reemplaza el `rke2-ingress-nginx` de ADR-053 §11.1**, +ADR-061 (workflow CI self-contained — sin `uses:` cross-repo; `helm-deploy` +inline sin `container:`) +**Visibilidad:** interna de Coralware — este spec NO se sincroniza al +repo Gitea ni al repo del cliente. + +--- + +## 1. Propósito + +Definir el comportamiento del shape `rest-api/python` — scaffold de API +HTTP stateless en FastAPI, sin persistencia. Es el shape **entry point** +para tier `free-trial`: el cliente arranca rápido con un endpoint REST +funcional sin tener que decidir BD, migraciones ni hooks. + +Cuando un cliente necesita persistencia, debería **migrar a +`web-backend/python`** (ADR-053), no extender este shape para incluir DB. + +--- + +## 2. Contrato del shape + +### 2.1 Entradas + +| Entrada | Origen | Descripción | +|---------|--------|-------------| +| `__APP_NAME__` | placeholder | Sustituido server-side por `repo-provisioner` | +| `__TENANT_ID__` | placeholder | UUID del tenant | +| `__TENANT_ID_NODASHES__` | placeholder | UUID sin guiones, para DNS y namespaces | +| `__TIER__` | placeholder | `free-trial` | `startup` | `business` | `enterprise` | +| `KUBECONFIG_TENANT_B64` | secret de proyecto Gitea | Provisto por `tenant-manager` | +| `vars.TENANT_ID`, `vars.TENANT_ID_NODASHES`, `vars.TENANT_TIER` | vars de proyecto | Provisto por `tenant-manager` | + +### 2.2 Salidas observables + +| Salida | Forma | +|--------|-------| +| Imagen Docker | `registry.coralsafety.com/t-/:` — proyecto Harbor dedicado del tenant (ADR-019 D-03) | +| Helm release | `` en namespace `apps` del cluster tenant (ADR-054) | +| App reachable | `https://..apps.coralware.cloud/health` retorna 200 (FQDN subdominio DOT, ADR-067/ADR-082 — reconciliado drift previo que usaba el host plano con guion) | +| Ingress controller | **Traefik Proxy + Gateway API** en `traefik-system` (bundle Fleet `traefik-bootstrap`) — ADR-060 Fase 1 (`GATEWAY-API-MIGRATION`); reemplaza `rke2-ingress-nginx` (EOL marzo 2026). El shape emite un `HTTPRoute` adjunto al `Gateway` `tenant-gateway` (ns `apps`, stack-deployer §3c). Paridad con web-backend/python | +| TLS | Wildcard per-tenant `*..apps.coralware.cloud` vía Secret `tenant-wildcard-tls` (cert-manager local del cluster, ADR-067 — reconciliado drift previo que citaba el `apps-wildcard-tls` compartido de Fase 0) | +| Endpoints obligatorios | `/health`, `/docs`, `/openapi.json`, `/metrics` | + +### 2.3 Invariantes + +- App stateless — ningún componente persistente requerido. +- `/health` retorna 200 con `{"status":"ok"}` siempre que el proceso esté vivo. +- `helm upgrade --install` del chart funciona **sin `--no-hooks`**. El + `ServiceAccount` se materializa como hook `pre-install,pre-upgrade` + (`hook-weight: -5`, `hook-delete-policy: before-hook-creation`) por paridad + con el shape `web-backend/python`. Este shape no tiene Job de migración, así + que el hook no es estrictamente necesario — se mantiene para coherencia entre + ambos charts. +- SecurityContext: `runAsNonRoot=true`, `readOnlyRootFilesystem=true`, + `drop ALL caps`. +- Sin IPs hardcoded; sin credenciales en código. +- Errores de servicios upstream **NO crashean** la app — se reportan en + el endpoint que los invoca, el proceso sigue. + +--- + +## 3. Flujo principal + +```mermaid +flowchart TD + dev_push([Dev hace git push\nal repo Gitea del tenant]) --> quality_gate[quality_gate\nruff + black + bandit + pytest --cov 80] + quality_gate -- fail --> pipeline_failed([pipeline_failed\nHelm no se invoca]) + quality_gate -- ok --> image_build[image_build\nbuildx push a Harbor t-short] + image_build -- fail --> pipeline_failed + image_build -- ok --> helm_deploy[helm_deploy\nhelm upgrade --install --wait] + helm_deploy --> rollout_apply[rollout_apply\nDeployment con nuevo image tag] + rollout_apply --> rollout_status_check[rollout_status_check\nkubectl rollout status timeout 3m] + rollout_status_check -- fail --> helm_rollback_manual([helm_rollback_manual\ndev decide rollback\npipeline_failed]) + rollout_status_check -- ok --> smoke_health_check[smoke_health_check\nwget /health 200] + smoke_health_check -- fail --> investigate([investigate\nlogs + events\npipeline_failed]) + smoke_health_check -- ok --> app_running([app_running]) +``` + +**Sin nodo `alembic_hook`** — diferencia clave con `web-backend/python`. +Los node IDs son labels Prometheus futuros. + +--- + +## 4. Estados terminales + +| Estado | Significado | Acción del dev | +|--------|-------------|----------------| +| `app_running` | Todo OK — app desplegada, /health 200 | Ninguna | +| `pipeline_failed` | Quality o build falló | Inspeccionar logs CI; corregir; push | +| `helm_rollback_manual` | Rollout no estabilizó en 3 min | `helm rollback`; investigar pod events | +| `investigate` | Smoke falla aunque rollout terminó | Logs Deployment + cluster events | + +--- + +## 5. Comportamiento fail-safe + +- **Servicio upstream caído:** endpoint responde 503 con detalle, app sigue. +- **Imagen no disponible:** `ImagePullBackOff` visible en Rancher; rollout no avanza. +- **Variable de entorno faltante (ej. `LOG_LEVEL`):** default seguro, no crash. + +--- + +## 6. Pruebas negativas + +| ID | Prueba | Estado terminal esperado | +|----|--------|--------------------------| +| N1 | Test fallando | `pipeline_failed` en `quality_gate` | +| N2 | Imagen tag inexistente | `ImagePullBackOff` visible en Rancher | +| N3 | Probe `/health` removido del pod | Rollout falla → `helm_rollback_manual` | + +(Set reducido vs. web-backend/python; sin pruebas de DB porque no hay BD.) + +--- + +## 7. Versionado del shape + +`coralware-shape.yaml` declara `shapeVersion` (semver). Mismas reglas +que `web-backend/python` (ver ADR-053 §7). + +--- + +## 8. Sincronización a Gitea + +`templates/rest-api-python/` (este repo IDP) → `idp-templates/template-rest-api-python` (Gitea). + +Exclusiones del sync: + +- `openspec/` — gobernanza interna del golden path. +- `.ruff_cache/`, `__pycache__/`, `.pytest_cache/`. + +Mecanismo: `rsync --exclude='openspec/' --exclude-from=.gitignore` +desde el scaffold local hacia el clone temporal del repo Gitea. + +--- + +## 9. Promoción a web-backend/python + +Si la app crece y necesita persistencia: + +1. Cliente clona el template `web-backend/python` en un repo nuevo. +2. Migra rutas de su app `rest-api` al nuevo scaffold. +3. Coralware provisiona el `AddonClaim` MariaDB (manual via runbook hasta + ADR-052). + +No hay path de migración in-place — son shapes distintos por diseño.