docker-swarm 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 916ac2da8a8d322e7cbf83d2e9b05d8aa62bd9a4e78c4d31eaef8086811e3e20
4
- data.tar.gz: b1328677dfd900829f21faaf112097833c98594f1c713ddd0c9138a352027f47
3
+ metadata.gz: c4fccd3ff16c561cf1f8563cf2dd518c69fd1a7fc46e9d74b7da804244dd1c4d
4
+ data.tar.gz: c71beba1337b83edde6eb18f424954e2563c35172e195615ed86b2b40153abe5
5
5
  SHA512:
6
- metadata.gz: 5b8bb4bdb2d18c0c6a13d8a3e13662afc529c2f8912ad9b858bf52360e91be4ad40ed123ee4aa3db9549eace28ad5a56cbdc9a0207747903491fa35b5abf93db
7
- data.tar.gz: 5ec96be7621f4797e55e1de283d2a646ecb8b405984c97a4aca85084dd0f20098771407d6dc2083b03378bff62ee86a33b46f30f56018f2355e06a0f3da7f33d
6
+ metadata.gz: 9165d28e2a0def50e674578600642dbaf3aca92b586bb5697590828a3081476139013922f45471dd84d45c376b997bd74bf78e3064d12ab14e3fd9bd046d2743
7
+ data.tar.gz: c2deb17e70f07a51b8e29ac69b9923706e8a57124fd3ddb4d36a30dded13de1fa4024d1a8c9dc1470cb984dd24da5a2a04cb7c6601a4022bcdecb84557516b79
data/CHANGELOG.md ADDED
@@ -0,0 +1,126 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.7.1] — 2026-05-29
6
+
7
+ ### Correcciones
8
+ - `Updatable#update`: captura `Version.Index` **antes** de `assign_attributes` para evitar que un payload con versión desactualizada pise el índice real del nodo y provoque `update out of sequence` en Docker — @Pablo
9
+
10
+ ## [0.7.0] — 2026-05-21
11
+
12
+ ### Breaking changes
13
+ - `Connection#request`: retries automáticos sólo en métodos seguros (GET/HEAD/PUT/DELETE/OPTIONS). POST/PATCH ya **no** reintentan para evitar recursos duplicados ante caída de socket — @Gabriel
14
+ - `Base#assign_attributes`: rama muerta que aceptaba no-Hash ahora levanta `ArgumentError` con mensaje claro — @Gabriel
15
+ - Ruby floor: `>= 3.2.0` (antes `>= 2.7.0`), alineado con `activesupport` 8.1 — @Gabriel
16
+
17
+ ### Correcciones
18
+ - `Connection#request`: clasificación de errores por `is_a?(DockerSwarm::Error)` en vez de `class.name.include?`, evita falsos positivos con clases externas — @Gabriel
19
+ - `LogHelper::SENSITIVE_KEYS`: `data` ahora se matchea con `\b` para no filtrar `metadata`, `database` u otras claves legítimas — @Gabriel
20
+
21
+ ### Documentación
22
+ - Re-estructura completa bajo skills `dev-*` (RFC-001/007/008/009/010): nuevos `docs/glossary/glossary.md` (20 términos) y `docs/behavior/behavior.md` (8 flujos load-bearing con Mermaid). `README.md` minimal-control y `skill/SKILL.md` con frontmatter, contrato resumido, version-lock — @Gabriel
23
+ - Eliminados docs ad-hoc pre-estándar (`docs/{api,configuration,errors,models,testing}.md` y `skill/references/`) — @Gabriel
24
+
25
+ ### Mejoras internas
26
+ - CI: tests unitarios en `bundle exec rspec --tag ~type:integration` para que el build verde sea significativo sin Docker disponible — @Gabriel
27
+ - Gemspec: `metadata` con `changelog_uri`/`bug_tracker_uri`/`documentation_uri`/`rubygems_mfa_required` version-locked; `LICENSE`, `CHANGELOG.md` y `docs/**/*` incluidos en `spec.files` para que viajen con el release — @Gabriel
28
+ - `LICENSE` (MIT) agregado al repo — @Gabriel
29
+ - Removido `debug_docker.rb` del repo (artefacto de desarrollo) — @Gabriel
30
+
31
+ ## [0.6.0] - 2026-04-08
32
+
33
+ ### Nuevas funcionalidades
34
+ - `Service#restart`: reinicia un servicio incrementando `ForceUpdate` en el `TaskTemplate`, equivalente a `docker service update --force` — @Gabriel
35
+
36
+ ## [0.5.4] - 2026-04-07
37
+
38
+ ### Correcciones
39
+ - `Inspectable`: todos los atributos deben usar `send(:attr)` para invocar métodos dinámicos — @Gabriel
40
+
41
+ ## [0.5.3] - 2026-04-07
42
+
43
+ ### Mejoras internas
44
+ - `Inspectable` ahora muestra todos los atributos (ID, Name, CreatedAt, UpdatedAt, Version, Spec) — @Gabriel
45
+
46
+ ## [0.5.2] - 2026-04-07
47
+
48
+ ### Correcciones
49
+ - Fix en `Inspectable`: usar `send(:ID)` en vez de `ID` directamente para evitar `NameError` por interpretación como constante — @Gabriel
50
+
51
+ ## [0.5.1] - 2026-04-06
52
+
53
+ ### Mejoras internas
54
+ - Refactor de logs en `Connection`: cambio de `duration_ms` a `duration_s` y de `status` a `http_status` para alinearse con estándares de observabilidad — @Gabriel
55
+ - Precisión de duración mejorada usando segundos con 4 decimales — @Gabriel
56
+ - Reestructuración de `skills.yml` para soportar variables de entorno en agentes y nuevas skills — @Gabriel
57
+
58
+ ## [0.5.0] - 2026-04-04
59
+
60
+ ### Nuevas funcionalidades
61
+ - Skill de conocimiento empaquetada (`skill/`) para consumo via `skill-manager sync` — @Gabriel
62
+ - Helper de testing documentado (`DockerSwarmHelpers`) con `stub_docker_find` y `stub_docker_list` — @Claude
63
+
64
+ ### Mejoras internas
65
+ - README reescrito con instalación, más ejemplos de uso, y sección Documentación linkeando a `docs/` — @Claude
66
+ - `docs/testing.md` reescrito: mockeo de CRUD, errores, helper reutilizable — @Claude
67
+ - `docs/errors.md` completado con errores faltantes (NotAcceptable 406, RequestTimeout 408, BadGateway 502) — @Claude
68
+ - Cross-references bidireccionales entre todos los docs — @Claude
69
+ - Centralización de logging en `LogHelper` con masking de campo `Data` para Secret/Config — @Gabriel
70
+ - Gemspec actualizado para empaquetar `skill/**/*` en el `.gem` — @Claude
71
+
72
+ ## [0.4.0] - 2026-03-31
73
+
74
+ ### Nuevas funcionalidades
75
+ - Dependabot configurado para actualizaciones semanales de gemas y mensuales de GitHub Actions — @Gabriel
76
+ - Integración de `rubocop-rails-omakase` y verificación RuboCop en CI — @Gabriel
77
+ - Tests de infraestructura robustos para `Connection`, `Configuration` y Middlewares — @Gabriel
78
+ - Documentación YARD en todos los modelos y componentes core — @Gabriel
79
+ - Concern `Inspectable` para inspección legible en consola (ID, Name, Image) — @Gabriel
80
+
81
+ ### Mejoras internas
82
+ - Memoización de accessors en `Base` para optimizar procesamiento de recursos — @Gabriel
83
+ - Refactor de logs a `Concerns::Loggable` para DRY en Service, Task y Container — @Gabriel
84
+ - Branch default renombrado de `master` a `main` — @Gabriel
85
+
86
+ ### Correcciones
87
+ - Casting automático de timeouts y retries para prevenir `TypeError` con ENV variables — @Gabriel
88
+ - Orden de carga interno reorganizado para resolver issues de inicialización — @Gabriel
89
+
90
+ ## [0.3.0] - 2026-03-31
91
+
92
+ ### Nuevas funcionalidades
93
+ - Timeouts y retries configurables: `read_timeout`, `write_timeout`, `connect_timeout`, `max_retries` — @Gabriel
94
+ - `RequestEncoder` soporta `application/x-www-form-urlencoded` y `multipart/form-data` — @Gabriel
95
+ - `ResponseJSONParser` aplica `with_indifferent_access` recursivo en Arrays — @Gabriel
96
+
97
+ ## [0.2.0] - 2026-03-31
98
+
99
+ ### Nuevas funcionalidades
100
+ - Logging estructurado KV (`component`, `event`, `source`, `duration_ms`) estándar Wispro — @Gabriel
101
+ - Reloj monotónico para medición precisa de duración de requests — @Gabriel
102
+ - Masking automático de claves sensibles en logs — @Gabriel
103
+ - Soporte para Unix sockets y HTTP/TCP via `socket_path` — @Gabriel
104
+ - `Network.update` implementado — @Gabriel
105
+ - `log_level` configurable en runtime — @Gabriel
106
+ - Error `TooManyRequests` (429) — @Gabriel
107
+ - Tests de integración para Service, Node, Task, Swarm, System, Image y Container — @Gabriel
108
+
109
+ ### Mejoras internas
110
+ - Modelos movidos a `lib/docker_swarm/models/` — @Gabriel
111
+ - `Base.all` normalizado para respuestas envueltas (`root_key`) — @Gabriel
112
+ - Método `logs` estandarizado a nivel de instancia — @Gabriel
113
+ - `Swarm` y `System` heredan de `Base` con atributos dinámicos — @Gabriel
114
+ - Jerarquía de errores reestructurada bajo `DockerSwarm::Error` con aliases — @Gabriel
115
+
116
+ ### Correcciones
117
+ - Excon ya no envuelve excepciones de negocio en `Excon::Error::Socket` — @Gabriel
118
+ - `Volume.all` corregido para la respuesta envuelta de Docker — @Gabriel
119
+ - `Gateway_Timeout` renombrado a `GatewayTimeout` — @Gabriel
120
+ - Entry point `lib/docker-swarm.rb` agregado para Bundler/Rails — @Gabriel
121
+ - Validaciones de `Service` relajadas para flexibilidad en tests — @Gabriel
122
+
123
+ ---
124
+
125
+ ## [0.1.0] - Early 2026
126
+ - Release inicial con ORM básico para Services, Networks y Volumes — @Gabriel
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gabriel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,145 +1,66 @@
1
- # Docker Swarm Gem
1
+ # docker-swarm
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/docker-swarm.svg)](https://badge.fury.io/rb/docker-swarm)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
5
 
6
- `docker-swarm` es un ORM ligero y cliente API robusto para interactuar con Docker Swarm desde Ruby. Diseñado para sentirse familiar a los desarrolladores de Rails, utiliza `ActiveModel` para ofrecer una interfaz limpia y potente con estándares de observabilidad de Wispro.
6
+ ## Propósito
7
7
 
8
- ## Instalacion
8
+ ORM ligero y cliente API para Docker Swarm en Ruby. Expone las primitivas del Docker Engine (Service, Node, Task, Container, Network, Volume, Config, Secret, Swarm, System, Image) como modelos `ActiveModel` con CRUD, validaciones y logs estructurados KV. Excon directo sobre el socket Unix (default) o TCP.
9
9
 
10
- Agrega la gema a tu `Gemfile`:
10
+ ## Setup
11
11
 
12
12
  ```ruby
13
+ # Gemfile
13
14
  gem 'docker-swarm', '~> 0.6'
14
15
  ```
15
16
 
16
- Y ejecuta:
17
-
18
17
  ```bash
19
18
  bundle install
20
19
  ```
21
20
 
22
- ## Quick Start
21
+ Quick start:
23
22
 
24
23
  ```ruby
25
24
  require 'docker_swarm'
26
25
 
27
- # Configurar (opcional, usa defaults razonables)
28
26
  DockerSwarm.configure do |config|
29
- config.socket_path = "unix:///var/run/docker.sock"
27
+ config.socket_path = "unix:///var/run/docker.sock" # default
30
28
  config.log_level = Logger::INFO
31
- config.max_retries = 3
32
29
  end
33
30
 
34
- # Verificar conectividad
35
- DockerSwarm::System.up # => "OK"
36
-
37
- # Listar servicios
38
- services = DockerSwarm::Service.all
39
- services.each { |s| puts "#{s.ID}: #{s.Spec[:Name]}" }
40
- ```
41
-
42
- ## Uso
43
-
44
- ### Crear un servicio
45
-
46
- ```ruby
47
- service = DockerSwarm::Service.create(
48
- Name: "my-webapp",
49
- TaskTemplate: {
50
- ContainerSpec: { Image: "nginx:latest" }
51
- }
52
- )
53
- puts service.ID
54
- ```
55
-
56
- ### Actualizar (maneja Version.Index automaticamente)
57
-
58
- ```ruby
59
- service = DockerSwarm::Service.find("svc-id")
60
- service.update(Mode: { Replicated: { Replicas: 3 } })
61
- ```
62
-
63
- ### Eliminar
64
-
65
- ```ruby
66
- service.destroy # graceful: retorna nil si ya no existe
67
- ```
68
-
69
- ### Reiniciar
70
-
71
- ```ruby
72
- service.restart # equivalente a docker service update --force
73
- ```
74
-
75
- ### Logs
76
-
77
- ```ruby
78
- puts service.logs(stdout: 1, stderr: 1)
31
+ DockerSwarm::System.up # => "OK"
32
+ DockerSwarm::Service.all # => [#<Service ...>, ...]
33
+ DockerSwarm::Service.find("svc-id") # => nil si no existe
79
34
  ```
80
35
 
81
- ### Filtros
36
+ Para el contrato completo de la gema (símbolos públicos, gotchas, integración) ver [`skill/SKILL.md`](skill/SKILL.md).
82
37
 
83
- ```ruby
84
- DockerSwarm::Service.all(label: ["env=production"])
85
- DockerSwarm::Node.all(role: ["manager"])
86
- DockerSwarm::Container.all(status: ["running"])
87
- ```
38
+ ## Índice de artefactos
88
39
 
89
- ### Sistema y Cluster
40
+ Documentación normada (RFC-001) por capa:
90
41
 
91
- ```ruby
92
- DockerSwarm::System.info # informacion del daemon
93
- DockerSwarm::System.version # version de Docker
94
- DockerSwarm::System.df # uso de disco
95
- DockerSwarm::Swarm.show # informacion del cluster
96
- ```
42
+ | Capa | Artefacto | Estado |
43
+ |---|---|---|
44
+ | Datos | — | `n/a` (gema sin DB) |
45
+ | Glosario | [`docs/glossary/glossary.md`](docs/glossary/glossary.md) | F1 completo |
46
+ | Comportamiento | [`docs/behavior/behavior.md`](docs/behavior/behavior.md) | F1 backfill on-demand (8 flujos) |
47
+ | API (operaciones) | — | F2 pendiente; contrato resumido inline en `skill/SKILL.md` |
48
+ | Interfaz | — | F2 pendiente; contrato resumido inline en `skill/SKILL.md` |
49
+ | Topología | — | F2 pendiente |
50
+ | Eventos | — | `n/a` (la gema no emite eventos) |
97
51
 
98
- ### Manejo de errores
52
+ `n/a` = no aplica al tipo de repo. F2 pendiente = capa declarada en la RFC pero todavía no implementada en `dev-structure`; el contenido relevante vive transitoriamente en `skill/SKILL.md` (RFC-008 §2 coexistencia transitoria).
99
53
 
100
- ```ruby
101
- begin
102
- DockerSwarm::Service.create(Name: "web", TaskTemplate: { ... })
103
- rescue DockerSwarm::Conflict => e
104
- puts "Nombre duplicado"
105
- rescue DockerSwarm::Communication => e
106
- puts "Docker inalcanzable: #{e.message}"
107
- end
108
- ```
109
-
110
- ## Configuracion
111
-
112
- ```ruby
113
- DockerSwarm.configure do |config|
114
- config.socket_path = "unix:///var/run/docker.sock" # o http://host:port
115
- config.logger = Logger.new($stdout)
116
- config.log_level = Logger::INFO
117
- config.read_timeout = 60 # segundos
118
- config.write_timeout = 60
119
- config.connect_timeout = 10
120
- config.max_retries = 3
121
- end
122
- ```
123
-
124
- Ver [Guia de Configuracion](docs/configuration.md) para opciones avanzadas, Rails integration y observabilidad.
125
-
126
- ## Documentacion
127
-
128
- - [Modelos (ORM)](docs/models.md) — Todos los modelos, concerns y ciclo de vida
129
- - [Configuracion](docs/configuration.md) — Opciones, observabilidad y seguridad
130
- - [Manejo de Errores](docs/errors.md) — Jerarquia de excepciones y uso
131
- - [Cliente API](docs/api.md) — Acceso de bajo nivel y middlewares
132
- - [Testing](docs/testing.md) — Estrategias de mocking para tus tests
133
-
134
- ## Contribuir
135
-
136
- Las contribuciones son bienvenidas. Por favor, lee `CLAUDE.md` para las guias de desarrollo.
54
+ ## Desarrollo
137
55
 
138
56
  ```bash
139
- bundle exec rspec # tests
140
- bundle exec rubocop -a # linting
57
+ bundle exec rspec --tag ~type:integration # unit suite
58
+ bundle exec rspec # incluye integration (requiere Docker socket)
59
+ bundle exec rubocop -a # lint
141
60
  ```
142
61
 
62
+ Release: `/gem-release` (publica a RubyGems via GitHub Action al tag `v*`).
63
+
143
64
  ## Licencia
144
65
 
145
- Este proyecto esta bajo la licencia MIT.
66
+ MIT ver [LICENSE](LICENSE).
@@ -0,0 +1,243 @@
1
+ # Comportamiento — docker-swarm
2
+
3
+ > meta: artefacto · RFC-007 · generado dev-enrich · anclado a `a4e3129` · cobertura: backfill on-demand inicial (8 flujos load-bearing)
4
+
5
+ ## 1. Resumen
6
+
7
+ Flujos de ejecución load-bearing de `docker-swarm`: cómo se materializan en runtime las operaciones CRUD, el control de concurrencia optimista de Docker, la política de retries post-fix correctness, y el mapeo de errores. Render Mermaid sequenceDiagram/flowchart por flujo.
8
+
9
+ ## 2. Cobertura declarada
10
+
11
+ ### Documentados (8)
12
+
13
+ 1. `Service.create` + reload
14
+ 2. `Service.update` con `Version.Index`
15
+ 3. `Service.restart` (force update)
16
+ 4. `Model.destroy` (graceful 404)
17
+ 5. Política de retries por método HTTP
18
+ 6. Error mapping (HTTP status → excepción tipada)
19
+ 7. `Container.start` / `Container.stop`
20
+ 8. `Loggable#logs` streaming
21
+
22
+ ### No documentados (ausencia ≠ inexistencia, RFC-007)
23
+
24
+ - Reconexión / reapertura de socket Unix (Excon nativo, fuera de nuestra superficie).
25
+ - Flujo de configuración / boot (`DockerSwarm.configure`) — trivial, sin secuencia de interés.
26
+ - Pull de imágenes con autenticación de registry (`X-Registry-Auth`) — **no implementado** en la gema (gap conocido).
27
+ - `Container.create` — **no implementado** en F1 (intencional, ver glossary).
28
+
29
+ ## 3. Flujos
30
+
31
+ ### 3.1 `Service.create` + reload
32
+
33
+ Crear un Service implica un POST seguido de un reload automático para hidratar la instancia con la respuesta completa de Docker (el endpoint `create` sólo devuelve `ID`).
34
+
35
+ ```mermaid
36
+ sequenceDiagram
37
+ actor Caller
38
+ participant Service as DockerSwarm::Service
39
+ participant Api as DockerSwarm::Api
40
+ participant Conn as Connection
41
+ participant Docker as Docker Engine
42
+
43
+ Caller->>Service: Service.create(Name:, TaskTemplate:)
44
+ Service->>Service: new(attrs) → valid?
45
+ Service->>Api: request(:create, payload: payload_for_docker)
46
+ Api->>Conn: request(method: :post, path: services/create, body:)
47
+ Conn->>Docker: POST /services/create
48
+ Docker-->>Conn: 201 { ID: "abc" }
49
+ Conn-->>Service: { ID: "abc" }
50
+ Service->>Service: self.ID = "abc"
51
+ Service->>Api: request(:show, arguments: { id: "abc" })
52
+ Api->>Conn: request(method: :get, path: services/abc)
53
+ Conn->>Docker: GET /services/abc
54
+ Docker-->>Conn: 200 { ID, Spec, Version, ... }
55
+ Conn-->>Service: full payload
56
+ Service-->>Caller: hydrated Service instance
57
+ ```
58
+
59
+ **Notas load-bearing:**
60
+ - `valid?` corre antes del POST. Si falla, `save` retorna `false` sin tocar el API.
61
+ - El POST no se reintenta automáticamente: ver §3.5 (retry policy).
62
+ - El reload usa `find` interno; si Docker devuelve 404 entre create+show (caso extremo), el reload no se realiza y la instancia queda sólo con `ID`.
63
+
64
+ ### 3.2 `Service.update` con `Version.Index`
65
+
66
+ Updates atómicos sobre Service/Node/Network requieren enviar el `Version.Index` actual como query param. Sin él, Docker rechaza con 500. La gema lo extrae automáticamente de `self.Version["Index"]`.
67
+
68
+ ```mermaid
69
+ sequenceDiagram
70
+ actor Caller
71
+ participant Model as Service/Node/Network
72
+ participant Api
73
+ participant Docker as Docker Engine
74
+
75
+ Caller->>Model: model.update(Mode: { Replicated: { Replicas: 3 } })
76
+ Model->>Model: assign_attributes(new_attrs) -- Spec deep_merge si aplica
77
+ Model->>Model: valid?
78
+ Model->>Api: request(:update, id:, query: { version: Version.Index }, payload:)
79
+ Api->>Docker: POST /services/abc/update?version=42
80
+ alt version coincide
81
+ Docker-->>Api: 200 OK
82
+ Api-->>Model: success
83
+ Model-->>Caller: true
84
+ else version stale
85
+ Docker-->>Api: 500 update out of sequence
86
+ Api->>Api: ErrorHandler → raise InternalServerError
87
+ Api-->>Caller: DockerSwarm::Error::InternalServerError
88
+ end
89
+ ```
90
+
91
+ **Notas load-bearing:**
92
+ - `assign_attributes` muta el estado local **antes** de validar/enviar. Si el update falla, la instancia queda mutada (gotcha conocido, ver SKILL.md).
93
+ - `Spec` se mergea con `deep_merge`; otros atributos top-level se asignan directo.
94
+ - El update **no** hace reload automático (a diferencia de `create`). El caller llama `reload` si necesita el `Version.Index` nuevo.
95
+
96
+ ### 3.3 `Service.restart` (force update)
97
+
98
+ Reinicio sin downtime equivalente a `docker service update --force`: incrementa `ForceUpdate` del `TaskTemplate` para que Docker recree todas las tasks.
99
+
100
+ ```mermaid
101
+ flowchart LR
102
+ A["service.restart"] --> B["lee Spec.TaskTemplate.ForceUpdate"]
103
+ B --> C["current = ForceUpdate o 0 si nil"]
104
+ C --> D["update Spec: TaskTemplate: ForceUpdate: current+1"]
105
+ D --> E["POST /services/id/update?version=X"]
106
+ E --> F["Docker recrea tasks"]
107
+ ```
108
+
109
+ **Notas load-bearing:**
110
+ - Usa el flujo §3.2 internamente — hereda Version.Index, retry-policy y error-mapping.
111
+ - Si `Spec` o `TaskTemplate` no existen aún (instancia nueva sin reload), `current = 0`.
112
+
113
+ ### 3.4 `Model.destroy` (graceful 404)
114
+
115
+ DELETE con tolerancia a 404: si el recurso ya fue eliminado, `destroy` retorna `nil` en vez de raise.
116
+
117
+ ```mermaid
118
+ sequenceDiagram
119
+ actor Caller
120
+ participant Model
121
+ participant Api
122
+ participant Docker
123
+
124
+ Caller->>Model: model.destroy
125
+ Model->>Api: request(:destroy, id:)
126
+ Api->>Docker: DELETE /services/abc
127
+ alt recurso existe
128
+ Docker-->>Api: 200 OK
129
+ Api-->>Model: true
130
+ Model-->>Caller: true
131
+ else 404 (ya eliminado)
132
+ Docker-->>Api: 404 Not Found
133
+ Api->>Api: ErrorHandler → raise NotFound
134
+ Model->>Model: rescue Errors::NotFound
135
+ Model-->>Caller: nil
136
+ end
137
+ ```
138
+
139
+ **Notas load-bearing:**
140
+ - Tanto `Model.destroy(id)` (class method) como `model.destroy` (instance) capturan `NotFound`.
141
+ - 409 (`Conflict` — recurso en uso) **no** se captura; el caller debe manejarlo si aplica.
142
+
143
+ ### 3.5 Política de retries por método HTTP
144
+
145
+ Post-fix correctness (PR #3): los retries automáticos sólo aplican a métodos seguros. POST/PATCH no reintentan para evitar duplicados.
146
+
147
+ ```mermaid
148
+ flowchart TD
149
+ Req[Connection#request<br/>method, path, body] --> Class{IDEMPOTENT_METHODS<br/>incluye method?}
150
+ Class -- get/head/put/delete/options --> Safe[idempotent: true<br/>retries: max_retries]
151
+ Class -- post/patch/otro --> Write[idempotent: false<br/>retries: 0]
152
+ Safe --> Excon[Excon.request<br/>retry on Socket/Timeout]
153
+ Write --> ExconNoRetry[Excon.request<br/>sin retry]
154
+ ```
155
+
156
+ **Notas load-bearing:**
157
+ - Si el socket se cierra durante la lectura de la respuesta de un `services/create`, **no** se replica — la instancia queda en error y el caller decide.
158
+ - `max_retries` (default 3) sólo afecta lecturas/idempotentes.
159
+
160
+ ### 3.6 Error mapping (status → excepción)
161
+
162
+ `ErrorHandler` interpreta status HTTP 4xx/5xx y emite excepción tipada de la jerarquía `DockerSwarm::Error`.
163
+
164
+ ```mermaid
165
+ flowchart TD
166
+ Resp[Response status, body] --> Range{status range}
167
+ Range -- 200-299 --> Pass[pass to next middleware]
168
+ Range -- 4xx/5xx --> Log[log business_error<br/>KV format]
169
+ Log --> Map{status}
170
+ Map -- 400 --> BadRequest
171
+ Map -- 401 --> Unauthorized
172
+ Map -- 403 --> Forbidden
173
+ Map -- 404 --> NotFound
174
+ Map -- 406 --> NotAcceptable
175
+ Map -- 408 --> RequestTimeout
176
+ Map -- 409 --> Conflict
177
+ Map -- 422 --> UnprocessableEntity
178
+ Map -- 429 --> TooManyRequests
179
+ Map -- 500 --> InternalServerError
180
+ Map -- 502 --> BadGateway
181
+ Map -- 503 --> ServiceUnavailable
182
+ Map -- 504 --> GatewayTimeout
183
+ Map -- otro --> GenericError[Error HTTP N]
184
+ ```
185
+
186
+ **Notas load-bearing:**
187
+ - Antes de raise, `ErrorHandler` emite un evento `business_error` en formato KV con `status`, `message`, `method`, `path`. Permite diagnosticar sin abrir backtrace.
188
+ - Errores de red (no HTTP) se mapean a `DockerSwarm::Error::Communication` en `Connection#request`.
189
+ - El mensaje de la excepción viene del body parseado: prefiere `body["message"]`, luego `body["error"]`, luego el body completo.
190
+
191
+ ### 3.7 `Container.start` / `Container.stop`
192
+
193
+ Operaciones específicas sin payload (POST a endpoint dedicado).
194
+
195
+ ```mermaid
196
+ sequenceDiagram
197
+ actor Caller
198
+ participant Container as DockerSwarm::Container
199
+ participant Api
200
+ participant Docker
201
+
202
+ Caller->>Container: container.start
203
+ Container->>Api: request(:start, id:)
204
+ Api->>Docker: POST /containers/abc/start
205
+ Docker-->>Api: 204 No Content
206
+ Api-->>Container: ""
207
+ Container-->>Caller: true
208
+ ```
209
+
210
+ Mismo patrón para `stop`. POST sin body → no se reintenta automáticamente (§3.5).
211
+
212
+ ### 3.8 `Loggable#logs` streaming
213
+
214
+ Obtención de logs raw para Service/Task/Container.
215
+
216
+ ```mermaid
217
+ sequenceDiagram
218
+ actor Caller
219
+ participant Model as Service/Task/Container
220
+ participant Api
221
+ participant Docker
222
+
223
+ Caller->>Model: model.logs(stdout: 1, stderr: 1, follow: 0)
224
+ Model->>Api: request(:logs, id:, query: { stdout:, stderr:, follow: })
225
+ Api->>Docker: GET /services/abc/logs?stdout=1&stderr=1
226
+ Docker-->>Api: 200 raw stream (text/plain)
227
+ Api-->>Model: raw body
228
+ Model-->>Caller: String
229
+ ```
230
+
231
+ **Notas load-bearing:**
232
+ - El body se devuelve sin parseo (no es JSON; `ResponseJSONParser` lo respeta porque Content-Type no es `application/json`).
233
+ - `follow: 1` mantiene la conexión abierta — el caller debe manejar el stream/timeout.
234
+
235
+ ## 4. Cobertura y fronteras
236
+
237
+ - **Cobertura inicial (RFC-007 backfill on-demand):** 8 flujos load-bearing documentados. Esta gema es chica; el backfill completo es factible y se hace ahora.
238
+ - **Frontera con glosario:** términos (Service, Spec, Version.Index, etc.) viven en [`docs/glossary/glossary.md`](../glossary/glossary.md). Esta capa documenta secuencias, no significado.
239
+ - **Frontera con configuración:** `DockerSwarm.configure` es boot, no flujo de negocio. No se diagrama.
240
+ - **No localizable / fuera de alcance:**
241
+ - Lógica interna de Excon (retry timing, socket pool) — vive en Excon, no se inventa diagrama.
242
+ - Flujo de auth registry para `Image.create` — la gema **no implementa** `X-Registry-Auth` (gap, no flujo a documentar).
243
+ - **Cadencia incremental a partir de acá:** sólo se diagrama un flujo cuando un PR lo toca o agrega. No barrido retroactivo de legacy.