chore(metodo): C26 — cada commit dice QUIEN lo hizo

git no tiene concepto de sesion, pero el mensaje si lo controlamos. Un trailer
«Sesion: <rol>/<frente>» hace visibles en el log los filos del working tree
compartido. Se juzga desde un punto de adopcion sellado por repo, asi que la
historia anterior queda fuera por construccion.

Sesion: superadmin

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
sirxavor 2026-08-31 19:00:09 +02:00
parent b1301bbc91
commit 35a6b1ef7a
4 changed files with 76 additions and 4 deletions

View File

@ -1,5 +1,5 @@
fd01f9f41dfbe394fcf94dbda8f9fcaf35bae1368755b7e9c47a9c9c1d35f1ec metodo.md 5ff67472a10f22c9425a3d0a4395d32930010c65499a2a6ce4e958ea47311f6c metodo.md
c8b7746c26471b6b041abbea1583d2bf6e5b13020df63ca1b9503fa0b4757ec3 bin/metodo d911bec6c3b969cce80d0a5c500780dde348eb3b0e8d15bb4ef61dbce5c8181e bin/metodo
f89b71bdf31028b05075636c1763c964e379bc37a00e0357d1c23a4dd19c09aa workflows/README.md f89b71bdf31028b05075636c1763c964e379bc37a00e0357d1c23a4dd19c09aa workflows/README.md
cc241bbecf9647c747b8525b3a6b527455cd50ca0312caa44bc3544342c8ac41 workflows/comunicacion-entre-sesiones.md cc241bbecf9647c747b8525b3a6b527455cd50ca0312caa44bc3544342c8ac41 workflows/comunicacion-entre-sesiones.md
2b9037f6dd57b5598c70a676cb728556654f05d1c5b024e7b716864659f7b884 workflows/sesion-ejecucion.md 2b9037f6dd57b5598c70a676cb728556654f05d1c5b024e7b716864659f7b884 workflows/sesion-ejecucion.md

View File

@ -3,3 +3,4 @@ master: metodo
version: 2026-08-30 version: 2026-08-30
estado: vendored estado: vendored
adopted_commit: 6a10eab342a477846ab5f3e67f8ab17169f12807 adopted_commit: 6a10eab342a477846ab5f3e67f8ab17169f12807
sesion_desde: b1301bbc911b9dbf750654d841b56ca0de9ee615

View File

