Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,11 @@ jobs:
# crash es R6: el decoder de yrs amplifica memoria sobre un update malformado de pocos bytes
# (`with_capacity(N)` sin cota → `handle_alloc_error`, que catch_unwind no puede contener). El shim
# es correcto (contiene panics, sin UB); el fallo está aguas abajo, en yrs. La mitigación llegó en
# CHARTER-08 (PR upstream y-crdt#639 + caveat en GOVERNANCE.md §Seguridad); lo que resta es
# adoptar el fix vía bump (FU-015). Ver AILOG-2026-07-10-001 §R6.
# CHARTER-08 (PR upstream y-crdt#639 + caveat en GOVERNANCE.md §Seguridad). El PR está
# MERGEADO upstream (2026-08-05) pero NO publicado: crates.io sigue en yrs 0.27.3, anterior al
# merge, y nuestro pin es =0.27.2 — así que el crash se sigue reproduciendo aquí y el trato
# informativo sigue vigente. Lo que resta es adoptar el fix vía bump cuando salga el release
# (FU-015). Ver AILOG-2026-07-10-001 §R6.
fuzz:
name: fuzz (smoke; crash informativo)
runs-on: ubuntu-latest
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
id: AILOG-2026-08-05-001
title: "y-crdt#639 mergeado — reconciliación del estado de R6 en los docs vivos y en FU-015"
status: accepted
created: 2026-08-05
agent: claude-opus-5
confidence: high
review_required: false
risk_level: low
eu_ai_act_risk: not_applicable
nist_genai_risks: []
iso_42001_clause: []
observability_scope: none
tags: [r6, upstream, y-crdt, follow-ups, seguridad, docs, charter-08]
related: [AILOG-2026-07-14-002, AIDEC-2026-07-14-001, AILOG-2026-07-10-001]
---

# AILOG: y-crdt#639 mergeado — reconciliación del estado de R6

## Summary

El PR upstream **`y-crdt/y-crdt#639`** (entregable (a) de CHARTER-08, endurecimiento del decoder de
`yrs` contra amplificación de asignación) **se mergeó el 2026-08-05 10:51 UTC**. El repo describía su
estado como «aprobado / sigue abierto» en cinco sitios vivos; este AILOG los reconcilia.

**Nada de código cambia**: el pin `yrs = "=0.27.2"` se queda donde está. El merge **no** dispara
FU-015, porque su trigger es «se mergee **y publique**» y crates.io sigue en `yrs 0.27.3` (13-jul),
anterior al merge. El follow-up pasa de «bloqueado por revisión upstream» a «esperando release», sin
cambiar de status.

## Context

Datos verificados contra la API de GitHub y el `raw` de `main`, no contra la memoria del agente:

- Merge commit `3074c84d`; nuestro commit `2ee533eb` entró **intacto** — ningún cambio pedido desde el
APPROVED de Horusiath del 15-jul (21 días de latencia entre aprobación y merge).
- 4 archivos, 5 sitios, +105/−6: `any.rs`, `id_set.rs`, `state_vector.rs`, `sync/awareness.rs`.
- `curl` a `crates.io/api/v1/crates/yrs` → `updated_at: 2026-07-13` → **sin release** que lo contenga.

Un segundo hecho apareció en la revisión: **`y-crdt#644`** (abierto
por un tercero el 04-ago) fue marcado por el maintainer un minuto después del merge con *«#639 seems to
cover the same issue»*. Es cierto **a medias**:

- `any.rs` casos 117/118 → duplicado exacto de lo que acaba de entrar (mismo `try_reserve`).
- Los dos `from_utf8_unchecked` (`encoding/read.rs:137`, `updates/decoder.rs:486`) → **#639 no los
toca y siguen vivos en `main`** (verificado leyendo el raw post-merge).

Y son una **clase distinta**: no un fallo de asignación sino UB / abort no-desenrollable. El
`catch_unwind` de cada entrada del shim — Principio I de la constitución — es estructuralmente ciego a
esa clase, igual que lo era a `handle_alloc_error`. Si #644 se cierra en bloque como duplicado, la
mitad del UTF-8 se va con él.

## Actions Performed

