disensor 0.6.0__tar.gz → 0.6.2__tar.gz

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.
Files changed (40) hide show
  1. {disensor-0.6.0/src/disensor.egg-info → disensor-0.6.2}/PKG-INFO +6 -6
  2. {disensor-0.6.0 → disensor-0.6.2}/README.md +5 -5
  3. {disensor-0.6.0 → disensor-0.6.2}/pyproject.toml +1 -1
  4. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/__init__.py +1 -1
  5. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/cli.py +12 -1
  6. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/residue.schema.json +1 -1
  7. {disensor-0.6.0 → disensor-0.6.2/src/disensor.egg-info}/PKG-INFO +6 -6
  8. disensor-0.6.2/tests/test_docs.py +167 -0
  9. disensor-0.6.0/tests/test_docs.py +0 -27
  10. {disensor-0.6.0 → disensor-0.6.2}/LICENSE +0 -0
  11. {disensor-0.6.0 → disensor-0.6.2}/setup.cfg +0 -0
  12. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/GUIDE.md +0 -0
  13. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/__main__.py +0 -0
  14. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/brief.py +0 -0
  15. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/gate.py +0 -0
  16. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/gitctx.py +0 -0
  17. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/guide.py +0 -0
  18. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/init.py +0 -0
  19. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/prompts/_common.md +0 -0
  20. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/prompts/architecture.md +0 -0
  21. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/prompts/diff.md +0 -0
  22. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/prompts/plan.md +0 -0
  23. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/render.py +0 -0
  24. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/rules.py +0 -0
  25. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/scope.py +0 -0
  26. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/template.py +0 -0
  27. {disensor-0.6.0 → disensor-0.6.2}/src/disensor/vectors.py +0 -0
  28. {disensor-0.6.0 → disensor-0.6.2}/src/disensor.egg-info/SOURCES.txt +0 -0
  29. {disensor-0.6.0 → disensor-0.6.2}/src/disensor.egg-info/dependency_links.txt +0 -0
  30. {disensor-0.6.0 → disensor-0.6.2}/src/disensor.egg-info/entry_points.txt +0 -0
  31. {disensor-0.6.0 → disensor-0.6.2}/src/disensor.egg-info/requires.txt +0 -0
  32. {disensor-0.6.0 → disensor-0.6.2}/src/disensor.egg-info/top_level.txt +0 -0
  33. {disensor-0.6.0 → disensor-0.6.2}/tests/test_brief.py +0 -0
  34. {disensor-0.6.0 → disensor-0.6.2}/tests/test_cli_errors.py +0 -0
  35. {disensor-0.6.0 → disensor-0.6.2}/tests/test_gate.py +0 -0
  36. {disensor-0.6.0 → disensor-0.6.2}/tests/test_guide.py +0 -0
  37. {disensor-0.6.0 → disensor-0.6.2}/tests/test_init.py +0 -0
  38. {disensor-0.6.0 → disensor-0.6.2}/tests/test_rules.py +0 -0
  39. {disensor-0.6.0 → disensor-0.6.2}/tests/test_scope.py +0 -0
  40. {disensor-0.6.0 → disensor-0.6.2}/tests/test_vectors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: disensor
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Adversarial plan & code review with a declared residue. Emits, validates and CI-enforces residue declarations (residue/v0.3 schema).
5
5
  Author-email: Nicolas Rocchia <nicolasrocchia@gmail.com>
6
6
  License: MIT
@@ -93,7 +93,7 @@ jobs:
93
93
  - uses: actions/checkout@v4
94
94
  with:
95
95
  fetch-depth: 0
