Deploy + Secrets — la convención per-producto
Cada producto tiene su propio plan de deploy y su propio catálogo de secretos. AWaC formaliza ambos como assets versionados que viven en <product-org>/agent-stack/. Los flujos (cómo se ejecuta cada deploy, cómo se resuelven los secretos) son universales y viven en <transversal-org>/agent-stack-core + topical stacks.
Introducido en CLI v0.6.0 (schemas) + v0.7.0 (comandos
wsp deployywsp secrets check).
Modelo de dos archivos por producto
Section titled “Modelo de dos archivos por producto”<product>/agent-stack/├── awac.yml ← stack repo + repos del producto + module_convention├── deploy.yml ← componentes deployables + targets + flujos pre/post└── devvault.yml ← catálogo de secretos (NUNCA vault_path local)Schemas formales: wsp schema deploy y wsp schema devvault.
deploy.yml (schema deploy/1)
Section titled “deploy.yml (schema deploy/1)”Declara qué se deploya, en qué orden, contra qué targets, y qué pre-steps + aprobaciones humanas son obligatorios.
Estructura
Section titled “Estructura”schema: deploy/1product: <slug> # debe coincidir con awac.yml#productdescription: <texto opcional>
components: - name: <stable_id> # snake_case o kebab-case target: <target_type> # ver tabla abajo repo: <org>/<name> # opcional (para componentes cross-repo) requires_human_approval: true # default true pre_steps: # workflow IDs que deben PASAR antes - run_tests_local - terraform_plan promote_after_pass: # opcional, para flujos two-step - target_repo: <org>/<name> target_branch: <branch> require_pass_on: <target> # solo promueve si ese target acka rollback_window_minutes: 30 # opcional <target>: # sub-bloque target-específico ...Targets soportados
Section titled “Targets soportados”| Target | Descripción | Topical workflow |
|---|---|---|
odoo_sh | Odoo SaaS hosting (push GH → Odoo.SH watches branch). | erp-partners/agent-stack/workflows/deploy_to_odoo_sh.md |
aws_ecs | ECS Fargate / EC2-backed. | getGanemo/agent-stack-aws/workflows/deploy_to_aws_ecs.md |
aws_lambda | Lambda functions. | getGanemo/agent-stack-aws/workflows/deploy_to_aws_lambda.md |
aws_ec2_ssm | EC2 con SSH cerrado, deploy vía SSM. | <transversal-org>/agent-stack-aws/workflows/deploy_to_aws_ec2_ssm.md |
cloudflare_pages | Cloudflare Pages static + workers. | <transversal-org>/agent-stack-cloudflare/workflows/deploy_to_cloudflare_pages.md |
cloudflare_workers | Cloudflare Workers serverless. | <transversal-org>/agent-stack-cloudflare/workflows/deploy_to_cloudflare_workers.md |
github_pages | GitHub Pages. | <transversal-org>/agent-stack-core/workflows/deploy_to_github_pages.md |
manual | Pasos no automatizados (último recurso, documentar en pre_steps). | n/a |
Cómo se ejecuta
Section titled “Cómo se ejecuta”El workflow router deploy_product (en <transversal-org>/agent-stack-core/workflows/):
- Lee
<product-org>/agent-stack/deploy.yml. - Para cada componente: corre
pre_stepsen orden. Cualquier fallo → abort. - Si
requires_human_approval: true: imprime el plan, PARA, espera ack explícito por componente. - Delega al
deploy_to_<target>.mdtopical workflow. - Tras success del target, ejecuta
promote_after_pass(si declararequire_pass_ony el target ackó). - Honra
rollback_window_minutespost-deploy. - Reporta resultado y registra entrada en
<product-org>/project_management/...progreso.md.
El CLI no ejecuta el deploy — solo planifica. La ejecución es workflow-driven (ADR 009).
El flujo Odoo en detalle
Section titled “El flujo Odoo en detalle”deploy_to_odoo_sh codifica el patrón completo:
| Step | Acción |
|---|---|
| 0 | Pre-condition: run_odoo_tests_docker_wsl PASS (mandatorio). |
| 1 | Lee la sección del componente del deploy.yml. |
| 2 | Ack humano explícito — un “ok” anterior en el chat NO cuenta. |
| 3 | Resuelve dependencias recursivas (__manifest__.py#depends). |
| 4 | Push al repo Odoo.SH-watched (staging). |
| 5 | Recupera token GH (Windows Credential Manager). |
| 6 | Polling del build status vía GH commit statuses + browser fallback. |
| 7 | Recupera instance_url via browser y actualiza deploy.yml. |
| 8 | Verifica ir.logging — entries W/E/C en módulos custom = BLOCK. |
| 9 | En falla real: lee logs, fix, restart desde Step 3. |
| 10 | Promote a erp-partners/<module>:<19.0> solo si Step 8 limpió. Fast-forward only — refuse force-push. |
| 11 | Reporta SUCCESS/FAILURE al router. |
Comando CLI
Section titled “Comando CLI”wsp deploy <product> [--component <name>] [--json]Plan-only: imprime los componentes resueltos, sus targets, pre_steps, ack requirement, promotions. Validación schema antes de imprimir. Para ejecutar de verdad → invocar el workflow router.
devvault.yml (schema devvault/1)
Section titled “devvault.yml (schema devvault/1)”Declara los secretos que el producto necesita, mapeados de nombre lógico a path relativo dentro del vault local del developer.
Modelo two-layer
Section titled “Modelo two-layer”| Layer | Vive en | Per | Versionado |
|---|---|---|---|
| Catálogo (logical → relative path) | <product-org>/agent-stack/devvault.yml | producto | ✅ |
| Vault path local | ~/.devvault/.config.yml | máquina | ❌ |
| Secret values | ~/.devvault/<vault_path>/<relative-path> | máquina | ❌ |
Esta separación es la decisión central (ADR 010): el catálogo es per-producto y compartido entre devs; el path local NUNCA va a un repo (cada developer mantiene su propio ~/.devvault/).
Estructura del catálogo
Section titled “Estructura del catálogo”schema: devvault/1product: <slug>description: <opcional>
secrets: <logical_name>: <relative_path> aws: aws/<product>.yml cloudflare: providers/cloudflare.yml odoo: odoo/general.yml github_app: github/<product>-app.yml github_pem: github/<product>-app.pemEl schema prohíbe explícitamente la clave vault_path dentro del catálogo — meterla ahí es el error más común y el motivo por el que el modelo se separó en dos.
Setup per-machine (una sola vez)
Section titled “Setup per-machine (una sola vez)”vault_path: "/absolute/path/to/your/devvault"Los secret values mismos los popula el developer desde su password manager (1Password, Bitwarden, etc.). Eso queda fuera del scope de AWaC.
Cómo se lee un secreto
Section titled “Cómo se lee un secreto”Patrón canónico (codificado en la rule use_devvault.md):
import yamlfrom pathlib import Path
# 1. Path local del vaultconfig = yaml.safe_load((Path.home() / ".devvault" / ".config.yml").read_text())vault_path = Path(config["vault_path"])
# 2. Catálogo del productocatalog = yaml.safe_load(Path("<product-org>/agent-stack/devvault.yml").read_text())
# 3. Resolver y leersecret_path = vault_path / catalog["secrets"]["aws"]aws_secrets = yaml.safe_load(secret_path.read_text())Comando CLI
Section titled “Comando CLI”wsp secrets check <product> [--json]Read-only. Reporta por entrada [ok] / [missing] / [unreadable]. Nunca abre ni imprime el contenido. Exit 0 si todos OK; 1 si alguno falta.
wsp doctor también incluye un check devvault_config que verifica que ~/.devvault/.config.yml exista y vault_path resuelva.
Ejemplos sintéticos
Section titled “Ejemplos sintéticos”Producto con orchestrator AWS + módulo Odoo
Section titled “Producto con orchestrator AWS + módulo Odoo”# <product-org>/agent-stack/deploy.ymlschema: deploy/1product: my-productcomponents: - name: orchestrator target: aws_ec2_ssm repo: my-product/orchestrator requires_human_approval: true pre_steps: [run_tests_local] rollback_window_minutes: 30
- name: my_product_portal_module target: odoo_sh requires_human_approval: true odoo_sh: project: my-org-my-product-portal branch: main modules: [my_product_portal] pre_steps: [run_odoo_tests_docker_wsl] promote_after_pass: - target_repo: erp-partners/my_product_portal target_branch: "19.0" require_pass_on: odoo_sh# <product-org>/agent-stack/devvault.ymlschema: devvault/1product: my-productsecrets: aws_account: aws/account.yml aws: aws/my-product.yml cloudflare: providers/cloudflare.yml odoo: odoo/general.yml github_app: github/my-product-app.yml github_pem: github/my-product-app.pemProducto con varios módulos Odoo + API en ECS
Section titled “Producto con varios módulos Odoo + API en ECS”Combina varios módulos Odoo (<product>_saas + <product>_core) con un servicio backend (target aws_ecs).
Producto con frontend en CF Pages
Section titled “Producto con frontend en CF Pages”api (aws_ecs) + web (cloudflare_pages, sin requires_human_approval porque CF Pages auto-publica preview por push) + deploy_orchestration (manual, Docker+Nginx).
Anti-patterns
Section titled “Anti-patterns”- Hardcodear secretos en cualquier archivo del repo, incluso “placeholder” tipo
API_KEY="changeme". Usar<placeholder>en config files committeados y resolver del vault en runtime. - Meter
vault_pathen<product>/agent-stack/devvault.yml. El schema lo rechaza; el dev path es per-machine. - Saltar
pre_steps“porque tarda”. Lospre_stepsexisten porque el alternative (deployar código roto) es peor. - Saltar
requires_human_approvalporque “el usuario dijo deployá hace 5 minutos”. El ack es per-componente, per-deploy. promote_after_passejecutado antes que el target acka. Esa es exactamente la falla querequire_pass_onpreviene.- Force-push en promote. La rama canónica es producción truth — reconciliar, no overwriting.
- Inventar un deploy procedure porque el spec no existe. La acción correcta es invocar la skill
create_deploy_spec, no improvisar.
Cross-references
Section titled “Cross-references”- Schemas:
wsp schema deploy/wsp schema devvault. - Skill
create_deploy_spec(publicada en<transversal-org>/agent-stack-core/skills/). - Rules
use_deploy_spec,use_devvault(publicadas en<transversal-org>/agent-stack-core/rules/). - Workflow router
deploy_product(publicado en<transversal-org>/agent-stack-core/workflows/). - Topical (Odoo): workflow
deploy_to_odoo_sh(publicado enerp-partners/agent-stack/workflows/). - ADRs: 009 (deploy.yml separado de awac.yml), 010 (devvault two-layer model).
- Comandos:
wsp deploy,wsp secrets check,wsp doctor. Ver05-cli-reference.md.