disensor 0.1.0__py3-none-any.whl

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.
disensor/vectores.py ADDED
@@ -0,0 +1,189 @@
1
+ """Vectores de conformidad del esquema v0.1.
2
+
3
+ Los vectores son la fuente de verdad compartida entre implementaciones del
4
+ validador (Python de referencia, TypeScript del plano de evidencia, y las que
5
+ vengan). Cada vector es un artefacto mas el veredicto esperado: valido o no,
6
+ y el conjunto de etiquetas de regla que deben dispararse ("schema" para
7
+ errores de forma, "R0" a "R10" para reglas estructurales).
8
+
9
+ Se comparan etiquetas, no mensajes: los mensajes son libres por
10
+ implementacion; las etiquetas no pueden divergir.
11
+
12
+ Uso: python -m disensor.vectores <directorio_destino>
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import copy
17
+ import json
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ from .reglas import validar_artefacto
22
+
23
+ RAIZ = Path(__file__).resolve().parents[2]
24
+ EJEMPLOS = RAIZ / "spec" / "ejemplos"
25
+
26
+
27
+ def _cargar(nombre: str) -> dict:
28
+ with open(EJEMPLOS / nombre, encoding="utf-8") as f:
29
+ return json.load(f)
30
+
31
+
32
+ def _etiquetas(errores: list[str]) -> list[str]:
33
+ tags = set()
34
+ for e in errores:
35
+ if e.startswith("[") and "]" in e:
36
+ tags.add(e[1:e.index("]")])
37
+ return sorted(tags)
38
+
39
+
40
+ def casos() -> list[tuple[str, dict, bool, set[str]]]:
41
+ """(nombre, artefacto, valido_esperado, reglas_que_deben_aparecer)."""
42
+ plan = _cargar("ejemplo_1_compuerta_plan.json")
43
+ diff = _cargar("ejemplo_2_compuerta_diff.json")
44
+ mini = _cargar("ejemplo_3_perfil_minimizado.json")
45
+ lista: list[tuple[str, dict, bool, set[str]]] = []
46
+
47
+ # Validos
48
+ lista.append(("valido_compuerta_plan", copy.deepcopy(plan), True, set()))
49
+ lista.append(("valido_compuerta_diff", copy.deepcopy(diff), True, set()))
50
+ lista.append(("valido_perfil_minimizado", copy.deepcopy(mini), True, set()))
51
+
52
+ m = copy.deepcopy(diff)
53
+ m["evento"]["nivel_criticidad"] = "A"
54
+ for i in m["residuo"]["items"]:
55
+ if i["clase"] == "gap_de_ejecucion":
56
+ i["aceptacion_referente"] = {
57
+ "referente": "referente-tecnico-01",
58
+ "fecha": "2026-07-15T19:00:00-03:00",
59
+ "registro": "actas/2026-07-15-aceptacion-gap.md",
60
+ }
61
+ lista.append(("valido_nivel_a_gap_con_referente", m, True, set()))
62
+
63
+ m = copy.deepcopy(mini)
64
+ m["hallazgos"] = []
65
+ m["residuo"] = {
66
+ "ausencia_declarada": True,
67
+ "declaracion": "El ciclo corrio completo sobre el diff y no quedo nada sin resolver: "
68
+ "todo hallazgo se incorporo y sus correcciones pasaron pruebas especificas.",
69
+ }
70
+ m["metricas"]["conteos"] = {
71
+ "total_hallazgos": 0,
72
+ "validos": {"incorporado": 0, "deuda_registrada": 0, "decision_del_dueno": 0},
73
+ "falsos_positivos": {"refutado_verificable": 0, "refutado_interpretativo": 0},
74
+ "escalados_abiertos": 0,
75
+ }
76
+ lista.append(("valido_minimizado_ausencia_declarada", m, True, set()))
77
+
78
+ # Invalidos: reglas estructurales
79
+ m = copy.deepcopy(diff)
80
+ m["actores"]["arbitro_humano"]["presente"] = False
81
+ lista.append(("r0_sin_arbitro_humano", m, False, {"R0"}))
82
+
83
+ m = copy.deepcopy(diff)
84
+ m["residuo"]["items"] = [i for i in m["residuo"]["items"] if i["id"] != "r1"]
85
+ lista.append(("r1_escalado_sin_item", m, False, {"R1"}))
86
+
87
+ m = copy.deepcopy(diff)
88
+ m["residuo"]["items"][0]["ref_hallazgo"] = "h99"
89
+ lista.append(("r1_referencia_inexistente", m, False, {"R1"}))
90
+
91
+ m = copy.deepcopy(diff)
92
+ m["residuo"]["items"][0]["descripcion"] = "ninguno"
93
+ lista.append(("r2_marcador_generico", m, False, {"R2"}))
94
+
95
+ m = copy.deepcopy(diff)
96
+ m["actores"]["revisores"][0]["modelo"] = "COMPLETAR_modelo_del_revisor"
97
+ lista.append(("r2_marcador_de_plantilla", m, False, {"R2"}))
98
+
99
+ m = copy.deepcopy(diff)
100
+ m["actores"]["revisores"][0]["familia"] = "anthropic"
101
+ lista.append(("r4_sin_decorrelacion", m, False, {"R4"}))
102
+
103
+ m = copy.deepcopy(diff)
104
+ m["evento"]["nivel_criticidad"] = "A"
105
+ lista.append(("r5_gap_nivel_a_sin_referente", m, False, {"R5"}))
106
+
107
+ m = copy.deepcopy(diff)
108
+ m["metricas"]["conteos"]["validos"]["incorporado"] = 3
109
+ lista.append(("r6_conteo_inflado", m, False, {"R6"}))
110
+
111
+ m = copy.deepcopy(diff)
112
+ m["hallazgos"][0]["verificacion_de_la_correccion"] = {"tipo": "pendiente_en_compuerta_diff"}
113
+ lista.append(("r7_correccion_pendiente_en_diff", m, False, {"R7"}))
114
+
115
+ m = copy.deepcopy(mini)
116
+ m["hallazgos"][0]["titulo"] = "titulo que no deberia estar"
117
+ lista.append(("r9_fuga_de_texto_en_minimizado", m, False, {"R9"}))
118
+
119
+ m = copy.deepcopy(mini)
120
+ m["evento"]["repositorio"] = "https://github.com/ejemplo/repo"
121
+ lista.append(("r9_url_en_claro_en_minimizado", m, False, {"R9"}))
122
+
123
+ m = copy.deepcopy(diff)
124
+ m["hallazgos"] = []
125
+ lista.append(("r10_completo_sin_hallazgos", m, False, {"R10"}))
126
+
127
+ # Invalidos: solo schema (las reglas no se evaluan si la forma falla)
128
+ m = copy.deepcopy(diff)
129
+ m["evento"]["ruta_abreviada"] = {
130
+ "usada": True,
131
+ "justificacion": "cambio trivial de una linea en un helper",
132
+ "casos_protegidos_tocados": ["migracion_datos"],
133
+ }
134
+ lista.append(("schema_ruta_abreviada_sobre_caso_protegido", m, False, {"schema"}))
135
+
136
+ m = copy.deepcopy(diff)
137
+ m["hallazgos"][0]["estado_final"] = "deuda_registrada"
138
+ lista.append(("schema_deuda_sin_id", m, False, {"schema"}))
139
+
140
+ m = copy.deepcopy(diff)
141
+ m["residuo"]["items"][1]["tipo_refutacion"] = "interpretativa"
142
+ m["residuo"]["items"][1]["requiere_atencion_humana"] = False
143
+ lista.append(("schema_interpretativa_sin_atencion_humana", m, False, {"schema"}))
144
+
145
+ m = copy.deepcopy(diff)
146
+ m["evento"]["commit_cabeza"] = "ZZZZ"
147
+ lista.append(("schema_commit_invalido", m, False, {"schema"}))
148
+
149
+ m = copy.deepcopy(diff)
150
+ del m["residuo"]["items"]
151
+ lista.append(("schema_residuo_vacio", m, False, {"schema"}))
152
+
153
+ return lista
154
+
155
+
156
+ def generar(destino: Path) -> int:
157
+ """Escribe los vectores con el veredicto de la implementacion de referencia.
158
+
159
+ El veredicto se registra desde la implementacion, pero cada caso lleva una
160
+ expectativa minima (valido si o no, y que reglas deben aparecer) que se
161
+ verifica antes de escribir: el generador no puede bendecir un bug propio.
162
+ """
163
+ destino.mkdir(parents=True, exist_ok=True)
164
+ indice = []
165
+ for nombre, artefacto, valido_esperado, reglas_minimas in casos():
166
+ errores = validar_artefacto(artefacto)
167
+ valido = not errores
168
+ etiquetas = _etiquetas(errores)
169
+ assert valido == valido_esperado, f"{nombre}: esperaba valido={valido_esperado}, dio {valido}: {errores}"
170
+ faltan = reglas_minimas - set(etiquetas)
171
+ assert not faltan, f"{nombre}: faltan reglas {faltan} en {etiquetas}"
172
+ vector = {
173
+ "nombre": nombre,
174
+ "esperado": {"valido": valido, "reglas": etiquetas},
175
+ "artefacto": artefacto,
176
+ }
177
+ with open(destino / f"{nombre}.json", "w", encoding="utf-8") as f:
178
+ json.dump(vector, f, ensure_ascii=False, indent=2)
179
+ f.write("\n")
180
+ indice.append(nombre)
181
+ with open(destino / "INDICE.json", "w", encoding="utf-8") as f:
182
+ json.dump({"esquema": "residuo/v0.1", "vectores": indice}, f, ensure_ascii=False, indent=2)
183
+ f.write("\n")
184
+ print(f"{len(indice)} vectores escritos en {destino}")
185
+ return 0
186
+
187
+
188
+ if __name__ == "__main__":
189
+ sys.exit(generar(Path(sys.argv[1] if len(sys.argv) > 1 else "spec/vectores")))
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: disensor
3
+ Version: 0.1.0
4
+ Summary: disensor: adversarial plan & code review with a declared residue. Emite, valida y hace cumplir en CI declaraciones de residuo (esquema residuo/v0.1).
5
+ Author-email: Nicolas Rocchia <nicolasrocchia@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: jsonschema>=4.18
11
+ Provides-Extra: test
12
+ Requires-Dist: pytest>=8; extra == "test"
13
+ Dynamic: license-file
14
+
15
+ # disensor
16
+
17
+ Adversarial plan & code review with a declared residue.
18
+
19
+ Declaración de residuo de revisión adversarial, con validación y gate de CI. Implementación de referencia del artefacto definido a partir del método de **desacuerdo controlado**: un modelo genera, un modelo de otra familia ataca, el generador verifica cada hallazgo, y el ciclo termina cuando todo hallazgo quedó resuelto, refutado con evidencia o escalado a un humano.
20
+
21
+ El artefacto que este repo define y hace cumplir registra cómo terminó cada evento de revisión: los hallazgos con su estado terminal, y el **residuo**: lo que el ciclo no pudo cerrar por sí mismo y descansa sobre el juicio de alguien. La declaración lista residuo, no cobertura: dirige el escrutinio del revisor humano en lugar de leerse como sello de calidad.
22
+
23
+ Paper del método: Rocchia, N. (2026), *Desacuerdo controlado: revisión adversarial automatizada con un segundo asistente de código en el desarrollo de software*, DOI [10.5281/zenodo.21633495](https://doi.org/10.5281/zenodo.21633495).
24
+
25
+ ## Qué hay acá
26
+
27
+ - `spec/residuo.schema.json`: el esquema del artefacto (JSON Schema 2020-12), versión v0.1.
28
+ - `spec/ejemplos/`: tres artefactos de ejemplo, incluido un evento real anonimizado y el perfil minimizado sin texto libre.
29
+ - `src/disensor/`: paquete Python con el validador (reglas R0 a R10), el gate de CI (chequeos G1 a G5), el render del comentario de PR y el scaffolding de artefactos.
30
+ - `action.yml`: GitHub Action compuesta, lista para usar.
31
+ - `docs/integracion-claude-code.md`: cómo el flujo real (Claude Code + Codex) emite el artefacto al cierre de cada evento.
32
+
33
+ ## Uso rápido
34
+
35
+ ```bash
36
+ pip install disensor # para desarrollo, desde el repo clonado: pip install -e .
37
+
38
+ disensor nuevo --compuerta diff --nivel B # plantilla prellenada en .residuo/
39
+ disensor validar .residuo/<id>.json # schema + reglas R0 a R10
40
+ disensor gate --sin-comentario # lo que va a correr CI, en local
41
+ ```
42
+
43
+ En el repositorio consumidor, `disensor.config.json` en la raíz declara el nivel de criticidad (el nivel viaja con el código, en un archivo versionado):
44
+
45
+ ```json
46
+ {
47
+ "nivel_criticidad": "B",
48
+ "nivel_A_habilitado": false
49
+ }
50
+ ```
51
+
52
+ Y el workflow (ver `docs/ejemplo-workflow.yml`):
53
+
54
+ ```yaml
55
+ on: pull_request
56
+ permissions:
57
+ contents: read
58
+ pull-requests: write
59
+ jobs:
60
+ gate:
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - uses: actions/checkout@v4
64
+ with:
65
+ fetch-depth: 0
66
+ - uses: NicolasRocchia/disensor@v0.1.0
67
+ ```
68
+
69
+ El gate valida todos los artefactos de `.residuo/` del PR, aplica la política y publica la declaración como comentario (se actualiza en el lugar en cada push).
70
+
71
+ ## Qué hace cumplir el gate
72
+
73
+ Por artefacto (reglas R0 a R10): coherencia entre hallazgos y residuo, conteos que cierran, decorrelación de familias entre generador y revisor, evidencia obligatoria en refutaciones verificables, atención humana obligatoria en refutaciones interpretativas, corrección verificada antes de cerrar un hallazgo en compuerta de diff, rechazo de marcadores genéricos, y perfil minimizado sin fugas de texto.
74
+
75
+ Por PR (chequeos G1 a G5): al menos una declaración válida en el rango, nivel del artefacto igual al declarado del repositorio, Nivel A bloqueado mientras la gobernanza no esté validada, política de confinamiento del revisor por nivel, y commit revisado dentro del rango del PR.
76
+
77
+ Límite honesto, heredado del protocolo: la máquina detecta el campo vacío y el marcador genérico, no la declaración falsa. El muestreo humano de PR cerrados sigue siendo la única defensa real contra el cumplimiento cosmético.
78
+
79
+ ## Qué no hace
80
+
81
+ 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 `minimizado` del artefacto permite ambientes donde ni siquiera el texto de los hallazgos puede salir del entorno.
82
+
83
+ ## Conformidad entre implementaciones
84
+
85
+ `spec/vectores/` contiene los vectores de conformidad: 22 artefactos con su veredicto esperado (valido o no, y las etiquetas de regla que deben dispararse). Toda implementacion del validador tiene que pasarlos identicos: la referencia en Python los corre en la suite (`tests/test_vectores.py`) y el port TypeScript del plano de evidencia los corre con `npm run conformidad`. Se comparan etiquetas, no mensajes. Los vectores se regeneran con `python -m disensor.vectores spec/vectores`.
86
+
87
+ `plano-evidencia/` contiene el Worker de ingesta (Cloudflare Workers mas D1) con el port TypeScript del validador y el recibo de integridad de solo agregado. Ver su README para el estado de verificacion y el despliegue.
88
+
89
+ ## Estado
90
+
91
+ v0.1, borrador en uso. El esquema puede cambiar hasta v1.0; los cambios se declaran en el propio esquema. Dos decisiones abiertas antes de v0.2: claves del esquema en español o inglés, y licencia definitiva (hoy MIT; Apache-2.0 está en consideración por la concesión de patentes antes del release público).
92
+
93
+ ## Licencia
94
+
95
+ MIT.
@@ -0,0 +1,15 @@
1
+ disensor/__init__.py,sha256=ZFrFd6fxbpAtqdhi5sj4RUH-lJSf3fxbVGaFvLixyig,110
2
+ disensor/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
3
+ disensor/cli.py,sha256=na88_YQB9MhGHb4cn1JLjbrnY2xsAPsS8t2SuN6Pd18,2394
4
+ disensor/gate.py,sha256=NsP1hrzV1lxVplJJHmMxmzH5XmA6Y1xKu6zJE6Qnv20,9869
5
+ disensor/plantilla.py,sha256=R6oXu8FV1R99IBX9Yv8dnmdFB-FW_oPB-lRz0WjoGJo,3260
6
+ disensor/reglas.py,sha256=fO9aEKg5-xggDF34Qb0JiWjLXydR5-x6tHChtdSjBcg,9055
7
+ disensor/render.py,sha256=XlFOgRUn2Aj8dUB1CK1f34OLGOEZYkdVQaJsGlW1khM,5334
8
+ disensor/residuo.schema.json,sha256=mHWXZCQqFKlw0E2thaDo-sijq-JZFJVyFrtQql1cFV0,19204
9
+ disensor/vectores.py,sha256=_JjC2oV5IyIla3oi74fll35zyWckyGVOL3cGWo_9Bwg,7373
10
+ disensor-0.1.0.dist-info/licenses/LICENSE,sha256=t4qKwdMVRVCTTjV9XDHfnKKtw7yjc13NzGZl3UTFRdk,1072
11
+ disensor-0.1.0.dist-info/METADATA,sha256=8ZNJFnlm69A45UgyxPphyjVcv7odqsAvqJiAYYVzPk8,5552
12
+ disensor-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
13
+ disensor-0.1.0.dist-info/entry_points.txt,sha256=cbPFH8kO5rduMHYMxYgtG91W0ScAn7_bkqsQBWlOttU,47
14
+ disensor-0.1.0.dist-info/top_level.txt,sha256=HGsTycFBoef6Ov_p03H4FWufQWjP50AeIzDOCpE7rlQ,9
15
+ disensor-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ disensor = disensor.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nicolas Rocchia
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.
@@ -0,0 +1 @@
1
+ disensor