96
- - uses: NicolasRocchia/disensor@v0.6.0
96
+ - uses: NicolasRocchia/disensor@v0.6.2
97
97
  ```
98
98
 
99
99
  El gate valida las declaraciones que **el PR agrega**, aplica la política y publica el resultado como comentario (se actualiza en el lugar en cada push). Todo lo que decide sale de objetos de git en el rango `merge-base..head`, nunca del working tree: en un evento `pull_request` el checkout deja el merge commit sintético mientras `head.sha` apunta al head real, así que leer del disco clasificaría un árbol y validaría otro.
@@ -155,7 +155,7 @@ Límite explícito: leer la política de la base convierte un bypass de un paso
155
155
 
156
156
  No corre modelos, no pide claves de API en CI, y ningún código viaja a ningún servicio: valida un JSON que ya está versionado en el repo. La orquestación del loop vive donde el equipo ya trabaja; el perfil `minimized` del artefacto está pensado para ambientes donde el texto de los hallazgos no puede salir del entorno.
157
157
 
158
- En el perfil `minimized`, R9 impide el texto libre en los campos que el protocolo define y el esquema exige además que todo valor bajo `extensions` sea opaco —un hash `sha256:`, un número, un booleano o contenedores de esos— y que toda clave tenga forma de identificador: un nombre, no un mensaje. El espacio de extensión no lo interpretan las reglas, que es justamente por qué el texto estacionado ahí saldría del entorno mientras el perfil afirma que nada sale. Queda un string libre fuera del alcance, `verification.detail`, cuya política se decide en la próxima tanda.
158
+ En el perfil `minimized`, R9 impide el texto libre en los campos que el protocolo define y el esquema exige además que todo valor bajo `extensions` sea opaco (un hash `sha256:`, un número, un booleano o contenedores de esos) y que toda clave tenga forma de identificador: un nombre, no un mensaje. El espacio de extensión no lo interpretan las reglas, que es justamente por qué el texto estacionado ahí saldría del entorno mientras el perfil afirma que nada sale. Queda un string libre fuera del alcance, `verification.detail`, cuya política se decide en la próxima tanda.
159
159
 
160
160
  ## Conformidad entre implementaciones
161
161
 
@@ -192,7 +192,7 @@ Migración desde v0.1: renombrar `.residuo/` a `.residue/`, las claves del confi
192
192
 
193
193
  Cuidado con la ambigüedad: esta sección habla de la versión **del esquema**; la siguiente habla de versiones **del paquete**. Son dos numeraciones distintas.
194
194
 
195
- La v0.3 no renombra ni agrega claves. Endurece los puntos donde la garantía declarada era más fuerte que la implementada —tres detectados antes de la ronda y dos que la propia ronda adversarial de v0.3 agregó—, y suma un valor a un enum:
195
+ La v0.3 no renombra ni agrega claves. Endurece los puntos donde la garantía declarada era más fuerte que la implementada (tres detectados antes de la ronda y dos que la propia ronda adversarial de v0.3 agregó), y suma un valor a un enum:
196
196
 
197
197
  | Antes valía | Ahora se rechaza | Por qué |
198
198
  |---|---|---|
@@ -204,7 +204,7 @@ La v0.3 no renombra ni agrega claves. Endurece los puntos donde la garantía dec
204
204
 
205
205
  Y `verification.against` acepta ahora **`external_source`**: literatura, especificaciones de terceros, advisories o documentación externa. En v0.2 una verificación contra una fuente externa no tenía categoría verdadera disponible y había que declararla como `repository` ([#7](https://github.com/NicolasRocchia/disensor/issues/7)).
206
206
 
207
- **Cómo migrar**: renombrar el campo `schema` a `residue/v0.3`. Si el artefacto ya satisface los invariantes de la tabla, no hay nada más que hacer — ninguno de los artefactos, ejemplos ni vectores de este repositorio los violaba. El validador reconoce un artefacto v0.2 y explica qué endureció la v0.3 en lugar de limitarse a decir que el `const` falló.
207
+ **Cómo migrar**: renombrar el campo `schema` a `residue/v0.3`. Si el artefacto ya satisface los invariantes de la tabla, no hay nada más que hacer: ninguno de los artefactos, ejemplos ni vectores de este repositorio los violaba. El validador reconoce un artefacto v0.2 y explica qué endureció la v0.3 en lugar de limitarse a decir que el `const` falló.
208
208
 
209
209
  **Por qué se subió el identificador en vez de endurecer v0.2 en el lugar**: no fue por compatibilidad, que no había ninguna que proteger. Fue porque el producto entero se apoya en que un identificador de esquema signifique una cosa; si v0.2 significara distinto según cuándo se lo lea, la herramienta se contradiría en su propio repositorio.
210
210
 
@@ -232,7 +232,7 @@ El esquema del artefacto no cambia y las declaraciones ya versionadas siguen sie
232
232
 
233
233
  ## Estado
234
234
 
235
- v0.6.0, la release de transición a **residue/v0.3** (la última publicada en PyPI es la 0.5.0 hasta que esta salga). La v0.4 reescribió el gate para que derive el alcance del PR de git (ver "Qué hace cumplir el gate") y la v0.5 entrega la consigna adversarial empaquetada con hash reproducible; el paso a residue/v0.3 endurece tres puntos del artefacto, cerrando los issues [#5](https://github.com/NicolasRocchia/disensor/issues/5), [#7](https://github.com/NicolasRocchia/disensor/issues/7) y [#8](https://github.com/NicolasRocchia/disensor/issues/8). Ver "Migración de v0.2 a v0.3". Decisión cerrada en v0.2: claves del esquema y CLI en inglés (el español queda como alias en la CLI y como idioma de la documentación). El esquema puede cambiar hasta v1.0; los cambios se declaran en el propio esquema. Decisión abierta antes de v1.0: licencia definitiva (hoy MIT; Apache-2.0 está en consideración por la concesión de patentes).
235
+ v0.6.2, sobre **residue/v0.3**. Las releases se publican a PyPI vía Trusted Publishing (OIDC, `release.yml`): sin tokens en ninguna máquina. La v0.4 reescribió el gate para que derive el alcance del PR de git (ver "Qué hace cumplir el gate") y la v0.5 entrega la consigna adversarial empaquetada con hash reproducible; el paso a residue/v0.3 endurece tres puntos del artefacto, cerrando los issues [#5](https://github.com/NicolasRocchia/disensor/issues/5), [#7](https://github.com/NicolasRocchia/disensor/issues/7) y [#8](https://github.com/NicolasRocchia/disensor/issues/8). Ver "Migración de v0.2 a v0.3". Decisión cerrada en v0.2: claves del esquema y CLI en inglés (el español queda como alias en la CLI y como idioma de la documentación). El esquema puede cambiar hasta v1.0; los cambios se declaran en el propio esquema. Decisión abierta antes de v1.0: licencia definitiva (hoy MIT; Apache-2.0 está en consideración por la concesión de patentes).
236
236
 
237
237
  ## Licencia
238
238
 
@@ -79,7 +79,7 @@ jobs:
79
79
  - uses: actions/checkout@v4
80
80
  with:
81
81
  fetch-depth: 0
82
- - uses: NicolasRocchia/disensor@v0.6.0
82
+ - uses: NicolasRocchia/disensor@v0.6.2
83
83
  ```
