fix(short): dos citas en el campo de una son una frase que nadie dijo
Build & Deploy ResearchOwl / build-and-push (push) Successful in 8s

El comprobador de fundamento une las líneas de `quote` antes de buscarlas, y eso
cerró la forma con la que falló Socorro en agosto (`366ded1`): `“LIKE ALUMINUM` +
`SMOOTH, NO WINDOWS”` unidas son una sola frase, no aparece en ninguna fuente, y
se rechaza.

Pero la unión se derrota poniéndole a cada línea su propio par de comillas.
Entonces son DOS citas, cada una fundamentada por su lado, y pasa en silencio
mientras el fotograma dibuja la frase de nadie. Medido sobre los cinco Shorts
generados hoy: le pasaba a dos.

Se comprueba la FORMA, no el contenido — dos aperturas son dos citas, diga lo
que diga la fuente — y se comprueba en `validate_spec`, o sea que cuesta un
reintento y no una revisión humana. Es regla del canal, no de shortsmith: allí
renderiza igual. Va como error duro y no como nota editorial porque el fallo que
evita es el peor que tiene este sistema: una cita fabricada con material
auténtico y atribuida a alguien con nombre y apellidos.

La comprobación es universal por nombre de prop, no por plantilla: una plantilla
nueva con un `quote` de varias líneas queda cubierta sin tocar nada.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ChemaVX
2026-08-13 21:47:07 +00:00
co-authored by Claude Opus 5
parent 818533c86f
commit 6e4b3e1379
2 changed files with 82 additions and 1 deletions
+39
View File
@@ -246,6 +246,45 @@ class TestCrossFieldRules:
"waypoints": [{"label": "A", "lat": 2.0, "lon": -1.0}]}}),
templates)
def test_two_quotes_welded_into_one_field_are_rejected(self):
"""El peor fallo del sistema: una frase que nadie dijo, hecha con
material auténtico y firmada por alguien con nombre y apellidos.
El comprobador de fundamento une las líneas antes de buscarlas, y eso
caza la forma con la que falló Socorro. Pero la unión se derrota
poniéndole a cada línea su propio par de comillas: entonces son dos
citas, cada una fundamentada por su lado, y pasa en silencio. Caso real
de la sesión 162.
"""
errors = errors_of(spec_with(shot(
"scale_bars", duration=25.0,
bars=[{"label": "A", "value": 1}],
quote=["“GRAY, LIKE ZINC”", "“TWO SAUCERS GLUED AT THE RIM”"])))
assert any("es UNA cita partida en líneas" in e for e in errors)
def test_a_span_broken_across_lines_is_the_normal_case(self):
"""La forma buena: abre en la primera línea y cierra en la última. Es
como está escrito el ejemplo de referencia, así que rechazarla rompería
el propio prompt."""
validate_spec(spec_with(shot(
"scale_bars", duration=25.0,
bars=[{"label": "A", "value": 1}],
quote=["“TWICE THE SIZE OF", "AN AIRCRAFT CARRIER”"])), TEMPLATES)
def test_a_quote_without_marks_is_left_alone(self):
validate_spec(spec_with(shot(
"scale_bars", duration=25.0,
bars=[{"label": "A", "value": 1}],
quote=["LANDING TRACE", "CONFIRMED BY LAB"])), TEMPLATES)
def test_the_reference_example_survives_the_quote_rule(self):
"""Si el ejemplo no pasara su propia regla, volveríamos a enseñar el
fallo que la regla intenta evitar."""
spec = json.loads(EXAMPLE.read_text())
permissive = {name: {"type": "object"} for name in
{s["template"] for s in spec["shots"]}}
validate_spec(spec, permissive)
def test_a_bad_schema_hides_the_cross_field_noise(self):
"""Con props mal tipadas, la regla cruzada diría algo que no es el fallo
real y taparía el que sí lo es."""