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
+43 -1
View File
@@ -232,6 +232,48 @@ def _track_map_waypoints_inside(props: dict, path: str) -> list[str]:
return []
#: Comillas de apertura. Una cita bien partida abre UNA vez.
_OPENING_QUOTES = "“«„‟"
def _quote_is_one_span(props: dict, path: str) -> list[str]:
"""Una `quote` de varias líneas es UN span partido, no dos citas.
Esta regla NO es de shortsmith: allí renderiza igual. Es del canal, y es de
las duras, porque el fallo que evita es el peor que tiene este sistema —
una cita fabricada con material auténtico y atribuida a una persona con
nombre y apellidos.
El comprobador de fundamento ya une las líneas antes de buscarlas, y eso
cerró la forma con la que falló Socorro en agosto (`“LIKE ALUMINUM` +
`SMOOTH, NO WINDOWS”`): unidas son una sola frase, no aparece en ninguna
fuente, y se rechaza. Pero la unión se puede derrotar 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. Le pasó a la sesión 162 el 2026-08-13 con
`“GRAY, LIKE ZINC”` + `“TWO SAUCERS GLUED AT THE RIM”`.
Por eso se mira la FORMA y no el contenido: dos aperturas son dos citas,
diga lo que diga la fuente.
"""
quote = props.get("quote")
if not isinstance(quote, list) or len(quote) < 2:
return []
joined = " ".join(str(line) for line in quote)
openings = sum(joined.count(glyph) for glyph in _OPENING_QUOTES)
if openings < 2:
return []
return [f"{path}.quote: son {openings} citas, y este campo es UNA cita "
"partida en líneas — leídas seguidas forman una frase que nadie "
"dijo. Elige un solo span verbatim y pártelo donde tenga que "
"partirse, o quita las comillas y cuenta el hecho en llano"]
#: Comprobaciones que se aplican a TODAS las plantillas, por nombre de prop. Van
#: aparte de las de abajo para que una plantilla nueva con un campo `quote` de
#: varias líneas quede cubierta sin tocar nada — el mismo pacto que el contrato.
UNIVERSAL_CHECKS = [_quote_is_one_span]
#: template -> comprobaciones extra. Una plantilla sin entrada no tiene reglas
#: cruzadas, que es el caso de casi todas.
CROSS_FIELD_CHECKS = {
@@ -244,7 +286,7 @@ def _check_cross_field(template: str, props: Any, path: str) -> list[str]:
if not isinstance(props, dict):
return []
errors: list[str] = []
for check in CROSS_FIELD_CHECKS.get(template, ()):
for check in (*UNIVERSAL_CHECKS, *CROSS_FIELD_CHECKS.get(template, ())):
errors.extend(check(props, path))
return errors