84
84
 
85
85
  El gate valida las declaraciones que **el PR agrega**, aplica la política y publica el resultado como comentario (se actualiza en el lugar en cada push). Todo lo que decide sale de objetos de git en el rango `merge-base..head`, nunca del working tree: en un evento `pull_request` el checkout deja el merge commit sintético mientras `head.sha` apunta al head real, así que leer del disco clasificaría un árbol y validaría otro.
@@ -141,7 +141,7 @@ Límite explícito: leer la política de la base convierte un bypass de un paso
141
141
 
142
142
  No corre modelos, no pide claves de API en CI, y ningún código viaja a ningún servicio: valida un JSON que ya está versionado en el repo. La orquestación del loop vive donde el equipo ya trabaja; el perfil `minimized` del artefacto está pensado para ambientes donde el texto de los hallazgos no puede salir del entorno.
143
143
 
144
- En el perfil `minimized`, R9 impide el texto libre en los campos que el protocolo define y el esquema exige además que todo valor bajo `extensions` sea opaco —un hash `sha256:`, un número, un booleano o contenedores de esos— y que toda clave tenga forma de identificador: un nombre, no un mensaje. El espacio de extensión no lo interpretan las reglas, que es justamente por qué el texto estacionado ahí saldría del entorno mientras el perfil afirma que nada sale. Queda un string libre fuera del alcance, `verification.detail`, cuya política se decide en la próxima tanda.
144
+ En el perfil `minimized`, R9 impide el texto libre en los campos que el protocolo define y el esquema exige además que todo valor bajo `extensions` sea opaco (un hash `sha256:`, un número, un booleano o contenedores de esos) y que toda clave tenga forma de identificador: un nombre, no un mensaje. El espacio de extensión no lo interpretan las reglas, que es justamente por qué el texto estacionado ahí saldría del entorno mientras el perfil afirma que nada sale. Queda un string libre fuera del alcance, `verification.detail`, cuya política se decide en la próxima tanda.
145
145
 
146
146
  ## Conformidad entre implementaciones
147
147
 
@@ -178,7 +178,7 @@ Migración desde v0.1: renombrar `.residuo/` a `.residue/`, las claves del confi
178
178
 
179
179
  Cuidado con la ambigüedad: esta sección habla de la versión **del esquema**; la siguiente habla de versiones **del paquete**. Son dos numeraciones distintas.
180
180
 
181
- La v0.3 no renombra ni agrega claves. Endurece los puntos donde la garantía declarada era más fuerte que la implementada —tres detectados antes de la ronda y dos que la propia ronda adversarial de v0.3 agregó—, y suma un valor a un enum:
181
+ La v0.3 no renombra ni agrega claves. Endurece los puntos donde la garantía declarada era más fuerte que la implementada (tres detectados antes de la ronda y dos que la propia ronda adversarial de v0.3 agregó), y suma un valor a un enum:
182
182
 
183
183
  | Antes valía | Ahora se rechaza | Por qué |
184
184
  |---|---|---|
@@ -190,7 +190,7 @@ La v0.3 no renombra ni agrega claves. Endurece los puntos donde la garantía dec
190
190
 