@ -111,6 +111,34 @@ _huella_reparto() { { tr -d '\r' < "$SELF_ROOT/metodo.md"; tr -d '\r' < "$SELF_R
# ⚠️ Se ata lo decidible: que lo declarado EXISTA. ⛔ Lo contrario —cazar un fichero que está y no # ⚠️ Se ata lo decidible: que lo declarado EXISTA. ⛔ Lo contrario —cazar un fichero que está y no
# está declarado— NO se ata, y se dice por qué: exigiría una definición de «línea base» del corpus # está declarado— NO se ata, y se dice por qué: exigiría una definición de «línea base» del corpus
# que hoy no existe, y un gate que la adivinara daría rojos crónicos. Queda como criterio humano. # que hoy no existe, y un gate que la adivinara daría rojos crónicos. Queda como criterio humano.
# ⛔ C26 · QUIÉN LO HIZO (2026-08-31, idea de Xavier). `git` no tiene concepto de sesión: sólo ve un
# working tree y un usuario, el mismo para todas. **El mensaje sí lo controlamos.** Un trailer con el
# ROL —no el nombre de sesión, que rota en cada reinicio— convierte los cuatro filos del árbol
# compartido en algo VISIBLE: un arrastre, un --amend ajeno o un push de lo de otra se leen en el log.
# Sesion: <rol>/<frente> p.ej. Sesion: orquestadora/securizacion · Sesion: superadmin
# Se juzga desde `sesion_desde:` de .metodo/VERSION (sellado la primera vez que este check llega al
# repo) ⇒ la historia anterior queda FUERA POR CONSTRUCCIÓN, no por una lista de excepciones que
# crecería para siempre.
check_sesion_trailer() {
local md="$ROOT/.metodo" desde n mal
[ -d "$md" ] || md="$ROOT/.metodo"
git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1 || { skip "no es repo git — sin commits que medir"; return 0; }
hdr "cada commit dice QUIÉN lo hizo (C26)"
desde=$(grep -E '^sesion_desde:' "$md/VERSION" 2>/dev/null | sed 's/sesion_desde:[[:space:]]*//')
[ -n "$desde" ] || { info "sin punto de adopción todavía — se sella en el próximo 'metodo update'"; return 0; }
git -C "$ROOT" cat-file -e "$desde^{commit}" 2>/dev/null || { skip "el punto de adopción $desde no existe en este repo"; return 0; }
n=0; mal=0
while read -r sha; do
[ -z "$sha" ] && continue
n=$((n+1))
git -C "$ROOT" log -1 --format='%B' "$sha" | grep -qE '^Sesion:[[:space:]]*\S' || {
bad "commit sin 'Sesion:' — $(git -C "$ROOT" log -1 --format='%h %s' "$sha" | cut -c1-70) (C26)"; mal=$((mal+1)); }
done <<EOF
$(git -C "$ROOT" rev-list "$desde..HEAD" 2>/dev/null)
EOF
[ "$mal" -eq 0 ] && ok "$n commit(s) desde la adopción, todos dicen quién los hizo"
}
check_incremento() { check_incremento() {
local f="$ROOT/.metodo/INCREMENTO" local f="$ROOT/.metodo/INCREMENTO"
hdr "incrementos declarados (lo que este repo tiene DE MÁS)" hdr "incrementos declarados (lo que este repo tiene DE MÁS)"
@ -918,7 +946,11 @@ cmd_init() {
adopt=$(git -C "$tgt" rev-parse HEAD 2>/dev/null || echo "") # marca de adopción brownfield adopt=$(git -C "$tgt" rev-parse HEAD 2>/dev/null || echo "") # marca de adopción brownfield
{ echo "# Copia vendorizada del Método (raíl C11). Gestionada por 'metodo init/update'." { echo "# Copia vendorizada del Método (raíl C11). Gestionada por 'metodo init/update'."
echo "master: metodo"; echo "version: ${mver:-?}"; echo "estado: vendored" echo "master: metodo"; echo "version: ${mver:-?}"; echo "estado: vendored"
[ -n "$adopt" ] && echo "adopted_commit: $adopt"; } > "$md/VERSION" [ -n "$adopt" ] && echo "adopted_commit: $adopt"
# C26: punto de adopción del trailer de sesión — se sella UNA vez y se preserva.
_sd=$(grep -E "^sesion_desde:" "$md/VERSION" 2>/dev/null | sed "s/sesion_desde:[[:space:]]*//")
[ -z "$_sd" ] && _sd=$(git -C "$tgt" rev-parse HEAD 2>/dev/null)
[ -n "$_sd" ] && echo "sesion_desde: $_sd"; } > "$md/VERSION.new" && mv "$md/VERSION.new" "$md/VERSION"
_write_checksums "$md" || echo " (aviso: sin sha256 → CHECKSUMS no escrito; la deriva no se medirá)" _write_checksums "$md" || echo " (aviso: sin sha256 → CHECKSUMS no escrito; la deriva no se medirá)"
printf '* text eol=lf\n' > "$md/.gitattributes" # un clone en Windows no mete CRLF ni falsea el checksum printf '* text eol=lf\n' > "$md/.gitattributes" # un clone en Windows no mete CRLF ni falsea el checksum
[ -f "$md/realimentacion.md" ] || cat > "$md/realimentacion.md" <<EOF [ -f "$md/realimentacion.md" ] || cat > "$md/realimentacion.md" <<EOF
@ -970,7 +1002,11 @@ cmd_update() {
adopt=$(grep -E '^adopted_commit:' "$md/VERSION" 2>/dev/null | sed 's/adopted_commit:[[:space:]]*//') # se preserva adopt=$(grep -E '^adopted_commit:' "$md/VERSION" 2>/dev/null | sed 's/adopted_commit:[[:space:]]*//') # se preserva
{ echo "# Copia vendorizada del Método (raíl C11). Gestionada por 'metodo init/update'." { echo "# Copia vendorizada del Método (raíl C11). Gestionada por 'metodo init/update'."
echo "master: metodo"; echo "version: ${mver:-?}"; echo "estado: vendored" echo "master: metodo"; echo "version: ${mver:-?}"; echo "estado: vendored"
[ -n "$adopt" ] && echo "adopted_commit: $adopt"; } > "$md/VERSION" [ -n "$adopt" ] && echo "adopted_commit: $adopt"
# C26: punto de adopción del trailer de sesión — se sella UNA vez y se preserva.
_sd=$(grep -E "^sesion_desde:" "$md/VERSION" 2>/dev/null | sed "s/sesion_desde:[[:space:]]*//")
[ -z "$_sd" ] && _sd=$(git -C "$tgt" rev-parse HEAD 2>/dev/null)
[ -n "$_sd" ] && echo "sesion_desde: $_sd"; } > "$md/VERSION.new" && mv "$md/VERSION.new" "$md/VERSION"
_write_checksums "$md" || echo " (aviso: sin sha256 → CHECKSUMS no escrito)" _write_checksums "$md" || echo " (aviso: sin sha256 → CHECKSUMS no escrito)"
printf '* text eol=lf\n' > "$md/.gitattributes" printf '* text eol=lf\n' > "$md/.gitattributes"
_stamp_gitattributes "$tgt" _stamp_gitattributes "$tgt"
@ -999,6 +1035,7 @@ cmd_check() {
printf 'metodo check — %s\n' "$ROOT" printf 'metodo check — %s\n' "$ROOT"
check_drift check_drift
check_apendices_no_viajan check_apendices_no_viajan
check_sesion_trailer
check_incremento check_incremento
check_reparto_al_dia check_reparto_al_dia
check_semaforo_forma check_semaforo_forma

View File

@ -1906,6 +1906,40 @@ pega tal cual.
no ha salido** — y saberlo exige **mirar `git log -1` antes**, no suponer que HEAD es lo último que no ha salido** — y saberlo exige **mirar `git log -1` antes**, no suponer que HEAD es lo último que
escribiste tú. ⛔ **Y el rechazo del `push` NO se resuelve forzando**: se resuelve con escribiste tú. ⛔ **Y el rechazo del `push` NO se resuelve forzando**: se resuelve con
`git reset --soft @{u}` y recommiteando **lo tuyo solo**, que deja el commit ajeno intacto. `git reset --soft @{u}` y recommiteando **lo tuyo solo**, que deja el commit ajeno intacto.
⛔⛔ **Y LA PROPIEDAD QUE UNE A LOS CUATRO, Y DICE CUÁNDO DEJAR DE BUSCAR UN GATE: `git` ES LA ÚNICA
HERRAMIENTA COMPARTIDA DEL ÁRBOL QUE NO TIENE EL CONCEPTO DE SESIÓN.** El semáforo sabe quién eres.
El canal sabe quién eres. El linter sabe en qué repo estás. **`git` sólo ve un working tree y un
usuario** — y en este árbol **ese usuario es el mismo para todas**.
⇒ Por eso **todos los filos aparecen ahí**, y por eso **ninguno lo caza un gate: no hay dato con el
que un gate pudiera distinguirlos.** ⭐ Y eso **no es una excusa: es el criterio para parar**
buscarle un gate a esto es construir un instrumento sin sujeto, que es la avería que este Método más
persigue. **Lo que sí se puede es avisar antes y decirlo después.**
⛔ **Corolario, y es el más traicionero: `--amend` en un árbol compartido NO es una operación local.**
Su único testigo es **un `push` rechazado**, o sea **un fallo que puede no llegar a producirse**. ⇒
**`--amend` sólo es seguro sobre un `HEAD` que has VERIFICADO que sigue siendo tuyo** —`git log -1`—,
nunca sobre el que recuerdas haber hecho.
⭐⭐ **Y LA MITAD QUE SÍ SE PUEDE ATAR, PORQUE EL MENSAJE SÍ LO CONTROLAMOS: CADA COMMIT DICE QUIÉN LO
HIZO.** `git` no tiene concepto de sesión — pero **el mensaje es nuestro**. Un *trailer* con el **ROL**
convierte los cuatro filos de arriba en algo **visible en el `log`**: un arrastre, un `--amend` ajeno
o un `push` que publica lo de otra **se leen**, en vez de descubrirse por un rechazo que puede no
llegar.
Sesion: <rol>/<frente> p. ej. Sesion: orquestadora/securizacion
Sesion: ejecucion/hermes-infra
Sesion: superadmin
**El ROL, no el nombre de sesión**: los nombres **rotan en cada reinicio** y el usuario **no los
ve** — un `log` lleno de identificadores caducados no identifica a nadie. El rol **sobrevive al
reinicio y significa algo dentro de seis meses**, que es cuando se lee un `log`.
⇒ Se ata (`C26`) **desde un punto de adopción sellado por repo**, así que **la historia anterior queda
fuera POR CONSTRUCCIÓN** — no por una lista de excepciones que crecería para siempre. *(Es la lección
del rojo crónico aplicada al diseñar el gate, no después de sufrirlo.)*
**Y lo que esto arregla de fondo**: hasta aquí, la única atribución que existía era **el mensaje**,
y por eso *«un commit ajeno bajo un mensaje que habla de otra cosa borra la única atribución que
hay»*. Con el trailer, **la atribución deja de depender de que el mensaje la mencione de pasada**:
está en un campo, es constante, y **se puede contar**.
- La **representación en disco es parte del artefacto**: cuando un hash o un runtime consume el - La **representación en disco es parte del artefacto**: cuando un hash o un runtime consume el
checkout, se fija con el repo — **eol** por `.gitattributes` y **bit de ejecución** — o el testigo checkout, se fija con el repo — **eol** por `.gitattributes` y **bit de ejecución** — o el testigo
mide el checkout, no el contenido (una DERIVA falsa en un clone sin tocar entrena a ignorar el mide el checkout, no el contenido (una DERIVA falsa en un clone sin tocar entrena a ignorar el