Saltar a contenido

ADR-022 · Marketplace lifecycle (internal → preview → beta → public)

Fecha: 2026-05-04 Source: /srv/projects/cis/cis-plan/DECISIONS.md (do not edit here — re-split desde la fuente)


Supersedes ADR-022 inicial del 2026-04-29 (consolidación rev2 + cochid slot). Esta versión integra el draft ADR-022-marketplace-lifecycle.md (rev2, agent-M, 2026-05-03) con la versión mergeada el 2026-04-29: añade migration c81d713d16c2 con marketplace_status/pricing_tier, matriz auto-vs-gated, decay/deprecation explícita, reserva de slot cochid (§5), implementación de referencia, alternativa (d) descartada y la regla de grandfathering de la versión original.

Contexto

El marketplace indieweb.cl está live (apex cutover 2026-04-17) consumiendo metadata de cis-platform /api/v1/services/. Con el Bloque M (output 2026-05-03):

  • Template canónico v1 (cis-plan/templates/cis-service-template.md) con frontmatter, validaciones y reglas de coherencia.
  • Schema de plataforma extendido: tabla services ahora tiene pricing_tier VARCHAR(32) y marketplace_status VARCHAR(32) NOT NULL DEFAULT 'internal' (migration c81d713d16c2_marketplace_status_pricing_tier.py, NO aplicada en producción al momento de este ADR).
  • API extendida: GET /api/v1/services/?status=public filtra por etapa, PATCH /api/v1/services/{slug} valida transiciones legales (no skip forward).

Falta consolidar el lifecycle como decisión arquitectónica: qué etapas, qué checklist por salto, qué transiciones requieren revisión humana, qué pasa con servicios deprecated, y cómo se reservan slots para productos futuros (cochid).

Alternativas consideradas

  1. (a) Sin lifecycle formal — cada producto se publica cuando esté "listo"
  2. Pro: máxima velocidad.
  3. Contra: estado pre-ADR; produce drift entre lo publicado y lo realmente operativo. Logos ausentes, AGENT-CONTEXT.md desactualizados, contratos sin versión.

  4. (b) Lifecycle 4 etapas con checklist por saltopropuesta

  5. Pro: cada salto fuerza disciplina mínima. Promotion review humano evita "publicar para 'ya hay'". Demotion explicit con aviso CHANNEL respeta a usuarios externos del marketplace.
  6. Contra: overhead de proceso (~30 min de review por salto). Mitigado porque la mayoría de productos solo cruzan cada gate una vez.

  7. (c) Solo 2 etapas (internal vs public)

  8. Pro: simple.
  9. Contra: insuficiente para productos en construcción que quiero compartir con beta testers (ej. cochid en 6 meses, periodismo2 redesign en review). Se necesita preview/beta intermedios.

  10. (d) Etapas continuas con score 0–100 (vs enum discreto)

  11. Pro: granularidad fina.
  12. Contra: imposible de razonar para humanos / auditor; el threshold para "aparece en catálogo" sería arbitrario. Enum discreto gana por legibilidad.

Decisión: (b) — 4 etapas, checklist acumulativo, gates en preview→beta y beta→public.

1. Estados canónicos

Cuatro estados ordenados linealmente. El campo marketplace_status (DB) y visibility (frontmatter / services.yaml) son la misma cosa con dos nombres por razones históricas — deben mantenerse alineados.

Estado Visible para Indexado SEO Catálogo indieweb.cl
internal sólo cis-superadmin + cis-staff no no aparece
preview + grupo invitado (<slug>-preview Authentik group) no sólo con link directo
beta + cualquier user autenticado CIS noindex, nofollow aparece con badge "Beta"
public cualquiera (auth opcional según producto) indexable aparece sin badge

Default seguro: todo servicio nuevo creado vía POST /api/v1/services/ arranca en internal. Promoción explícita requerida para aparecer en catálogo.

2. Checklist por salto (acumulativo)

Cada transición forward exige el checklist anterior + el del salto actual.

internal → preview

  • Logo SVG 64×64 monogram en core-style/v6/brands/<slug>/logo.svg.
  • AGENT-CONTEXT.md en el repo del servicio (puerto, secrets keys, gotchas, owner).
  • Health endpoint /healthz respondiendo 200 con JSON {status: "ok"}.
  • Entry en cis-platform services tabla con marketplace_status='preview' (post-migration).
  • Authentik group <slug>-preview creado (lista de invitados).