191
191
  Y `verification.against` acepta ahora **`external_source`**: literatura, especificaciones de terceros, advisories o documentación externa. En v0.2 una verificación contra una fuente externa no tenía categoría verdadera disponible y había que declararla como `repository` ([#7](https://github.com/NicolasRocchia/disensor/issues/7)).
192
192
 
193
- **Cómo migrar**: renombrar el campo `schema` a `residue/v0.3`. Si el artefacto ya satisface los invariantes de la tabla, no hay nada más que hacer — ninguno de los artefactos, ejemplos ni vectores de este repositorio los violaba. El validador reconoce un artefacto v0.2 y explica qué endureció la v0.3 en lugar de limitarse a decir que el `const` falló.
193
+ **Cómo migrar**: renombrar el campo `schema` a `residue/v0.3`. Si el artefacto ya satisface los invariantes de la tabla, no hay nada más que hacer: ninguno de los artefactos, ejemplos ni vectores de este repositorio los violaba. El validador reconoce un artefacto v0.2 y explica qué endureció la v0.3 en lugar de limitarse a decir que el `const` falló.
194
194
 
195
195
  **Por qué se subió el identificador en vez de endurecer v0.2 en el lugar**: no fue por compatibilidad, que no había ninguna que proteger. Fue porque el producto entero se apoya en que un identificador de esquema signifique una cosa; si v0.2 significara distinto según cuándo se lo lea, la herramienta se contradiría en su propio repositorio.
196
196
 
@@ -218,7 +218,7 @@ El esquema del artefacto no cambia y las declaraciones ya versionadas siguen sie
218
218
 
219
219
  ## Estado
220
220
 
221
- v0.6.0, la release de transición a **residue/v0.3** (la última publicada en PyPI es la 0.5.0 hasta que esta salga). La v0.4 reescribió el gate para que derive el alcance del PR de git (ver "Qué hace cumplir el gate") y la v0.5 entrega la consigna adversarial empaquetada con hash reproducible; el paso a residue/v0.3 endurece tres puntos del artefacto, cerrando los issues [#5](https://github.com/NicolasRocchia/disensor/issues/5), [#7](https://github.com/NicolasRocchia/disensor/issues/7) y [#8](https://github.com/NicolasRocchia/disensor/issues/8). Ver "Migración de v0.2 a v0.3". Decisión cerrada en v0.2: claves del esquema y CLI en inglés (el español queda como alias en la CLI y como idioma de la documentación). El esquema puede cambiar hasta v1.0; los cambios se declaran en el propio esquema. Decisión abierta antes de v1.0: licencia definitiva (hoy MIT; Apache-2.0 está en consideración por la concesión de patentes).
221
+ v0.6.2, sobre **residue/v0.3**. Las releases se publican a PyPI vía Trusted Publishing (OIDC, `release.yml`): sin tokens en ninguna máquina. La v0.4 reescribió el gate para que derive el alcance del PR de git (ver "Qué hace cumplir el gate") y la v0.5 entrega la consigna adversarial empaquetada con hash reproducible; el paso a residue/v0.3 endurece tres puntos del artefacto, cerrando los issues [#5](https://github.com/NicolasRocchia/disensor/issues/5), [#7](https://github.com/NicolasRocchia/disensor/issues/7) y [#8](https://github.com/NicolasRocchia/disensor/issues/8). Ver "Migración de v0.2 a v0.3". Decisión cerrada en v0.2: claves del esquema y CLI en inglés (el español queda como alias en la CLI y como idioma de la documentación). El esquema puede cambiar hasta v1.0; los cambios se declaran en el propio esquema. Decisión abierta antes de v1.0: licencia definitiva (hoy MIT; Apache-2.0 está en consideración por la concesión de patentes).
222
222
 
223
223
  ## Licencia
224
224
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "disensor"
7
- version = "0.6.0"
7
+ version = "0.6.2"
8
8
  description = "Adversarial plan & code review with a declared residue. Emits, validates and CI-enforces residue declarations (residue/v0.3 schema)."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,2 +1,2 @@
1
1
  """disensor: residue declaration of adversarial review (controlled disagreement)."""
2
- __version__ = "0.6.0"
2
+ __version__ = "0.6.2"
@@ -89,7 +89,18 @@ def build_parser() -> argparse.ArgumentParser:
89
89
  validate.add_argument("files", nargs="+")
90
90
  validate.set_defaults(func=main_validate)
91
91
 
92
- gate = sub.add_parser("gate", help="CI gate: validates the declarations this PR adds and applies policy G1 to G8.")
92
+ gate = sub.add_parser(
93
+ "gate",
94
+ help="CI gate: validates the declarations this PR adds and applies policy G1 to G9.",
95
+ # `help` solo se ve en el listado de `disensor --help`; `description` es lo
96
+ # que muestra `disensor gate --help`, que es donde alguien mira cuando
97
+ # quiere saber que hace este subcomando.
98
+ description=(
99
+ "CI gate: validates the residue declarations this PR adds and applies policy "
100
+ "G1 to G9. Everything it decides comes from git objects in the merge-base..head "
101
+ "range, never from the working tree."
102
+ ),
103
+ )
93
104
  gate.add_argument("--directory", "--directorio", default=".residue")
94
105
  gate.add_argument("--config", default="disensor.config.json")
95
106
  gate.add_argument("--base", default=None, help="Base SHA of the PR (defaults to the GitHub event).")
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://www.pelatech.com.ar/specs/residue/v0.3/residue.schema.json",
4
4
  "title": "Residue declaration of an adversarial review event (controlled disagreement)",
5
- "description": "Artifact that an adversarial review event emits at the close of the cycle. It declares residue, not coverage (protocol, section 9): it lists what the cycle could not close by itself, or expressly declares its absence. Resolved and verified findings are not residue; their evidence is the diff. Provisional version v0.2, subject to ratification. v0.2 renames every key and enum from Spanish (residuo/v0.1) to English; the mapping is documented in the repository glossary. No structural semantics changed between v0.1 and v0.2.",
5
+ "description": "Artifact that an adversarial review event emits at the close of the cycle. It declares residue, not coverage (protocol, section 9): it lists what the cycle could not close by itself, or expressly declares its absence. Resolved and verified findings are not residue; their evidence is the diff. Provisional version v0.3, subject to ratification. v0.2 renames every key and enum from Spanish (residuo/v0.1) to English; the mapping is documented in the repository glossary. No structural semantics changed between v0.1 and v0.2.",
6
6
  "type": "object",
7
7
  "required": [
8
8
  "schema",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: disensor
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Adversarial plan & code review with a declared residue. Emits, validates and CI-enforces residue declarations (residue/v0.3 schema).
5
5
  Author-email: Nicolas Rocchia <nicolasrocchia@gmail.com>
6
6
  License: MIT
@@ -93,7 +93,7 @@ jobs:
93
93
  - uses: actions/checkout@v4
94
94
  with:
95
95
  fetch-depth: 0
96
- - uses: NicolasRocchia/disensor@v0.6.0
96
+ - uses: NicolasRocchia/disensor@v0.6.2
97
97
  ```
98
98
 
99
99
  El gate valida las declaraciones que **el PR agrega**, aplica la política y publica el resultado como comentario (se actualiza en el lugar en cada push). Todo lo que decide sale de objetos de git en el rango `merge-base..head`, nunca del working tree: en un evento `pull_request` el checkout deja el merge commit sintético mientras `head.sha` apunta al head real, así que leer del disco clasificaría un árbol y validaría otro.
@@ -155,7 +155,7 @@ Límite explícito: leer la política de la base convierte un bypass de un paso
155
155
 
156
156
  No corre modelos, no pide claves de API en CI, y ningún código viaja a ningún servicio: valida un JSON que ya está versionado en el repo. La orquestación del loop vive donde el equipo ya trabaja; el perfil `minimized` del artefacto está pensado para ambientes donde el texto de los hallazgos no puede salir del entorno.
157
157
 
158
- En el perfil `minimized`, R9 impide el texto libre en los campos que el protocolo define y el esquema exige además que todo valor bajo `extensions` sea opaco —un hash `sha256:`, un número, un booleano o contenedores de esos— y que toda clave tenga forma de identificador: un nombre, no un mensaje. El espacio de extensión no lo interpretan las reglas, que es justamente por qué el texto estacionado ahí saldría del entorno mientras el perfil afirma que nada sale. Queda un string libre fuera del alcance, `verification.detail`, cuya política se decide en la próxima tanda.
158
+ En el perfil `minimized`, R9 impide el texto libre en los campos que el protocolo define y el esquema exige además que todo valor bajo `extensions` sea opaco (un hash `sha256:`, un número, un booleano o contenedores de esos) y que toda clave tenga forma de identificador: un nombre, no un mensaje. El espacio de extensión no lo interpretan las reglas, que es justamente por qué el texto estacionado ahí saldría del entorno mientras el perfil afirma que nada sale. Queda un string libre fuera del alcance, `verification.detail`, cuya política se decide en la próxima tanda.
159
159
 
160
160
  ## Conformidad entre implementaciones
161
161
 
@@ -192,7 +192,7 @@ Migración desde v0.1: renombrar `.residuo/` a `.residue/`, las claves del confi
192
192
 
193
193
  Cuidado con la ambigüedad: esta sección habla de la versión **del esquema**; la siguiente habla de versiones **del paquete**. Son dos numeraciones distintas.
194
194
 
195
- La v0.3 no renombra ni agrega claves. Endurece los puntos donde la garantía declarada era más fuerte que la implementada —tres detectados antes de la ronda y dos que la propia ronda adversarial de v0.3 agregó—, y suma un valor a un enum:
195
+ La v0.3 no renombra ni agrega claves. Endurece los puntos donde la garantía declarada era más fuerte que la implementada (tres detectados antes de la ronda y dos que la propia ronda adversarial de v0.3 agregó), y suma un valor a un enum:
196
196
 
197
197
  | Antes valía | Ahora se rechaza | Por qué |
198
198
  |---|---|---|
@@ -204,7 +204,7 @@ La v0.3 no renombra ni agrega claves. Endurece los puntos donde la garantía dec
204
204
 
205
205
  Y `verification.against` acepta ahora **`external_source`**: literatura, especificaciones de terceros, advisories o documentación externa. En v0.2 una verificación contra una fuente externa no tenía categoría verdadera disponible y había que declararla como `repository` ([#7](https://github.com/NicolasRocchia/disensor/issues/7)).
206
206
 
207
- **Cómo migrar**: renombrar el campo `schema` a `residue/v0.3`. Si el artefacto ya satisface los invariantes de la tabla, no hay nada más que hacer — ninguno de los artefactos, ejemplos ni vectores de este repositorio los violaba. El validador reconoce un artefacto v0.2 y explica qué endureció la v0.3 en lugar de limitarse a decir que el `const` falló.
207
+ **Cómo migrar**: renombrar el campo `schema` a `residue/v0.3`. Si el artefacto ya satisface los invariantes de la tabla, no hay nada más que hacer: ninguno de los artefactos, ejemplos ni vectores de este repositorio los violaba. El validador reconoce un artefacto v0.2 y explica qué endureció la v0.3 en lugar de limitarse a decir que el `const` falló.
208
208
 
209
209
  **Por qué se subió el identificador en vez de endurecer v0.2 en el lugar**: no fue por compatibilidad, que no había ninguna que proteger. Fue porque el producto entero se apoya en que un identificador de esquema signifique una cosa; si v0.2 significara distinto según cuándo se lo lea, la herramienta se contradiría en su propio repositorio.
210
210
 
@@ -232,7 +232,7 @@ El esquema del artefacto no cambia y las declaraciones ya versionadas siguen sie
232
232
 
233
233
  ## Estado
234
234
 
235
- v0.6.0, la release de transición a **residue/v0.3** (la última publicada en PyPI es la 0.5.0 hasta que esta salga). La v0.4 reescribió el gate para que derive el alcance del PR de git (ver "Qué hace cumplir el gate") y la v0.5 entrega la consigna adversarial empaquetada con hash reproducible; el paso a residue/v0.3 endurece tres puntos del artefacto, cerrando los issues [#5](https://github.com/NicolasRocchia/disensor/issues/5), [#7](https://github.com/NicolasRocchia/disensor/issues/7) y [#8](https://github.com/NicolasRocchia/disensor/issues/8). Ver "Migración de v0.2 a v0.3". Decisión cerrada en v0.2: claves del esquema y CLI en inglés (el español queda como alias en la CLI y como idioma de la documentación). El esquema puede cambiar hasta v1.0; los cambios se declaran en el propio esquema. Decisión abierta antes de v1.0: licencia definitiva (hoy MIT; Apache-2.0 está en consideración por la concesión de patentes).
235
+ v0.6.2, sobre **residue/v0.3**. Las releases se publican a PyPI vía Trusted Publishing (OIDC, `release.yml`): sin tokens en ninguna máquina. La v0.4 reescribió el gate para que derive el alcance del PR de git (ver "Qué hace cumplir el gate") y la v0.5 entrega la consigna adversarial empaquetada con hash reproducible; el paso a residue/v0.3 endurece tres puntos del artefacto, cerrando los issues [#5](https://github.com/NicolasRocchia/disensor/issues/5), [#7](https://github.com/NicolasRocchia/disensor/issues/7) y [#8](https://github.com/NicolasRocchia/disensor/issues/8). Ver "Migración de v0.2 a v0.3". Decisión cerrada en v0.2: claves del esquema y CLI en inglés (el español queda como alias en la CLI y como idioma de la documentación). El esquema puede cambiar hasta v1.0; los cambios se declaran en el propio esquema. Decisión abierta antes de v1.0: licencia definitiva (hoy MIT; Apache-2.0 está en consideración por la concesión de patentes).
236
236
 
237
237
  ## Licencia
238
238
 
@@ -0,0 +1,167 @@
1
+ """The documented Action version has to be the version being shipped.
2
+
3
+ Between 0.2.0 and 0.3.0 the README kept pointing at `@v0.2.0` while `init` wrote
4
+ `@v0.3.0`, so anyone following the documentation ran a different gate than the
5
+ one the tool installs. That is cheap to catch and expensive to notice by hand.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import re
11
+ import subprocess
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ from disensor import __version__
16
+
17
+ ROOT = Path(__file__).resolve().parents[1]
18
+ DOCS = [ROOT / "README.md", ROOT / "docs" / "ejemplo-workflow.yml"]
19
+
20
+ PIN = re.compile(r"NicolasRocchia/disensor@v([0-9]+\.[0-9]+\.[0-9]+)")
21
+
22
+
23
+ def test_documented_action_version_matches_the_package():
24
+ for path in DOCS:
25
+ pins = PIN.findall(path.read_text(encoding="utf-8"))
26
+ assert pins, f"{path.name} documents no Action version"
27
+ for pinned in pins:
28
+ assert pinned == __version__, (
29
+ f"{path.name} documents @v{pinned} while the package is {__version__}"
30
+ )
31
+
32
+
33
+ NUL = chr(0)
34
+
35
+
36
+ def split_ls_files(output: str) -> list[str]:
37
+ """Corta la salida de `git ls-files -z` por NUL.
38
+
39
+ Separada del test para poder probarla con una salida armada. `git ls-files`
40
+ sin `-z` separa por salto de linea y entrecomilla los nombres raros, y
41
+ `.split()` ademas corta en los espacios: un archivo llamado `mi nota.md` se
42
+ partia en dos rutas inexistentes, las dos se saltaban en silencio, y el
43
+ archivo que si violaba la regla nunca se abria. El test pasaba justo cuando
44
+ tenia que fallar.
45
+ """
46
+ return [name for name in output.split(NUL) if name]
47
+
48
+
49
+ def tracked_markdown() -> list[str]:
50
+ out = subprocess.run(
51
+ ["git", "ls-files", "-z", "--", "*.md"],
52
+ cwd=ROOT, capture_output=True, text=True, check=True,
53
+ ).stdout
54
+ return split_ls_files(out)
55
+
56
+
57
+ def test_a_name_with_spaces_stays_one_path():
58
+ assert split_ls_files("README.md" + NUL + "docs/mi nota.md" + NUL) == [
59
+ "README.md", "docs/mi nota.md",
60
+ ]
61
+
62
+
63
+ EM_DASH = "\u2014"
64
+
65
+
66
+ def test_no_em_dashes_in_the_documents():
67
+ """Regla editorial dura del proyecto: jamás guiones largos.
68
+
69
+ Vivía en la cabeza de quien escribía y las rondas 0.4 a 0.6 metieron 38 sin
70
+ que nadie los viera, tres de ellos en el README, que es la página de PyPI.
71
+ Una regla que depende de recordarla no es una regla, es una intención: acá
72
+ pasa al CI, la misma doctrina que el gate aplica a las declaraciones.
73
+
74
+ `.residue/` queda afuera a propósito. Son declaraciones ya emitidas y la
75
+ evidencia es de solo agregar (G8): corregirles el texto para satisfacer una
76
+ regla de estilo sería reescribir un registro histórico, que es exactamente
77
+ lo que la herramienta existe para impedir.
78
+ """
79
+ # -z y corte por NUL: `git ls-files` separa por salto de línea y entrecomilla
80
+ # los nombres raros, y `.split()` además corta en los espacios. Un archivo
81
+ # llamado `mi nota.md` se partía en dos rutas inexistentes, las dos se
82
+ # saltaban en silencio, y el archivo que sí tenía el guión largo nunca se
83
+ # abría: el test pasaba justo cuando tenía que fallar.
84
+ tracked = tracked_markdown()
85
+ offenders = {}
86
+ for name in tracked:
87
+ if name.startswith(".residue/"):
88
+ continue
89
+ text = (ROOT / name).read_text(encoding="utf-8")
90
+ if EM_DASH in text:
91
+ offenders[name] = text.count(EM_DASH)
92
+ assert not offenders, f"guiones largos encontrados: {offenders}"
93
+
94
+
95
+ def implemented_checks() -> list[int]:
96
+ """Los chequeos G que gate.py implementa de verdad, no los que dice implementar."""
97
+ src = (ROOT / "src" / "disensor" / "gate.py").read_text(encoding="utf-8")
98
+ # \d+ y no \d: con un solo digito, un G10 quedaba invisible.
99
+ return sorted({int(n) for n in re.findall(r"\[G(\d+)\]", src)})
100
+
101
+
102
+ def cli_help(*argv: str) -> str:
103
+ """La ayuda que ve un usuario, ejercitando la interfaz y no leyendo el fuente.
104
+
105
+ El test anterior leia cli.py como texto y por eso daba verde sobre una ayuda
106
+ que el usuario nunca ve: `help=` solo aparece en el listado de
107
+ `disensor --help`, mientras `disensor gate --help` muestra `description`.
108
+ Corregir el primero y afirmar que se arreglo el segundo era falso, y el test
109
+ lo confirmaba igual.
110
+ """
111
+ out = subprocess.run(
112
+ [sys.executable, "-m", "disensor", *argv, "--help"],
113
+ cwd=ROOT, capture_output=True, text=True,
114
+ env={**os.environ, "PYTHONPATH": str(ROOT / "src")},
115
+ )
116
+ return out.stdout
117
+
118
+
119
+ def test_the_gate_help_names_every_implemented_check():
120
+ """G9 es el que exige residue/v0.3 a las declaraciones que un PR agrega.
121
+
122
+ Una ayuda que no lo nombra esconde el chequeo que hace cumplir la version
123
+ del contrato.
124
+ """
125
+ checks = implemented_checks()
126
+ assert checks, "no se detecto ningun chequeo G en gate.py"
127
+ assert checks == list(range(checks[0], checks[-1] + 1)), (
128
+ f"los chequeos implementados no son contiguos: {checks}. Un rango en la ayuda "
129
+ "no puede describirlos con honestidad"
130
+ )
131
+ for texto, donde in ((cli_help("gate"), "disensor gate --help"),
132
+ (cli_help(), "disensor --help")):
133
+ rango = re.search(r"G(\d+)\s*(?:to|a)\s*G(\d+)", texto.replace("\n", " "))
134
+ assert rango, f"{donde} no declara un rango de chequeos"
135
+ assert [int(rango.group(1)), int(rango.group(2))] == [checks[0], checks[-1]], (
136
+ f"{donde} dice G{rango.group(1)} a G{rango.group(2)} y gate.py implementa "
137
+ f"G{checks[0]} a G{checks[-1]}"
138
+ )
139
+
140
+
141
+ def test_the_schema_declares_the_version_the_tool_enforces():
142
+ """El $id, la descripcion y lo que el gate exige tienen que decir lo mismo.
143
+
144
+ El test anterior comparaba el $id contra la descripcion del mismo archivo, o
145
+ sea derivaba la expectativa del valor que debia proteger: bajar los dos a
146
+ v0.2 a la vez lo dejaba pasar. La referencia ahora es CURRENT_SCHEMA, que es
147
+ lo que el gate exige de verdad a las declaraciones nuevas.
148
+ """
149
+ import json
150
+
151
+ from disensor.gate import CURRENT_SCHEMA
152
+
153
+ esperada = CURRENT_SCHEMA.split("/")[-1]
154
+ for ruta in (ROOT / "spec" / "residue.schema.json",
155
+ ROOT / "src" / "disensor" / "residue.schema.json"):
156
+ s = json.loads(ruta.read_text(encoding="utf-8"))
157
+ assert f"/residue/{esperada}/" in s["$id"], (
158
+ f"{ruta.name}: el $id es {s['$id']} y el gate exige {CURRENT_SCHEMA}"
159
+ )
160
+ provisional = re.search(r"Provisional version (v[0-9.]+)", s.get("description", ""))
161
+ assert provisional, f"{ruta.name}: la descripcion no dice cual es la version provisional"
162
+ assert provisional.group(1) == esperada, (
163
+ f"{ruta.name}: la descripcion dice {provisional.group(1)} y el gate exige {esperada}"
164
+ )
165
+ assert CURRENT_SCHEMA in s["properties"]["schema"]["enum"], (
166
+ f"{ruta.name}: el enum de schema no admite {CURRENT_SCHEMA}"
167
+ )
@@ -1,27 +0,0 @@
1
- """The documented Action version has to be the version being shipped.
2
-
3
- Between 0.2.0 and 0.3.0 the README kept pointing at `@v0.2.0` while `init` wrote
4
- `@v0.3.0`, so anyone following the documentation ran a different gate than the
5
- one the tool installs. That is cheap to catch and expensive to notice by hand.
6
- """
7
- from __future__ import annotations
8
-
9
- import re
10
- from pathlib import Path
11
-
12
- from disensor import __version__
13
-
14
- ROOT = Path(__file__).resolve().parents[1]
15
- DOCS = [ROOT / "README.md", ROOT / "docs" / "ejemplo-workflow.yml"]
16
-
17
- PIN = re.compile(r"NicolasRocchia/disensor@v([0-9]+\.[0-9]+\.[0-9]+)")
18
-
19
-
20
- def test_documented_action_version_matches_the_package():
21
- for path in DOCS:
22
- pins = PIN.findall(path.read_text(encoding="utf-8"))
23
- assert pins, f"{path.name} documents no Action version"
24
- for pinned in pins:
25
- assert pinned == __version__, (
26
- f"{path.name} documents @v{pinned} while the package is {__version__}"
27
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes