diff --git a/website/blog/2026-07-18-what-the-follow-up-took-for-granted.md b/website/blog/2026-07-18-what-the-follow-up-took-for-granted.md new file mode 100644 index 00000000..1108c22a --- /dev/null +++ b/website/blog/2026-07-18-what-the-follow-up-took-for-granted.md @@ -0,0 +1,88 @@ +--- +slug: what-the-follow-up-took-for-granted +title: What the follow-up took for granted +authors: + - jose +tags: + - straymark + - governance + - followups + - charters + - cli +draft: false +date: 2026-07-18T00:00:00.000Z +description: A greenfield adopter cleared a backlog of follow-ups before shipping and found that three of them — the three that were real work — each carried a premise that was false the moment you checked it. Not because the code had changed under them, but because a follow-up is written at the one moment you are least equipped to verify it. The lesson isn't "write better follow-ups." It's that a follow-up is a dated hypothesis, and the cheap place to test it is when you read it, not when you write it — so that is where StrayMark now puts the check. +--- + +*An adopter cleared a follow-up backlog before shipping — seven entries down to one. Four were already-resolved noise. Of the three that were real, unresolved work, every single one carried a false premise: a test to "replicate" that never existed, a gate to build against a reference that can't exist, an optimization to fix a cost that wasn't there. None of the three was wrong because the code drifted under it. They were wrong the moment they were written, and the falseness only surfaced on a thirty-second check performed months later. That timing is the whole story. A follow-up is authored at the exact moment you are least able to verify it — and read at a moment when verifying is nearly free. StrayMark's registry used to treat entries as instructions to execute. They are better understood as dated hypotheses to re-test — and, as of this release, that is what the tool says and where it puts the check.* + + + +## The setup + +The adopter is the same greenfield .NET 10 / Rust CRDT library behind [#345](https://github.com/StrangeDaysTech/straymark/issues/345)/[#346](https://github.com/StrangeDaysTech/straymark/issues/346)/[#355](https://github.com/StrangeDaysTech/straymark/issues/355)/[#360](https://github.com/StrangeDaysTech/straymark/issues/360) — the [Adopter] Weft discussion. Milestone 3 was code-complete except for the operator-gated publish. Before pulling that irreversible lever, the plan was to drain the follow-up backlog: seven open entries, so that whatever shipped wasn't shipping with known-but-unwired gaps frozen into a public package. + +Triage split them fast. Four were not work: two were already-resolved risks the extractor had scraped, one was a decision deferred to a trigger that hadn't fired, one was blocked on an upstream merge outside the project's control. That left **three follow-ups that were genuine, unresolved, actionable work.** All three got a Charter. All three, on inspection, turned out to rest on something that wasn't true. + +## Three follow-ups, three false premises + +**The parity test that didn't exist.** One follow-up read, in effect, "replicate for the Loro shim the header↔binding parity test that the yrs shim already has." Reasonable — except the yrs test did not exist. What existed were two *comments* — one in the C# binding, one in the C header — both stating "a CI test validates that these declarations match this header." No such test was ever written; the line was aspirational, traceable back to a research note that said the check *could* be generated. The follow-up's author had read a comment as a fact. The follow-up inherited the comment's falseness and added a hop of authority. Building it "as written" would have meant porting a test that had no original. + +**The gate against a reference that can't exist.** Another asked for a determinism gate for the Loro engine, mirroring the existing yrs↔Yjs parity gate. The symmetry is seductive: yrs has an independent reference implementation (Yjs) to check byte-for-byte against, so surely Loro should have its equivalent. It can't. There is no independent second implementation of Loro's format — the npm package is a WebAssembly build of the *same* Rust core, so comparing against it is comparing the crate to itself. The follow-up reasoned by analogy, and the analogy quietly failed. The realizable gate was a different, more modest thing (self-determinism across runs, a regression witness rather than a parity proof) — which is what got built, but only because the premise was caught first. + +**The optimization for a cost that wasn't there.** The third feared that reordering the relay to persist-before-broadcast would push I/O onto the actor's hot path and hurt throughput. Tracing the actual await chain showed the persistence call was *already* awaited on the receive loop, before the connection reads its next frame — reordering added no I/O to any hot path it wasn't already on. A load harness confirmed it: the "safe" ordering cost effectively zero at p50/p99. The follow-up had encoded a *mental model* of the architecture, not the architecture as built. This is the subtlest of the three: no lying comment, no broken analogy — just a map in someone's head that had drifted a notch from the territory, and a note that faithfully recorded the map. + +Three follow-ups. A comment believed, an analogy over-trusted, a mental model slightly stale. Different failure modes, one shared shape: **each premise was false at the moment of writing, and cheap to falsify at the moment of reading.** + +## The two moments + +There are exactly two places you could put a "verify the premise" rule: when the follow-up is written, or when it is read. + +Writing happens at the worst possible moment for verification. A follow-up is a note-to-self jotted while *finishing something else* — closing Charter N, head full of the current subsystem, glancing sideways at a different one you're about to leave. Verifying the sideways glance means a full context switch away from the work you're trying to land. It's expensive precisely *then*. + +Reading happens at the best possible moment. When you finally act on the follow-up, you're already inside that subsystem, with the code open. Checking "does this test actually exist / does this reference actually exist / does this cost actually exist" is a `grep`, a file read, a traced call chain — seconds. The three false premises above collapsed on exactly these checks. + +So the verification is not just cheaper at read time — it's *categorically* cheaper, because at read time you've already paid the context-switch cost for other reasons. The economics point in one direction. + +## The reframe: a backlog is a speculative buffer + +Here's the part that changes how the whole feature should be understood. It's tempting to conclude "authors should verify follow-ups harder before writing them." That's the wrong lesson, and it would make the tool worse. + +A follow-up backlog is a *speculative buffer*. Its job is to capture, cheaply, that something *might* be worth doing — so the signal isn't lost when attention moves on. If you demanded verification at capture time, you'd spend the close of every Charter spelunking subsystems you're abandoning, and the rational response would be to stop writing follow-ups at all. **Eager verification defeats the purpose of the buffer.** The under-verified entry isn't a defect of the author; it's the *expected epistemic status* of anything in a speculative buffer. + +Which means the false premises weren't bugs in how the follow-ups were written. They were the natural state of a hypothesis that had never been tested — and a follow-up is a hypothesis. The only real bug would have been *executing one without re-testing it*. Which is exactly the trap the registry's framing set up: it presented entries as a to-do list, an instruction set, a plan. Read as instructions, false premises become wasted Charters. Read as **dated hypotheses**, they become what they are — cheap bets to re-check the moment you're positioned to. + +## What we shipped + +The field report landed as [#365](https://github.com/StrangeDaysTech/straymark/issues/365). It split cleanly into two changes, and — fittingly — the reframe itself was handled as a *dated hypothesis*: recorded as a decision ([`AIDEC-2026-07-18-001`](https://github.com/StrangeDaysTech/straymark/blob/main/.straymark/07-ai-audit/decisions/AIDEC-2026-07-18-001-followups-as-hypotheses.md)), reviewed by a human, and signed before a line of the shipped doc changed. A claim about how to treat claims deserved the same discipline it was arguing for. + +### Frame entries as hypotheses, and move verification to execution + +Released as [`fw-4.36.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/fw-4.36.0) / [`cli-3.37.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.37.0) ([#369](https://github.com/StrangeDaysTech/straymark/pull/369)), in three layers: + +**The words.** The follow-ups pattern doc gains a first-class *"Epistemic status"* section that says the quiet part out loud: the registry is a speculative buffer, an entry is a dated and decaying hypothesis rather than an instruction, an under-verified entry is the *expected* state and not an authoring defect — and the only real bug is executing one without re-testing its premise. The agent directives (`AGENT-RULES.md §13`) gain the matching rule: **write cheaply at capture; re-verify the premise when you promote or act — never at capture.** The framing was the load-bearing change. Everything else just gives it teeth. + +**The fields.** An entry can now carry an explicit `Premise` — the load-bearing assumption it rests on — and a `Verified-at` date. Both are optional and the schema stays `v1`, so no existing registry changes. Stating the premise is what turns "re-verify" from a vague nudge into a concrete target: *"the yrs shim already has a parity test"* is a sentence you can falsify in one `grep`. `Verified-at` absent means "never re-checked since capture" — the honest default; its presence is provenance that the hypothesis was tested against reality before anyone spent a Charter on it. + +**The checkpoint.** Two CLI affordances put the check exactly where it's cheap: + +- `straymark followups verify FU-NNN` surfaces the premise, optionally records or updates it (`--premise "..."`), and stamps `Verified-at` when you confirm the re-check (`--verified`). With no flags it's read-only — it just shows you the assumption and asks whether it still holds. This is the common path: an entry acted on as a chore that never becomes a formal debt document. +- `straymark followups promote FU-NNN --premise-verified` does the same at the moment a follow-up graduates into a TDE: it prints the premise with a *"is this still true? re-verify against the code"* reminder, and stamps `Verified-at` on confirmation. + +The design rule under both: **the CLI reminds and records; it never gates.** Promotion proceeds with or without the flag; `verify` never blocks anything. It won't decide whether your premise is true — that's the human's job, standing in the one spot where the check is nearly free. Anything stricter would recreate the capture-time tax the reframe exists to avoid. + +### The title the machine wrote + +The secondary finding was smaller and more concrete, and it shipped first, in [`cli-3.36.2`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.36.2) ([#366](https://github.com/StrangeDaysTech/straymark/pull/366)). When `followups drift --apply` auto-extracts an entry from an AILOG's `## Follow-ups` section, it took the *first physical line* of the bullet as the title. AILOG bullets are hard-wrapped prose, so a lead sentence got sliced at the author's wrap column — three of this session's entries were literally titled things like *"**Footgun of a local pack contaminated with `test-hooks`** — the pack reads from"*, cut mid-thought. A machine grabbing a line-fragment loses the nuance a hand title carries, and a follow-up whose *title* misrepresents it starts life already a little wrong — which compounds the exact "read as instruction" hazard the rest of this post is about. + +The fix un-wraps the bullet, prefers a substantial leading `**bold**` span as the title (the convention authors already reach for), and otherwise takes the first *sentence*, capped at a word boundary. The subtle part was keeping it **hash-neutral**: the registry dedupes entries by a content hash derived from the raw first line, so a nicer title had to be decoupled from the dedup key — otherwise every already-extracted entry in every adopter's registry would re-appear as a duplicate on the next scan. Titles got sharper; nothing re-duplicated. + +## The portable version + +If you keep any backlog of deferred work — a follow-ups registry, a `// TODO(later)`, an issue tagged `someday` — you're keeping a buffer of hypotheses, whether or not you call it that. The entries are cheap to write and were written when you couldn't check them. The mistake is not writing them loosely; that's correct, and demanding rigor at capture would just make you stop capturing. The mistake is reading them as a plan and executing on faith. Re-test the premise when you act — you're standing in the one spot where it's nearly free — and let a false premise cost you a `grep`, not a Charter. + +--- + +*Empirical basis: three Charters clearing a follow-up backlog in the [Adopter] Weft project, 2026-07-16 → 2026-07-18 (7 open → 1). Shipped in StrayMark [`fw-4.36.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/fw-4.36.0) / [`cli-3.37.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.37.0) (the reframe, [#365](https://github.com/StrangeDaysTech/straymark/issues/365)/[#369](https://github.com/StrangeDaysTech/straymark/pull/369), [`AIDEC-2026-07-18-001`](https://github.com/StrangeDaysTech/straymark/blob/main/.straymark/07-ai-audit/decisions/AIDEC-2026-07-18-001-followups-as-hypotheses.md)) and [`cli-3.36.2`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.36.2) (title fidelity, [#366](https://github.com/StrangeDaysTech/straymark/pull/366)). Related: [#360](https://github.com/StrangeDaysTech/straymark/issues/360), [#355](https://github.com/StrangeDaysTech/straymark/issues/355), [#346](https://github.com/StrangeDaysTech/straymark/issues/346).* + +*This document was produced with assistance from generative AI tools (Claude Opus 4.8); all responsibility for the content rests with the human author.* diff --git a/website/i18n/es/docusaurus-plugin-content-docs-blog/current/2026-07-18-what-the-follow-up-took-for-granted.md b/website/i18n/es/docusaurus-plugin-content-docs-blog/current/2026-07-18-what-the-follow-up-took-for-granted.md new file mode 100644 index 00000000..3f92bc8f --- /dev/null +++ b/website/i18n/es/docusaurus-plugin-content-docs-blog/current/2026-07-18-what-the-follow-up-took-for-granted.md @@ -0,0 +1,88 @@ +--- +slug: what-the-follow-up-took-for-granted +title: Lo que el follow-up dio por sentado +authors: + - jose +tags: + - straymark + - governance + - followups + - charters + - cli +draft: false +date: 2026-07-18T00:00:00.000Z +description: Un adopter greenfield drenó un backlog de follow-ups antes de publicar y descubrió que tres de ellos — los tres que eran trabajo real — cargaban cada uno una premisa que era falsa en el momento en que la chequeabas. No porque el código hubiera cambiado bajo ellos, sino porque un follow-up se escribe en el único momento en que estás menos equipado para verificarlo. La lección no es "escribe mejores follow-ups". Es que un follow-up es una hipótesis fechada, y el lugar barato para probarla es cuando la lees, no cuando la escribes — así que ahí es donde StrayMark ahora pone el chequeo. +--- + +*Un adopter drenó un backlog de follow-ups antes de publicar — de siete entradas a una. Cuatro eran ruido ya resuelto. De las tres que eran trabajo real, sin resolver, cada una cargaba una premisa falsa: un test a "replicar" que nunca existió, una gate a construir contra una referencia que no puede existir, una optimización para arreglar un coste que no estaba ahí. Ninguna de las tres estaba mal porque el código hubiera derivado bajo ella. Estaban mal en el momento en que se escribieron, y la falsedad solo afloró en un chequeo de treinta segundos hecho meses después. Ese timing es toda la historia. Un follow-up se escribe en el momento exacto en que menos puedes verificarlo — y se lee en un momento en que verificar es casi gratis. El registry de StrayMark solía tratar las entradas como instrucciones a ejecutar. Se entienden mejor como hipótesis fechadas a re-testear — y, a partir de este release, eso es lo que la herramienta dice y donde pone el chequeo.* + + + +## El escenario + +El adopter es la misma librería CRDT greenfield en .NET 10 / Rust detrás de [#345](https://github.com/StrangeDaysTech/straymark/issues/345)/[#346](https://github.com/StrangeDaysTech/straymark/issues/346)/[#355](https://github.com/StrangeDaysTech/straymark/issues/355)/[#360](https://github.com/StrangeDaysTech/straymark/issues/360) — la discusión [Adopter] Weft. El Milestone 3 estaba code-complete salvo la publicación gateada por el operador. Antes de tirar de esa palanca irreversible, el plan era drenar el backlog de follow-ups: siete entradas abiertas, para que lo que se publicara no se publicara con brechas conocidas-pero-sin-cablear congeladas en un paquete público. + +El triage las separó rápido. Cuatro no eran trabajo: dos eran riesgos ya resueltos que el extractor había raspado, una era una decisión diferida a un trigger que no se había disparado, una estaba bloqueada por un merge upstream fuera del control del proyecto. Eso dejó **tres follow-ups que eran trabajo genuino, sin resolver, accionable.** Los tres recibieron un Charter. Los tres, al inspeccionarlos, resultaron descansar sobre algo que no era cierto. + +## Tres follow-ups, tres premisas falsas + +**El test de paridad que no existía.** Un follow-up decía, en efecto, "replica para el shim de Loro el test de paridad header↔binding que el shim de yrs ya tiene". Razonable — salvo que el test de yrs no existía. Lo que existía eran dos *comentarios* — uno en el binding de C#, otro en el header de C — ambos afirmando "un test de CI valida que estas declaraciones coinciden con este header". Nunca se escribió tal test; la línea era aspiracional, rastreable hasta una nota de investigación que decía que el chequeo *podría* generarse. El autor del follow-up había leído un comentario como un hecho. El follow-up heredó la falsedad del comentario y añadió un salto de autoridad. Construirlo "tal cual" habría significado portar un test que no tenía original. + +**La gate contra una referencia que no puede existir.** Otro pedía una gate de determinismo para el motor Loro, espejo de la gate de paridad yrs↔Yjs existente. La simetría es seductora: yrs tiene una implementación de referencia independiente (Yjs) contra la cual chequear byte-a-byte, así que seguramente Loro debería tener su equivalente. No puede. No hay una segunda implementación independiente del formato de Loro — el paquete npm es un build en WebAssembly del *mismo* core en Rust, así que compararse contra él es comparar el crate consigo mismo. El follow-up razonó por analogía, y la analogía falló en silencio. La gate realizable era una cosa distinta, más modesta (auto-determinismo entre corridas, un testigo de regresión en vez de una prueba de paridad) — que es lo que se construyó, pero solo porque la premisa se cazó primero. + +**La optimización para un coste que no estaba ahí.** El tercero temía que reordenar el relay a persist-before-broadcast pusiera I/O en el hot path del actor y dañara el throughput. Trazar la cadena de `await` real mostró que la llamada de persistencia *ya* se await-eaba en el receive loop, antes de que la conexión leyera su siguiente frame — reordenar no añadía I/O a ningún hot path en el que no estuviera ya. Un harness de carga lo confirmó: el ordenamiento "seguro" costaba efectivamente cero en p50/p99. El follow-up había codificado un *modelo mental* de la arquitectura, no la arquitectura como estaba construida. Este es el más sutil de los tres: ningún comentario mentiroso, ninguna analogía rota — solo un mapa en la cabeza de alguien que había derivado un poco del territorio, y una nota que registró fielmente el mapa. + +Tres follow-ups. Un comentario creído, una analogía sobre-confiada, un modelo mental ligeramente obsoleto. Modos de falla distintos, una forma compartida: **cada premisa era falsa en el momento de escribirla, y barata de falsificar en el momento de leerla.** + +## Los dos momentos + +Hay exactamente dos lugares donde podrías poner una regla de "verifica la premisa": cuando el follow-up se escribe, o cuando se lee. + +Escribir ocurre en el peor momento posible para verificar. Un follow-up es una nota-para-uno-mismo anotada mientras *terminas otra cosa* — cerrando el Charter N, con la cabeza llena del subsistema actual, mirando de reojo uno distinto que estás por dejar. Verificar la mirada de reojo significa un cambio de contexto completo lejos del trabajo que intentas aterrizar. Es caro precisamente *entonces*. + +Leer ocurre en el mejor momento posible. Cuando por fin actúas sobre el follow-up, ya estás dentro de ese subsistema, con el código abierto. Chequear "¿existe de verdad este test / existe de verdad esta referencia / existe de verdad este coste?" es un `grep`, la lectura de un archivo, una cadena de llamadas trazada — segundos. Las tres premisas falsas de arriba colapsaron exactamente en estos chequeos. + +Así que la verificación no es solo más barata en tiempo de lectura — es *categóricamente* más barata, porque en tiempo de lectura ya pagaste el coste del cambio de contexto por otras razones. La economía apunta en una sola dirección. + +## El reencuadre: un backlog es un buffer especulativo + +Aquí está la parte que cambia cómo debe entenderse toda la feature. Es tentador concluir "los autores deberían verificar los follow-ups más fuerte antes de escribirlos". Esa es la lección equivocada, y empeoraría la herramienta. + +Un backlog de follow-ups es un *buffer especulativo*. Su trabajo es capturar, barato, que algo *podría* valer la pena hacer — para que la señal no se pierda cuando la atención se mueve. Si exigieras verificación en el momento de captura, pasarías el cierre de cada Charter espeleando subsistemas que estás abandonando, y la respuesta racional sería dejar de escribir follow-ups del todo. **La verificación ansiosa anula el propósito del buffer.** La entrada sub-verificada no es un defecto del autor; es el *estatus epistémico esperado* de cualquier cosa en un buffer especulativo. + +Lo que significa que las premisas falsas no eran bugs en cómo se escribieron los follow-ups. Eran el estado natural de una hipótesis que nunca se había probado — y un follow-up es una hipótesis. El único bug real habría sido *ejecutar una sin re-testearla*. Que es exactamente la trampa que tendía el encuadre del registry: presentaba las entradas como una lista de tareas, un set de instrucciones, un plan. Leídas como instrucciones, las premisas falsas se vuelven Charters desperdiciados. Leídas como **hipótesis fechadas**, se vuelven lo que son — apuestas baratas a re-chequear en el momento en que estás posicionado para hacerlo. + +## Lo que enviamos + +El reporte de campo llegó como [#365](https://github.com/StrangeDaysTech/straymark/issues/365). Se partió limpio en dos cambios y — apropiadamente — el reencuadre mismo se trató como una *hipótesis fechada*: se registró como una decisión ([`AIDEC-2026-07-18-001`](https://github.com/StrangeDaysTech/straymark/blob/main/.straymark/07-ai-audit/decisions/AIDEC-2026-07-18-001-followups-as-hypotheses.md)), la revisó un humano, y se firmó antes de que cambiara una línea de la doc enviada. Una afirmación sobre cómo tratar afirmaciones merecía la misma disciplina que argumentaba. + +### Enmarcar las entradas como hipótesis, y mover la verificación a la ejecución + +Liberado como [`fw-4.36.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/fw-4.36.0) / [`cli-3.37.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.37.0) ([#369](https://github.com/StrangeDaysTech/straymark/pull/369)), en tres capas: + +**Las palabras.** La doc del patrón de follow-ups gana una sección de primera clase, *"Estatus epistémico"*, que dice en voz alta la parte que se callaba: el registry es un buffer especulativo, una entrada es una hipótesis fechada y decadente en vez de una instrucción, una entrada sub-verificada es el estado *esperado* y no un defecto de autoría — y el único bug real es ejecutar una sin re-testear su premisa. Las directivas de agente (`AGENT-RULES.md §13`) ganan la regla correspondiente: **escribe barato en la captura; re-verifica la premisa cuando promuevas o actúes — nunca en la captura.** El encuadre fue el cambio que carga el peso. Todo lo demás solo le da dientes. + +**Los campos.** Una entrada ahora puede cargar un `Premise` explícito — la suposición que la sostiene — y una fecha `Verified-at`. Ambos son opcionales y el schema sigue en `v1`, así que ningún registry existente cambia. Declarar la premisa es lo que convierte "re-verificar" de un empujón vago en un blanco concreto: *"el shim de yrs ya tiene un test de paridad"* es una oración que puedes falsificar en un `grep`. `Verified-at` ausente significa "nunca re-chequeada desde la captura" — el default honesto; su presencia es procedencia de que la hipótesis se probó contra la realidad antes de que alguien gastara un Charter en ella. + +**El checkpoint.** Dos afordancias del CLI ponen el chequeo justo donde es barato: + +- `straymark followups verify FU-NNN` superficie la premisa, opcionalmente la registra o actualiza (`--premise "..."`), y sella `Verified-at` cuando confirmas el re-chequeo (`--verified`). Sin flags es read-only — solo te muestra la suposición y pregunta si aún se sostiene. Este es el camino común: una entrada actuada como chore que nunca se vuelve un documento formal de deuda. +- `straymark followups promote FU-NNN --premise-verified` hace lo mismo en el momento en que un follow-up gradúa a un TDE: imprime la premisa con un recordatorio *"¿sigue siendo cierto? re-verifica contra el código"*, y sella `Verified-at` al confirmar. + +La regla de diseño bajo ambas: **el CLI recuerda y registra; nunca bloquea.** La promoción procede con o sin el flag; `verify` nunca bloquea nada. No decidirá si tu premisa es cierta — ese es el trabajo del humano, parado en el único lugar donde el chequeo es casi gratis. Cualquier cosa más estricta recrearía el impuesto de tiempo-de-captura que el reencuadre existe para evitar. + +### El título que escribió la máquina + +El hallazgo secundario era más pequeño y más concreto, y se envió primero, en [`cli-3.36.2`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.36.2) ([#366](https://github.com/StrangeDaysTech/straymark/pull/366)). Cuando `followups drift --apply` auto-extrae una entrada de la sección `## Follow-ups` de un AILOG, tomaba la *primera línea física* del bullet como título. Los bullets de AILOG son prosa hard-wrapped, así que una oración de arranque quedaba cortada en la columna de wrap del autor — tres de las entradas de esta sesión quedaron literalmente tituladas como *"**Footgun de pack local contaminado con `test-hooks`** — el pack lee de"*, cortadas mid-thought. Una máquina agarrando un fragmento-de-línea pierde el matiz que carga un título a mano, y un follow-up cuyo *título* lo tergiversa arranca su vida ya un poco mal — lo que compone el mismísimo peligro de "leer como instrucción" del que trata el resto de este post. + +El fix des-envuelve el bullet, prefiere un span en `**negrita**` de cabecera como título (la convención a la que los autores ya recurren), y si no toma la primera *oración*, con cap en un límite de palabra. La parte sutil fue mantenerlo **hash-neutral**: el registry deduplica entradas por un hash de contenido derivado de la línea cruda, así que un título más bonito tuvo que desacoplarse de la clave de dedup — de lo contrario cada entrada ya-extraída en el registry de cada adopter reaparecería como duplicado en el siguiente escaneo. Los títulos quedaron más nítidos; nada se re-duplicó. + +## La versión portable + +Si mantienes cualquier backlog de trabajo diferido — un registry de follow-ups, un `// TODO(later)`, un issue etiquetado `someday` — estás manteniendo un buffer de hipótesis, lo llames así o no. Las entradas son baratas de escribir y se escribieron cuando no podías chequearlas. El error no es escribirlas sueltas; eso es correcto, y exigir rigor en la captura solo haría que dejaras de capturar. El error es leerlas como un plan y ejecutar por fe. Re-testea la premisa cuando actúas — estás parado en el único lugar donde es casi gratis — y deja que una premisa falsa te cueste un `grep`, no un Charter. + +--- + +*Base empírica: tres Charters drenando un backlog de follow-ups en el proyecto [Adopter] Weft, 2026-07-16 → 2026-07-18 (7 abiertos → 1). Enviado en StrayMark [`fw-4.36.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/fw-4.36.0) / [`cli-3.37.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.37.0) (el reencuadre, [#365](https://github.com/StrangeDaysTech/straymark/issues/365)/[#369](https://github.com/StrangeDaysTech/straymark/pull/369), [`AIDEC-2026-07-18-001`](https://github.com/StrangeDaysTech/straymark/blob/main/.straymark/07-ai-audit/decisions/AIDEC-2026-07-18-001-followups-as-hypotheses.md)) y [`cli-3.36.2`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.36.2) (fidelidad de título, [#366](https://github.com/StrangeDaysTech/straymark/pull/366)). Relacionados: [#360](https://github.com/StrangeDaysTech/straymark/issues/360), [#355](https://github.com/StrangeDaysTech/straymark/issues/355), [#346](https://github.com/StrangeDaysTech/straymark/issues/346).* + +*Este documento se produjo con asistencia de herramientas de IA generativa (Claude Opus 4.8); toda la responsabilidad del contenido recae en el autor humano.* diff --git a/website/i18n/zh-CN/docusaurus-plugin-content-docs-blog/current/2026-07-18-what-the-follow-up-took-for-granted.md b/website/i18n/zh-CN/docusaurus-plugin-content-docs-blog/current/2026-07-18-what-the-follow-up-took-for-granted.md new file mode 100644 index 00000000..84f1e996 --- /dev/null +++ b/website/i18n/zh-CN/docusaurus-plugin-content-docs-blog/current/2026-07-18-what-the-follow-up-took-for-granted.md @@ -0,0 +1,83 @@ +--- +slug: what-the-follow-up-took-for-granted +title: follow-up 想当然的那件事 +authors: + - jose +tags: [straymark, governance, followups, charters, cli] +draft: false +date: 2026-07-18 +description: 一个 greenfield adopter 在发布前清空了一个 follow-up backlog,发现其中三个 —— 那三个是真正工作的 —— 各自都带着一个在你核查它的那一刻就为假的前提。不是因为代码在它们之下发生了变化,而是因为一个 follow-up 恰恰写于你最没有能力验证它的那一刻。教训不是"把 follow-up 写得更好"。而是一个 follow-up 是一个有日期的假设,而检验它的廉价之处在你读它时,而非写它时 —— 所以那正是 StrayMark 现在放置核查的地方。 +--- + +*一个 adopter 在发布前清空了一个 follow-up backlog —— 从七个条目降到一个。四个是已解决的噪声。在那三个真正的、未解决的工作里,每一个都带着一个虚假的前提:一个要"复制"却从不存在的测试、一个要针对无法存在的参照物去构建的 gate、一个要修复一个根本不在那里的成本的优化。三者中没有一个是因为代码在其之下漂移而出错的。它们在被写下的那一刻就是错的,而这份虚假只在几个月后一次三十秒的核查中才浮现。那个时机就是整个故事。一个 follow-up 恰恰写于你最无法验证它的那一刻 —— 而读于验证几乎免费的那一刻。StrayMark 的注册表过去把条目当作要执行的指令。它们更应被理解为要重新测试的有日期的假设 —— 而且,从这个 release 起,这正是工具所说的,以及它放置核查的地方。* + + + +## 背景 + +这个 adopter 就是 [#345](https://github.com/StrangeDaysTech/straymark/issues/345)/[#346](https://github.com/StrangeDaysTech/straymark/issues/346)/[#355](https://github.com/StrangeDaysTech/straymark/issues/355)/[#360](https://github.com/StrangeDaysTech/straymark/issues/360) 背后那个 greenfield 的 .NET 10 / Rust CRDT 库 —— 即 [Adopter] Weft 讨论。Milestone 3 除了由操作员把关的发布之外都已 code-complete。在拉下那个不可逆的操纵杆之前,计划是清空 follow-up backlog:七个打开的条目,好让发布出去的东西不会带着已知却未接线的缺口被冻结进一个公开包里。 + +Triage 很快把它们分开了。四个不是工作:两个是提取器刮取到的已解决的风险,一个是推迟到某个尚未触发的 trigger 的决定,一个被一个项目控制之外的 upstream merge 阻塞。剩下**三个是真正的、未解决的、可执行的工作。** 三个都拿到了一个 Charter。三个在检查之下,都原来建立在某个并不为真的东西之上。 + +## 三个 follow-up,三个虚假前提 + +**那个不存在的一致性测试。** 一个 follow-up 实际上写的是"为 Loro shim 复制 yrs shim 已经有的那个 header↔binding 一致性测试"。合理 —— 只不过 yrs 那个测试并不存在。存在的是两条*注释* —— 一条在 C# binding 里,一条在 C header 里 —— 都声称"一个 CI 测试验证这些声明与此 header 匹配"。从没写过这样的测试;那句话是憧憬性的,可追溯到一条说该核查*可以*被生成的研究笔记。这个 follow-up 的作者把一条注释读成了一个事实。follow-up 继承了注释的虚假,并加了一跳权威。"照原样"构建它,就意味着移植一个没有原本的测试。 + +**那个针对无法存在的参照物的 gate。** 另一个要求给 Loro 引擎做一个确定性 gate,镜像已有的 yrs↔Yjs 一致性 gate。这种对称很诱人:yrs 有一个独立的参考实现(Yjs)可以逐字节核对,那么 Loro 想必也该有它的对应物。它不能。Loro 格式没有第二个独立实现 —— 那个 npm 包是同一个 Rust core 的 WebAssembly build,所以拿它来比对就是拿这个 crate 跟自己比。这个 follow-up 靠类比来推理,而类比悄然失效了。可实现的 gate 是另一个更朴素的东西(跨运行的自我确定性,一个回归见证而非一致性证明)—— 那正是最终构建出来的,但只因为前提先被抓住了。 + +**那个针对根本不在那里的成本的优化。** 第三个担心把 relay 重排为 persist-before-broadcast 会把 I/O 推到 actor 的 hot path 上并损害吞吐。追踪真实的 `await` 链表明,持久化调用*已经*在 receive loop 上被 await 了,在连接读取下一帧之前 —— 重排并没有把 I/O 加到任何它本就不在其上的 hot path。一个负载 harness 证实了这点:"安全"的排序在 p50/p99 上的成本实际为零。这个 follow-up 编码的是一份架构的*心智模型*,而不是被构建出来的架构。这是三者中最微妙的一个:没有撒谎的注释,没有破裂的类比 —— 只是某人脑中一份已经偏离领土一格的地图,以及一条忠实记录了那份地图的笔记。 + +三个 follow-up。一条被轻信的注释、一个被过度信任的类比、一个略微过时的心智模型。不同的失败模式,一个共同的形状:**每个前提在写下的那一刻就为假,而在读取的那一刻却便宜可证伪。** + +## 两个时刻 + +放置一条"验证前提"规则的地方恰好有两个:follow-up 被写下时,或它被读取时。 + +写下发生在验证的最糟时刻。一个 follow-up 是在*完成别的事情*时随手记下的一条给自己的笔记 —— 正在关闭 Charter N,满脑子都是当前的子系统,侧眼瞥一眼你正要离开的另一个。验证那一瞥意味着从你正试图落地的工作上做一次完整的上下文切换。它恰恰*在那时*昂贵。 + +读取发生在最好的时刻。当你终于对这个 follow-up 采取行动时,你已经身处那个子系统之中,代码就开着。核查"这个测试真的存在吗 / 这个参照物真的存在吗 / 这个成本真的存在吗"就是一次 `grep`、读一个文件、追踪一条调用链 —— 数秒。上面三个虚假前提正是在这些核查上崩塌的。 + +所以验证不仅在读取时更便宜 —— 它是*范畴性地*更便宜,因为在读取时你已经因为别的原因付了上下文切换的成本。经济学只指向一个方向。 + +## 重构:一个 backlog 是一个推测性缓冲区 + +这里是改变整个功能该如何被理解的部分。人们很容易得出"作者应该在写下前更用力地验证 follow-up"的结论。那是错误的教训,而且会让工具更糟。 + +一个 follow-up backlog 是一个*推测性缓冲区*。它的职责是廉价地捕获"某件事*可能*值得做" —— 好让信号在注意力移开时不丢失。如果你要求在捕获时验证,你就会把每个 Charter 的收尾都花在你正在放弃的子系统里探洞,而理性的反应将是干脆停止撰写 follow-up。**急于验证会破坏缓冲区的目的。** 未经充分验证的条目不是作者的缺陷;它是推测性缓冲区中任何事物*预期的认识论地位*。 + +这意味着那些虚假前提并不是 follow-up 撰写方式中的 bug。它们是一个从未被测试过的假设的自然状态 —— 而一个 follow-up 就是一个假设。唯一真正的 bug 会是*在不重新测试的情况下执行它*。而这恰恰是注册表的框架所设下的陷阱:它把条目呈现为一份待办清单、一套指令、一份计划。当作指令来读,虚假前提就变成被浪费的 Charter。当作**有日期的假设**来读,它们就成了它们本来的样子 —— 在你有条件的那一刻去重新核查的廉价赌注。 + +## 我们交付了什么 + +现场报告以 [#365](https://github.com/StrangeDaysTech/straymark/issues/365) 落地。它干净地分成两个改动,而且 —— 恰如其分地 —— 重构本身也被当作一个*有日期的假设*来处理:它被记录为一个决定([`AIDEC-2026-07-18-001`](https://github.com/StrangeDaysTech/straymark/blob/main/.straymark/07-ai-audit/decisions/AIDEC-2026-07-18-001-followups-as-hypotheses.md)),由一个人审查,并在交付的文档改动一行之前被签署。一个关于如何对待主张的主张,值得它所论证的那份同样的纪律。 + +### 把条目当作假设来框定,并把验证移到执行 + +作为 [`fw-4.36.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/fw-4.36.0) / [`cli-3.37.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.37.0) 发布([#369](https://github.com/StrangeDaysTech/straymark/pull/369)),分三层: + +**措辞。** follow-ups 模式文档新增了一个一等公民的*"认识论地位"*小节,把过去闷着没说的话讲出来:注册表是一个推测性缓冲区,一个条目是一个有日期、会衰减的假设而非一条指令,一个未经充分验证的条目是*预期*状态而非撰写缺陷 —— 而唯一真正的 bug 是在不重新测试其前提的情况下执行它。代理指令(`AGENT-RULES.md §13`)新增了对应规则:**在捕获时廉价地写;在提升或行动时重新验证前提 —— 绝不在捕获时。** 框架才是承重的改动。其余一切只是给它装上牙齿。 + +**字段。** 一个条目现在可以携带一个显式的 `Premise`(支撑它的假设)和一个 `Verified-at` 日期。二者皆可选,且 schema 仍为 `v1`,所以没有任何现有注册表会改变。声明前提正是把"重新验证"从一个模糊的提醒变成一个具体靶子的东西:*"yrs shim 已经有一个一致性测试"* 是一句你能用一次 `grep` 证伪的话。`Verified-at` 缺失意味着"自捕获以来从未重新核查" —— 诚实的默认;它的存在是一种溯源证据,表明在有人为它花一个 Charter 之前,该假设已针对现实被检验过。 + +**检查点。** 两个 CLI 可供性(affordance)把核查放在它便宜的地方: + +- `straymark followups verify FU-NNN` 浮现前提,可选地记录或更新它(`--premise "..."`),并在你确认重新核查时盖上 `Verified-at`(`--verified`)。不带 flag 时它是只读的 —— 只把假设展示给你,并问它是否仍然成立。这是常见路径:一个作为杂务被执行、从不成为正式债务文档的条目。 +- `straymark followups promote FU-NNN --premise-verified` 在一个 follow-up 毕业为 TDE 的那一刻做同样的事:它打印前提并附上一句*"这还成立吗?针对代码重新验证"*的提醒,并在确认时盖上 `Verified-at`。 + +二者之下的设计规则:**CLI 提醒并记录;它从不设卡。** 无论带不带 flag,提升都会进行;`verify` 从不阻塞任何东西。它不会裁定你的前提是否为真 —— 那是人的工作,站在那个核查几乎免费的唯一位置上。任何更严格的做法都会重建重构所要避免的那笔捕获时税。 + +### 机器写的那个标题 + +次要的发现更小更具体,而且先交付了,在 [`cli-3.36.2`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.36.2) 中([#366](https://github.com/StrangeDaysTech/straymark/pull/366))。当 `followups drift --apply` 从一个 AILOG 的 `## Follow-ups` 小节自动提取一个条目时,它把 bullet 的*第一个物理行*当作标题。AILOG 的 bullet 是硬换行的散文,所以一句开头会在作者的换行列被切断 —— 这次会话中有三个条目字面上被命名成诸如 *"**被 `test-hooks` 污染的本地 pack 的 footgun** —— 该 pack 读取自"* 之类的东西,在半句处被截断。一台机器抓一个行片段会丢掉手写标题所携带的细微差别,而一个其*标题*就歪曲了自己的 follow-up 一出生就已经有点不对 —— 这恰恰放大了本文其余部分所讲的"当作指令来读"的危险。 + +修复会把 bullet 展开,优先取一个够分量的开头 `**加粗**` span 作为标题(作者本就会用的约定),否则取第一*句*,并在词边界处截断。微妙之处在于保持它**哈希中性**:注册表按一个从原始首行派生的内容哈希来去重,所以一个更好看的标题必须与去重键解耦 —— 否则每个 adopter 注册表中每个已提取的条目都会在下一次扫描时作为重复项重新出现。标题变清晰了;什么都没有重新重复。 + +## 可移植的版本 + +如果你保留着任何一份延期工作的 backlog —— 一个 follow-ups 注册表、一个 `// TODO(later)`、一个打了 `someday` 标签的 issue —— 你就在保留一个假设的缓冲区,无论你是否这么叫它。这些条目写起来便宜,而且是在你无法核查它们时写下的。错误不在于把它们写得随意;那是对的,而在捕获时要求严谨只会让你干脆不再捕获。错误在于把它们当作一份计划来读、并凭信念去执行。在你行动时重新测试前提 —— 你正站在它几乎免费的那个唯一位置上 —— 让一个虚假前提花掉你一次 `grep`,而不是一个 Charter。 + +--- + +*经验基础:在 [Adopter] Weft 项目中清空一个 follow-up backlog 的三个 Charter,2026-07-16 → 2026-07-18(7 个打开 → 1 个)。交付于 StrayMark [`fw-4.36.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/fw-4.36.0) / [`cli-3.37.0`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.37.0)(重构,[#365](https://github.com/StrangeDaysTech/straymark/issues/365)/[#369](https://github.com/StrangeDaysTech/straymark/pull/369),[`AIDEC-2026-07-18-001`](https://github.com/StrangeDaysTech/straymark/blob/main/.straymark/07-ai-audit/decisions/AIDEC-2026-07-18-001-followups-as-hypotheses.md))与 [`cli-3.36.2`](https://github.com/StrangeDaysTech/straymark/releases/tag/cli-3.36.2)(标题保真,[#366](https://github.com/StrangeDaysTech/straymark/pull/366))。相关:[#360](https://github.com/StrangeDaysTech/straymark/issues/360)、[#355](https://github.com/StrangeDaysTech/straymark/issues/355)、[#346](https://github.com/StrangeDaysTech/straymark/issues/346)。* + +*本文档在生成式 AI 工具(Claude Opus 4.8)的协助下产生;内容的全部责任由人类作者承担。*