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 migrationc81d713d16c2conmarketplace_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
servicesahora tienepricing_tier VARCHAR(32)ymarketplace_status VARCHAR(32) NOT NULL DEFAULT 'internal'(migrationc81d713d16c2_marketplace_status_pricing_tier.py, NO aplicada en producción al momento de este ADR). - API extendida:
GET /api/v1/services/?status=publicfiltra 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
- (a) Sin lifecycle formal — cada producto se publica cuando esté "listo"
- Pro: máxima velocidad.
-
Contra: estado pre-ADR; produce drift entre lo publicado y lo realmente operativo. Logos ausentes, AGENT-CONTEXT.md desactualizados, contratos sin versión.
-
(b) Lifecycle 4 etapas con checklist por salto ← propuesta
- 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.
-
Contra: overhead de proceso (~30 min de review por salto). Mitigado porque la mayoría de productos solo cruzan cada gate una vez.
-
(c) Solo 2 etapas (internal vs public)
- Pro: simple.
-
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.
-
(d) Etapas continuas con score 0–100 (vs enum discreto)
- Pro: granularidad fina.
- 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.mden el repo del servicio (puerto, secrets keys, gotchas, owner). - Health endpoint
/healthzrespondiendo 200 con JSON{status: "ok"}. - Entry en
cis-platformservices tabla conmarketplace_status='preview'(post-migration). - Authentik group
<slug>-previewcreado (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_tierdeclarado en DB (no NULL):free | metered | flat | custom. -
pricing_summaryprovisional en metadata.
beta → public¶
- Coverage ≥60% (ADR-018).
- Logging structlog JSON canónico (ADR-019).
- OIDC integration via
core/py-common/oidc.pyo equivalente (ADR-020). - Documentación pública (
README.mdcara al mundo si aplica al producto). - Owner declarado en
AGENT-CONTEXT.md(maintainer:field) y en metadata. -
pricing_tierdefinitivo (nocustompara 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, losContractactivos quedan en estadois_active=Falsey los créditos enheldse liberan (releasetransactions). Operación scriptable enscripts/decay_service.py(pendiente, BACKLOG flujo M.6).
Deprecation explícita (separada de marketplace_status):
- Field
status: deprecateden 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_statusbaja ainternal+is_active=Falseel 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, verSTRUCTURE.mdFase 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.yamlparaslug: cochid(parent) — los sub-products bastan. - No reservar
marketplace_statusen DB (no existe la fila). - DNS
cochid.clqueda 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— columnaspricing_tier,marketplace_status.app/routes/services.py::_validate_marketplace_transition— enforce skip-forward 409.app/routes/services.py::list_all_services— filter?status=..., default ocultainternal.alembic/versions/c81d713d16c2_marketplace_status_pricing_tier.py— migration aditiva con backfill desdemetadata_->>'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.clpuede confiar que un productopubliccumple un mínimo. La etapapreviewpermite compartir trabajo en curso sin exposición pública. Demotion con grace respeta usuarios externos. - Positivo: el campo
marketplace_statusen DB es la fuente de verdad operacional — fácil de querear, indexar, y filtrar desde admin UI. - Positivo:
pricing_tierseparado depricing_summarypermite 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 decis-platform. - Riesgo: un producto puede quedar atorado en
previewpor meses si nadie completa el checklist. Mitigación: entry trimestral en CHANNEL con productos enpreview>90 días → reviewer humano decide demote o promote-or-remove. - Migración de datos existentes: la migration
c81d713d16c2backfillamarketplace_statusdesdemetadata_->>'visibility'cuando ese valor existe en el enum. Servicios sinvisibilityen metadata quedan eninternal(default seguro). - Compatibilidad: el campo
visibilityya existe enmetadata_(CONTRACTS.md fila marketplace metadata) y se mantiene como duplicado redundante hasta que se migre todo amarketplace_status. TODO: deprecation demetadata_.visibilityen 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 → publicreferencia coverage, logging y OIDC convencionales. - Authentik groups: cada
<slug>-previewgroup crea un namespace por producto. Si queremos preview cross-product (early access tester pool global), creamoscis-preview-testercuando 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)
- Aplicar migration
c81d713d16c2encis_platformprod:cd /srv/projects/cds/cis/cis-platform && .venv/bin/alembic upgrade head. Acción humana (Martín). - Auditar servicios
publicexistentes contra checklist debeta → public. Productos que no pasen → demote abetacon badge. - Implementar
scripts/check_marketplace_lifecycle.pypara CI gate (BACKLOG flujo M.5). - Implementar
scripts/decay_service.pypara 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.mdv1 (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.clqueda crítico). - Bloque V.3 (pendiente): construcción real del slot cochid.