preview → beta

  • Contrato versionado en CONTRACTS.md (REST /api/v1/...) con anchor referenciable.
  • Brand tokens completos en core-style/v6/brands/<slug>/tokens.css (no sólo logo).
  • Tests funcionales mínimos (smoke test) en CI.
  • Page detail /s/<slug>/ en marketplace renderizando con tagline + features.
  • pricing_tier declarado en DB (no NULL): free | metered | flat | custom.
  • pricing_summary provisional en metadata.

beta → public

  • Coverage ≥60% (ADR-018).
  • Logging structlog JSON canónico (ADR-019).
  • OIDC integration via core/py-common/oidc.py o equivalente (ADR-020).
  • Documentación pública (README.md cara al mundo si aplica al producto).
  • Owner declarado en AGENT-CONTEXT.md (maintainer: field) y en metadata.
  • pricing_tier definitivo (no custom para acceso self-service).
  • Frontmatter del template v1 completo y validado.

3. Promotion: auto vs gated

Distinción según delta de exposición:

Transición Tipo Razón
internal → preview Auto (PATCH directo, sólo admin) Exposición a círculo cerrado invitado; bajo riesgo.
preview → beta Gated (review humano cis-superadmin) Pasa a "cualquier user CIS" — requiere checklist de contrato + brand verificado.
beta → public Gated (review humano + 24 h cooldown) Indexable, sin paywall técnico — el checklist completo (coverage, logs, OIDC) debe verificarse antes.

Mecánica del gate: PATCH a marketplace_status lo ejecuta admin-API, pero un check pre-merge en CI valida el checklist contra el repo del servicio (scripts/check_marketplace_lifecycle.py — pendiente de implementación, captured en BACKLOG flujo M.5).

Skip forward (ej. internal → public en una sola llamada): rechazado con 409 Conflict por cis-platform. Implementado en app/routes/services.py::_validate_marketplace_transition.

4. Demotion / decay / deprecation

Demotion (mover a estado anterior): permitida en cualquier delta backward (público → internal directo es legal mecánicamente). Reglas operativas:

  • CHANNEL announcement con 7 días de aviso mínimo (regla CONTRACTS.md "Convenciones de versionado").
  • Razón documentada en CHANGELOG del servicio (bug crítico, refactor mayor, deprecation).
  • URL viva con HTTP 410 + redirect a explicación durante el grace period.
  • Subscribers / contracts activos: cuando un servicio baja a internal, los Contract activos quedan en estado is_active=False y los créditos en held se liberan (release transactions). Operación scriptable en scripts/decay_service.py (pendiente, BACKLOG flujo M.6).

Deprecation explícita (separada de marketplace_status):

  • Field status: deprecated en frontmatter del template (modela el ciclo técnico interno, no el de marketplace).
  • Convivencia: un servicio puede estar marketplace_status='public' + status='deprecated' durante el grace period — el badge "Deprecated · sunsetting YYYY-MM-DD" se renderiza en card y detail page.
  • Cierre final: marketplace_status baja a internal + is_active=False el día de sunset.

Auto-demotion por inactividad: si un servicio en preview queda >90 días sin promoción, entry trimestral en CHANNEL para que reviewer humano decida demote-to-internal o promote-to-beta. No hay timer automático que demote; sería demasiado agresivo.

5. Reserva de slots futuros

Productos en pipeline pueden reservar slot sin construir nada. La reserva queda en este ADR + en services.yaml opcionalmente con marketplace_status: 'internal' para que aparezca en queries admin pero no en marketplace.

Cochid (reservado · NO implementado)

Reserva trazabilidad-only declarada por Bloque V.3:

  • Dominio público: cochid.cl (DNS gestionado en Cloudflare, zone apex pendiente).
  • Puerto interno en vps-cis: 8290 (en rango 8200–8299 reservado para servicios CIS, ver /srv/projects/a §3).
  • Path canónico: /srv/projects/cis/projects/cochid/ (post-migración, ver STRUCTURE.md Fase 2).
  • Brand tokens: core-style/v6/brands/cochid/ ya existe (logo + tokens — output del Bloque V.2 / ADR-021).
  • Sub-products (cochid-datos, cochid-congreso, cochid-lex, cochid-elecciones) ya tienen brand tokens y entries en services.yaml.

El slot principal cochid (no sub-products) será publicado por el agente del Bloque V.3 cuando aterrice. Hasta entonces:

  • No crear entry en services.yaml para slug: cochid (parent) — los sub-products bastan.
  • No reservar marketplace_status en DB (no existe la fila).
  • DNS cochid.cl queda en zona crítica (ADR-026): cualquier edit/delete del apex requiere FES.