1. **Reconciliación del estado en los cinco sitios vivos** («aprobado» → «mergeado el 2026-08-05, sin
publicar; crates.io en 0.27.3»). Cambio mínimo: solo donde la afirmación pasó a ser **falsa**.
2. **`FU-015` vía CLI** (`straymark followups verify` + `note`, nunca a mano — §13):
- **Premisa re-registrada y `Verified-at: 2026-08-05`**. La premisa original arrastraba una parte
**muerta desde el día 1** — «revertir el fork `StrangeDaysTech/y-crdt`» — cuando el repo **nunca
consumió ese fork**: `native/**/Cargo.toml` pide `yrs` de crates.io, sin `[patch.crates-io]` ni
dependencias git. La premisa nueva nombra los **3 sitios reales** del bump
(`weft-yrs-ffi/Cargo.toml:17,21` + `fuzz/Cargo.toml:12`) y exige **probar** el fix
(`cargo +nightly fuzz run -s none export_since` → RSS acotado), no asumirlo.
- **Status sigue `open`.** No hay release que adoptar.
3. **Comentario publicado en `y-crdt#644`**
([issuecomment-5199361207](https://github.com/y-crdt/y-crdt/pull/644#issuecomment-5199361207),
2026-08-06 01:30 UTC, cuenta `montfort`). Acota qué cubre #639 y qué no, con las dos líneas de
`main` citadas, para que la decisión sobre #644 se tome con el alcance real. **La premisa se
re-verificó contra `main` justo antes de publicar** (`read.rs:137` y `decoder.rs:486` seguían
intactas; #644 abierto, con el comentario del maintainer como única réplica). El borrador de
trabajo se descartó tras publicar — el texto canónico es el propio comentario de GitHub, enlazado
arriba; no se deja copia en el repo.

## Modified Files

| File | Lines Changed (+/-) | Change Description |
|------|--------------------|--------------------|
| `docs/architecture.md` | +6/−2 | §Known limits, R6: «submitted upstream, approved» → merged 2026-08-05, pendiente de release |
| `GOVERNANCE.md` | +5/−2 | §Security, caveat R6: el fix canónico upstream está mergeado, no publicado |
| `specs/001-weft-crdt-versioning/quickstart.md` | +2/−1 | Fila «Fuzzing» de la tabla de gates |
| `specs/001-weft-crdt-versioning/checklists/requirements.md` | +2/−1 | Ítem Fuzzing (P-I/P-II) |
| `.github/workflows/ci.yml` | +7/−2 | Comentario de bloque del job `fuzz`: por qué el trato informativo **sigue** vigente pese al merge (pin `=0.27.2`, sin release) |
| `.straymark/follow-ups-backlog.md` | +4/−1 | FU-015: premisa corregida, `Verified-at`, anotación del movimiento (escrito por el CLI) |

**Deliberadamente sin tocar**: los strings `::warning` del job `fuzz` — siguen siendo ciertos, porque el
fix no está en nuestro pin y la adopción sigue siendo FU-015. Tampoco `native/**` ni ningún `Cargo.toml`.
Tampoco el registro histórico (`.straymark/charters/12-*.md:75` dice «#639 sigue abierto» y era cierto
al emitirse; los AILOG/AIDEC previos, igual).

## Decisions Made

Ninguna arquitectónica — no procede AIDEC. Dos decisiones de alcance, ambas del operador:

1. **No bumpear.** Sin release publicado no hay nada que adoptar, y el valor de FU-015 está en
**probar** el fix con el fuzz, cosa imposible hoy.
2. **No registrar follow-up propio para la clase UTF-8; sí plantearla upstream.** Queda anotada en
FU-015 y argumentada en el hilo de #644, donde el fix ya existe con tests de un tercero — abrir
entrada propia duplicaría trabajo ajeno. Consecuencia asumida: **el desenlace depende del
maintainer**. Si #644 muere sin merge y esos dos sitios se quedan en `main`, procede reabrir la
decisión (entrada propia y/o PR).

## Impact

- **Functionality**: ninguna. Cero cambios de código o de dependencias.
- **Performance**: N/A.
- **Security**: la postura declarada deja de estar desfasada respecto a upstream, y el caveat público de
`GOVERNANCE.md` §Security gana precisión (mergeado ≠ disponible). La exposición real **no cambia**:
el pin sigue en `=0.27.2`, y las mitigaciones del relay (cap de tamaño + límites por conexión,
FU-002) siguen siendo la defensa efectiva. Queda **documentada** una segunda clase, hermana de R6:
UB por UTF-8 no validado, igualmente opaca a `catch_unwind`.
- **Privacy**: N/A.
- **Environmental**: N/A.

## Verification

- [x] Code compiles without errors — sin cambios de código; `ci.yml` sigue parseando como YAML válido
- [x] Tests pass — N/A (sin cambios de código); `git diff` limpio en `native/**` y en los `Cargo.toml`
- [x] Manual review performed — cada afirmación contrastada contra la API de GitHub, `crates.io` y el
`raw` de `main`; `straymark followups status` sigue en **1 open / 20 total**
- [ ] Security scan passed — N/A (`risk_level: low`)
- [ ] Privacy review completed — N/A

## Additional Notes

**Lección que se repite** (cf. AIDEC-2026-07-18-001, «un follow-up es una hipótesis fechada que
decae»): FU-015 llevaba 22 días pidiendo revertir un fork que nunca se usó. La premisa era falsa **desde
que se escribió**, no se pudrió con el tiempo — y solo se detectó al ir a ejecutar el follow-up, que es
donde verificar cuesta casi nada. Es el mismo patrón que desmontó FU-016/017/018/019/020 en el vaciado
del backlog.

**Disparador pendiente de FU-015**: cuando `yrs 0.27.4`/`0.28` aparezca en crates.io — bump de los 3
pins, `cargo +nightly fuzz run -s none export_since` (que **prueba** el fix, RSS acotado) y revisar si
los `::warning` del job `fuzz` pueden endurecerse a rojo.

---

<!-- Template: StrayMark | https://strangedays.tech -->
4 changes: 3 additions & 1 deletion .straymark/follow-ups-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ fully_extracted_ailogs:
- **Trigger**: when el PR upstream #639 se mergee y publique en un release de crates.io
- **Destination**: chore
- **Cost**: S
- **Notes**: Bump `yrs = "=0.27.x"` en `native/weft-yrs-ffi/Cargo.toml` (+ el crate de fuzz) a la versión con el fix de R6; re-correr el fuzz `export_since` (debe pasar a RSS acotado, probando el fix) y revertir el fork `StrangeDaysTech/y-crdt` (volver a consumir yrs de crates.io). No bloquea el cierre de CHARTER-08 (entregable diferido por diseño, fuera de nuestro control: timeline de revisión de y-crdt).
- **Notes**: Bump `yrs = "=0.27.x"` en `native/weft-yrs-ffi/Cargo.toml` (+ el crate de fuzz) a la versión con el fix de R6; re-correr el fuzz `export_since` (debe pasar a RSS acotado, probando el fix) y revertir el fork `StrangeDaysTech/y-crdt` (volver a consumir yrs de crates.io). No bloquea el cierre de CHARTER-08 (entregable diferido por diseño, fuera de nuestro control: timeline de revisión de y-crdt). · [2026-08-05 · AILOG-2026-08-05-001] Movimiento upstream: y-crdt#639 MERGEADO el 2026-08-05 10:51 UTC (merge commit `3074c84d`, commit `2ee533eb` intacto — sin cambios pedidos tras el APPROVED del 15-jul). El trigger sigue SIN cumplirse: no hay release — crates.io publica `yrs 0.27.3` (13-jul), anterior al merge. Status sigue `open`; pasa de «bloqueado por revisión upstream» a «esperando release». Hallazgo colateral: y-crdt#644 (tercero) arregla dos `from_utf8_unchecked` (`encoding/read.rs:137`, `updates/decoder.rs:486`) que #639 NO toca y siguen vivos en main — clase abort/UB no contenible por `catch_unwind`, distinta de la amplificación de asignación. No se registra follow-up propio por decisión del operador. · [2026-08-05 · AILOG-2026-08-05-001] Alcance de la sesión del 2026-08-05: NO se intervino upstream en #644 (decisión del operador). La clase UTF-8 (`from_utf8_unchecked`, abort/UB opaco a `catch_unwind`) queda anotada aquí pero SIN entrada propia y SIN réplica en el hilo de #644, que sigue marcado como posible duplicado. Si #644 muere sin merge y esos dos sitios siguen en `main`, procede reabrir la decisión. · [2026-08-05 · AILOG-2026-08-05-001] CORRIGE la anotación anterior de hoy: SÍ se intervino upstream. Comentario publicado en y-crdt#644 (issuecomment-5199361207, 2026-08-06 01:30 UTC) acotando que #639 cubre `any.rs` 117/118 pero NO los dos `from_utf8_unchecked`, re-verificados vivos en `main` (`encoding/read.rs:137`, `updates/decoder.rs:486`) justo antes de publicar. Sigue SIN entrada propia en el backlog: el fix ya existe en #644 con tests de un tercero. Si #644 muere sin merge y esos sitios siguen en `main`, procede reabrir la decisión.
- **Premise**: El fix de R6 llega a Weft por un bump del pin `yrs = "=0.27.2"` a la primera versión PUBLICADA en crates.io que contenga y-crdt#639 — 3 sitios: `native/weft-yrs-ffi/Cargo.toml:17,21` y `native/weft-yrs-ffi/fuzz/Cargo.toml:12`. El fix se PRUEBA, no se asume: re-correr `cargo +nightly fuzz run -s none export_since` debe pasar a RSS acotado. NO hay fork que revertir — el repo siempre consumió yrs de crates.io (sin `[patch.crates-io]` ni dependencias git); esa parte de la nota original era una premisa muerta desde el día 1.
- **Verified-at**: 2026-08-05

### FU-002 — R6 (CHARTER-01): hardening del decoder ante amplificación de memoria (DoS)
- **Origin**: AILOG-2026-07-10-001 §R6 (new, not in Charter)
Expand Down
5 changes: 3 additions & 2 deletions GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ recoverable error, not abort), so `apply_update` is hardened upstream; two resid
unbounded `with_capacity` (decode of *delete sets* and of *state vectors*, the latter reachable via
`export_since`). On `glibc` (overcommit) the practical effect is a virtual reservation and a **clean
decode error**, not a crash; the non-catchable `abort` only appears on hard memory-constrained hosts or
eager allocators. The canonical fix lives upstream (the `try_reserve` PR to `y-crdt`); a regression fuzz
target tracks the residual.
eager allocators. The canonical fix lives upstream (the `try_reserve` PR to `y-crdt`, **merged
2026-08-05**, not yet in a published release — crates.io still ships `yrs 0.27.3`); a regression fuzz
target tracks the residual until a release carries it.

## License

Expand Down
6 changes: 4 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,10 @@ Better to state them than to discover them in production:
- **R6 — `yrs` decoder memory amplification.** A malformed update of a few bytes can declare an
enormous length and make `yrs` reserve without bound; the allocation fails and the process aborts
(`handle_alloc_error`, **not** catchable by `catch_unwind`). The shim is correct — it contains
panics, no UB; the failure is downstream. The fix is submitted upstream
([y-crdt#639](https://github.com/y-crdt/y-crdt/pull/639), approved) and will be adopted via a bump.
panics, no UB; the failure is downstream. The fix is **merged upstream**
([y-crdt#639](https://github.com/y-crdt/y-crdt/pull/639), merged 2026-08-05) but **not yet
released**: crates.io still ships `yrs 0.27.3`, which predates the merge. It will be adopted via
a bump once a release carries it.
**In the meantime**: the relay already protects itself with a size cap and a memory limit; if you
use the **direct** FFI path (`LoadDoc`/`ApplyUpdate`) with **untrusted** bytes, protect it the same
way. See [`GOVERNANCE.md`](../GOVERNANCE.md#security) (Security section).
Expand Down
2 changes: 1 addition & 1 deletion specs/001-weft-crdt-versioning/checklists/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Pase end-to-end de [quickstart.md](../quickstart.md) contra el árbol de `charte
- [x] **Determinismo (P-III)** — `dotnet test tests/Weft.Determinism.Tests` → 4/4, incluida la aserción **bloqueante** de paridad `Yrs_export_matches_yjs_golden`. Harness Node (`npm test`) → hash de Yjs coincide con `golden.json` en ascii (`27a84875…`) y unicode (`afd15f9c…`).
- [x] **Dual-engine (P-IV)** — suite de versionado verde sobre ambos motores (ver US5).
- [x] **Build + tests (P-VI)** — `dotnet test Weft.sln -c Release` → **132/132 verdes**; `cargo test --features test-hooks` → 14/14. Ejecutado en linux-x64; win-x64/osx-arm64 → jobs `test-win` / `test-mac`.
- [x] **Fuzzing (P-I/P-II)** — `cargo +nightly fuzz run doc_load -- -max_total_time=45` → **OOM reproducido** con un input de 4 bytes (`f6f4d621`). **Es el resultado esperado, no una regresión**: es R6; el shim es correcto (contiene panics, sin UB) y el fix vive upstream en [y-crdt#639](https://github.com/y-crdt/y-crdt/pull/639) (aprobado), adopción vía FU-015. En CI el job **bloquea a medias**: un crash sólo emite `::warning` (`|| echo` por paso), pero un fallo de compilación de los targets sí lo pone rojo. Caveat de la ruta directa en `GOVERNANCE.md` §Seguridad.
- [x] **Fuzzing (P-I/P-II)** — `cargo +nightly fuzz run doc_load -- -max_total_time=45` → **OOM reproducido** con un input de 4 bytes (`f6f4d621`). **Es el resultado esperado, no una regresión**: es R6; el shim es correcto (contiene panics, sin UB) y el fix vive upstream en [y-crdt#639](https://github.com/y-crdt/y-crdt/pull/639) (**mergeado el 2026-08-05**, aún **sin publicar**: crates.io sigue en `yrs 0.27.3`, anterior al merge), adopción vía FU-015 cuando salga el release. En CI el job **bloquea a medias**: un crash sólo emite `::warning` (`|| echo` por paso), pero un fallo de compilación de los targets sí lo pone rojo. Caveat de la ruta directa en `GOVERNANCE.md` §Seguridad.
- [x] **Empaquetado (P-VI)** — pack local verde + ausencia del símbolo de test verificada con `nm -D` (ver US4). **El gate real no corre por PR**: el `pack-smoke` de `ci.yml` es un marcador; la matriz por RID y la verificación del símbolo viven en `release.yml` (`workflow_dispatch`) → se validan en el dry-run del release. Ver gap #9.

### No ejecutado (sin evidencia — deliberadamente sin marcar)
Expand Down
2 changes: 1 addition & 1 deletion specs/001-weft-crdt-versioning/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Un rojo bloquea merge, **con dos excepciones** que conviene conocer antes de fia
| Memoria | `asan` (linux, nightly) | `RUSTFLAGS="-Zsanitizer=address" cargo +nightly test --target x86_64-unknown-linux-gnu` en ambos shims → 0 fugas/0 double-free | P-II |
| Determinismo | `determinism` | `dotnet test tests/Weft.Determinism.Tests` cross-RID. La paridad yrs↔Yjs es **bloqueante** y vive aquí (`Yrs_export_matches_yjs_golden`, contra `tests/determinism-yjs/golden.json`) desde CHARTER-09/FU-012 — la promoción que research R13 anticipaba ya ocurrió. El job Node `determinism-yjs` (`release.yml`, `continue-on-error`) es **informativo**: regenera el hash de Yjs para cazar drift del upstream, no es la aserción de paridad | P-III |
| Dual-engine | `dual-engine` | suite Versioning con ambos motores | P-IV |
| Fuzzing | `fuzz` (smoke 60 s/target en PR; extendido nightly) — **bloquea a medias** | `cargo fuzz run doc_load` / `apply_update`; CsCheck convergencia. Un fallo de **compilación** de los targets pone el job rojo (deliberado); un **crash encontrado** sólo emite `::warning` — es un `\|\| echo` por paso, **no** `continue-on-error` en el job. Razón: los targets **reproducen R6 hoy** (un input de ~4 B hace que el decoder de yrs reserve sin cota → `handle_alloc_error`, que `catch_unwind` no puede contener). El shim es correcto (contiene panics, sin UB); el fix vive upstream ([y-crdt#639](https://github.com/y-crdt/y-crdt/pull/639), aprobado) y se adopta vía bump (FU-015). Caveat de la ruta directa en `GOVERNANCE.md` §Seguridad (CHARTER-08) | P-I/P-II |
| Fuzzing | `fuzz` (smoke 60 s/target en PR; extendido nightly) — **bloquea a medias** | `cargo fuzz run doc_load` / `apply_update`; CsCheck convergencia. Un fallo de **compilación** de los targets pone el job rojo (deliberado); un **crash encontrado** sólo emite `::warning` — es un `\|\| echo` por paso, **no** `continue-on-error` en el job. Razón: los targets **reproducen R6 hoy** (un input de ~4 B hace que el decoder de yrs reserve sin cota → `handle_alloc_error`, que `catch_unwind` no puede contener). El shim es correcto (contiene panics, sin UB); el fix vive upstream ([y-crdt#639](https://github.com/y-crdt/y-crdt/pull/639), **mergeado el 2026-08-05** pero aún **sin publicar** — crates.io sigue en `yrs 0.27.3`, anterior al merge) y se adopta vía bump (FU-015) cuando salga el release. Caveat de la ruta directa en `GOVERNANCE.md` §Seguridad (CHARTER-08) | P-I/P-II |
| Empaquetado | `pack-smoke` — **no corre por PR** | El job `pack-smoke` de `ci.yml` es un **marcador** (sólo hace `echo`): no empaqueta ni valida nada. La matriz real (pack + instalar + hello-Weft por RID, SC-007) vive en `release.yml`, que es `workflow_dispatch` únicamente porque la matriz cross-compile es cara → se valida en el **dry-run del release**, no en cada PR. La verificación de que `weft_test_panic` no está exportado (SC-009) la hace el job **`native`** de ese mismo workflow, con `nm` sobre los cdylibs antes del pack | P-VI |

## Criterio de cierre por hito
Expand Down
Loading