diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1dd16e..97f63ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.straymark/07-ai-audit/agent-logs/AILOG-2026-08-05-001-y-crdt-639-mergeado-reconciliacion-estado.md b/.straymark/07-ai-audit/agent-logs/AILOG-2026-08-05-001-y-crdt-639-mergeado-reconciliacion-estado.md new file mode 100644 index 0000000..85316d5 --- /dev/null +++ b/.straymark/07-ai-audit/agent-logs/AILOG-2026-08-05-001-y-crdt-639-mergeado-reconciliacion-estado.md @@ -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. + +--- + + diff --git a/.straymark/follow-ups-backlog.md b/.straymark/follow-ups-backlog.md index 627b54c..957af87 100644 --- a/.straymark/follow-ups-backlog.md +++ b/.straymark/follow-ups-backlog.md @@ -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) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 0028cd7..d516349 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -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 diff --git a/docs/architecture.md b/docs/architecture.md index b75a26b..64d82ab 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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). diff --git a/specs/001-weft-crdt-versioning/checklists/requirements.md b/specs/001-weft-crdt-versioning/checklists/requirements.md index 49e7392..ae171c6 100644 --- a/specs/001-weft-crdt-versioning/checklists/requirements.md +++ b/specs/001-weft-crdt-versioning/checklists/requirements.md @@ -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) diff --git a/specs/001-weft-crdt-versioning/quickstart.md b/specs/001-weft-crdt-versioning/quickstart.md index cee1f72..bb5199c 100644 --- a/specs/001-weft-crdt-versioning/quickstart.md +++ b/specs/001-weft-crdt-versioning/quickstart.md @@ -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