6. Implementación de referencia

Código en cis-platform (Bloque M.2, output 2026-05-03):

  • app/models/service.py — columnas pricing_tier, marketplace_status.
  • app/routes/services.py::_validate_marketplace_transition — enforce skip-forward 409.
  • app/routes/services.py::list_all_services — filter ?status=..., default oculta internal.
  • alembic/versions/c81d713d16c2_marketplace_status_pricing_tier.py — migration aditiva con backfill desde metadata_->>'visibility' y CHECK constraints.
  • tests/test_services.py::TestMarketplaceLifecycle — 12 tests cubren filter + transitions + idempotencia + demotion.

7. Cutoff y grandfathering

Para cada producto promovido a public post-2026-05-01, el checklist completo de beta → public es obligatorio. Productos pre-2026-05-01 ya en public (cis-www, indieweb.cl, isometrico, situacion, librospa, periodismo2, usaia, firmasydocumentos, idea.indieweb.cl) quedan grandfathered pero deben adoptar el checklist al próximo refactor mayor; los items pendientes se documentan como deuda en BACKLOG flujo 10/M. Auditoría manual al 2026-05-01: productos que no pasen → demote a beta con badge.

Consecuencias

  • Positivo: cualquier user externo de indieweb.cl puede confiar que un producto public cumple un mínimo. La etapa preview permite compartir trabajo en curso sin exposición pública. Demotion con grace respeta usuarios externos.
  • Positivo: el campo marketplace_status en DB es la fuente de verdad operacional — fácil de querear, indexar, y filtrar desde admin UI.
  • Positivo: pricing_tier separado de pricing_summary permite filtros ("muestra solo APIs metered") sin parsear texto libre.
  • Negativo: ~30–60 min de checklist por promotion. Documentation drift posible si el checklist no se enforce — mitigado por el script check_marketplace_lifecycle.py (pendiente de implementación) que se corre en CI de cis-platform.
  • Riesgo: un producto puede quedar atorado en preview por meses si nadie completa el checklist. Mitigación: entry trimestral en CHANNEL con productos en preview >90 días → reviewer humano decide demote o promote-or-remove.
  • Migración de datos existentes: la migration c81d713d16c2 backfilla marketplace_status desde metadata_->>'visibility' cuando ese valor existe en el enum. Servicios sin visibility en metadata quedan en internal (default seguro).
  • Compatibilidad: el campo visibility ya existe en metadata_ (CONTRACTS.md fila marketplace metadata) y se mantiene como duplicado redundante hasta que se migre todo a marketplace_status. TODO: deprecation de metadata_.visibility en una v2 del template (post-2026-Q3).
  • Dependencia de ADR-021: el checklist incluye brand tokens en core-style/v6/brands/<slug>/. Esa convención queda bloqueante.
  • Dependencia de ADR-018, ADR-019, ADR-020: el checklist beta → public referencia coverage, logging y OIDC convencionales.
  • Authentik groups: cada <slug>-preview group crea un namespace por producto. Si queremos preview cross-product (early access tester pool global), creamos cis-preview-tester cuando el caso aparezca. Por ahora per-producto.
  • Cochid reservado pero no implementado: sección §5 documenta dominio + puerto + path + brands ya prereservados. La construcción real es output del Bloque V.3, no de éste.

Acciones pendientes (post-merge)

  1. Aplicar migration c81d713d16c2 en cis_platform prod: cd /srv/projects/cds/cis/cis-platform && .venv/bin/alembic upgrade head. Acción humana (Martín).
  2. Auditar servicios public existentes contra checklist de beta → public. Productos que no pasen → demote a beta con badge.
  3. Implementar scripts/check_marketplace_lifecycle.py para CI gate (BACKLOG flujo M.5).
  4. Implementar scripts/decay_service.py para demotion automatizada con cleanup de contracts/credits (BACKLOG flujo M.6).

Referencias

  • Template canónico: /srv/projects/cds/cis/cis-plan/templates/cis-service-template.md v1 (2026-05-03).
  • Migration: /srv/projects/cds/cis/cis-platform/alembic/versions/c81d713d16c2_marketplace_status_pricing_tier.py.
  • Tests: /srv/projects/cds/cis/cis-platform/tests/test_services.py::TestMarketplaceLifecycle.
  • ADR-021: design system governance + brand tokens convention.
  • ADR-018 / ADR-019 / ADR-020: coverage, logging, user-model canon.
  • ADR-026: operations permission model (DNS apex de cochid.cl queda crítico).
  • Bloque V.3 (pendiente): construcción real del slot cochid.