--- name: field-divergence-sweep description: Sweep a codebase for one field/value computed in more than one place, then derive the correct rule from what a CONSUMER of the value must do — never by picking the surviving implementation. Use when two implementations of the same field disagree, when a value crosses a producer/consumer or cross-repo boundary, after a shared-name collision surfaces, or as a standing audit ("check the fleet for fields computed twice"). Collapses to one imported implementation and makes migrations recompute rather than adjust. --- # Field-divergence sweep — one value, one definition, derived from its consumer A value computed in two places is not a style problem. It is a **latent divergence with a delivery date** — it stays invisible until the two computations meet, and they meet at the worst moment: a gate refusing valid work, a migration corrupting a subset, a coordinate pointing at the wrong page. This skill is the standing probe for that class. It is the same shape as a no-op/orphan sweep, aimed at **values instead of stages**. ## The one idea, stated first > **When two implementations of one field disagree, do not pick the survivor. Derive the rule from what a *consumer* of the value must be able to do — then test the shapes where each candidate happens to be right.** Comparing A against B tells you they differ. It tells you **nothing about whether either is correct**. Choosing between them *feels* like derivation and is **selection** — it silently inherits the assumption that the right answer is already in the room. Often it is not. ## Procedure ### 1. Find the multi-site computations Grep for the same value being derived in more than one place. Productive shapes: ```bash # the field's own name, at assignment sites grep -rn '""\]\s*=\|\s*=' --include=*.py . # the computation idiom, not the field name (catches renamed locals) grep -rn 'sha256(\|len(.*splitlines()\|count("\\n")\|findall(r"\\w+"' --include=*.py . ``` Cast wider than the field that prompted the sweep. The yield is usually a *family*: hashes, line/offset counts, tokenizations, slug/ID derivations, normalizations, timestamps. For each hit, record **what exactly is fed in** — `read_text()` vs `read_bytes()`, pre-stripped vs raw, lowercased vs not. Two identical-looking formulas over different inputs are still two definitions. ### 2. Ask the consumer question — before looking at either implementation For each multi-site value: **what must a consumer be able to DO with this?** Derive the rule from that answer alone. Write it down *before* comparing the implementations, so the existing code cannot anchor you. - A **line coordinate** must resolve the same way for the curator who writes it, the gate that checks it, and every consumer that follows it — an editor, `wc -l`, `sed -n 'Np'`, the chunker. That question decides the rule; neither candidate implementation gets a vote. - A **binding hash** must change exactly when the bound content changes — which rules out any basis that normalizes on read. - A **token** must be the unit the guarantee is *stated over*, not the unit that was convenient to compute. If the derived rule matches neither implementation, that is the expected outcome, not a surprise. ### 3. Enumerate the shapes where each candidate is right — and test all of them This is the step that cannot be skipped, and the reason a casual sample is worthless: > **Complementary-correctness defeats sampling.** Each wrong candidate is *right* on some subset of inputs. If your sample happens to sit inside one candidate's correct region, it confirms the wrong rule with a clean run. So: name the input shapes that discriminate — the edge that makes A right and B wrong, and the edge that flips it — and test **every** one. Then census the real population for each shape, so you know how many artifacts sit in each region. The steward's formulation for this: **confirm the rule, not the sample.** ### 4. Collapse to ONE implementation Fixing the value without fixing the multiplicity leaves the door open — a third formula arrives the same way the second did. - Put the definition **where the semantics live** (the gate/module that owns the contract), and have producers **import** it. - Make an unknown/unsupported variant **raise**, never silently fall back. A wrong convention here is a wrong coordinate. - If the convention is governed, declare it as **data** (with the reasoning and the rejected candidates), not as a tool default. ### 5. Migrate: recompute, never adjust > **A migration must RECOMPUTE per artifact. Arithmetic on the stored value (`-= 1`, `* 2`, a uniform offset) assumes the discrepancy is uniform — and step 3 usually proves it is not.** An adjustment corrupts exactly the artifacts that were already correct. ### 6. Land producer-then-consumers in ONE change-set The change-impact clause: **fixing the producer while N consumers hold the old value is the orphan pattern.** One change-set: producer fix + all consumer artifacts re-stamped + the **byte-diff proof** that only the intended field moved. ```bash git diff --stat -- git diff -U0 -- | grep -E '^[+-]' | grep -vE '^(\+\+\+|---)' | sort | uniq -c ``` Every changed line should be the field. If anything else moved, the change is not bounded. ### 7. Pin it so it cannot silently reopen Tests that pin **all** the discriminating shapes from step 3 — including the shape where the *rejected* candidate happens to agree — plus: - the **one-definition invariant** (the producer delegates; assert it), - the **cross-tool invariant** (producer's stamp == what the consumer/gate measures), - **refuse-unknown-convention** behaviour. ### 8. Report what agreed, not only what diverged A site checked and found consistent is a real result — record it, with the evidence. Where two computations differ **by design** (different inputs, different purposes), state the difference and account for it numerically, so the next sweep doesn't re-open a closed question. ## Load-bearing disciplines (each earned) - **Comparison is selection, not derivation.** Verifying A ≠ B never establishes that A or B is right. ([[feedback-derive-the-rule-from-the-consumer-not-from-the-survivor]]) - **Confirm the rule, not the sample.** Especially when one producer generated the whole sample — its output shares a shape, so the sample cannot see the rule. - **Check what your primitive actually does.** `str.splitlines()` also breaks on `\v \f \x1c \x1d \x1e \x85 U+2028 U+2029`; `read_text()` applies universal-newline translation. A convention adopted without reading its primitive's semantics is an assumption wearing a standard's clothes. - **Sixth-instance rule.** When shared-name / two-senses collisions reach a count, they are no longer a pattern but a **base rate** — the sweep becomes standing maintenance, not incident response. - **A latent divergence with zero live instances still gets named**, not silently fixed in passing, when the fix would touch a producer with landed consumers. Name it for its own cycle. ## The proven run (2026-07-27, chamber-library `source_lines`) Kept because it demonstrates every step, including the failure this skill exists to prevent: 1. Two implementations found: `len(splitlines())` (engine-side) vs `count("\n") + 1` (chamber-side `write_sidecar`). 2. **Skipped step 2** — ratified `splitlines()` because it was the engine-side convention. *Selection, not derivation.* Landed it. 3. Steward: *"confirm the rule rather than the sample — the formulas disagree only when the file ends in a trailing newline."* Census: **36 of 1,297** canonicals lack one, so a blanket `-= 1` would have **corrupted exactly those 36**. The 11-file sample (one producer, all newline-terminated) could never have shown it. 4. The sweep then hit the just-landed fix: `splitlines()` **also** breaks on `\f`/U+2028 — **4 canonicals**, and `the-constitution-of-the-roman-republic-lintott.md` off by **308 lines**. `wc -l` confirmed the consumer-derived rule. 5. **Both** candidates were wrong, on **complementary subsets**. The correct rule was a third thing: `count("\n") + (0 if trailing newline else 1)`, empty = 0. 6. Collapsed to one imported implementation; migration recomputed; producer + 11 consumers in one change-set; byte-diff showed **11 files / 11 insertions / 11 deletions, every changed line the field**. 7. Same sweep **cleared** `sha256` (one latent CRLF-only basis divergence, 0 live instances — named, not silently changed) and the **tokenizers** (identical across five sites; the 20-token delta on a real file was exactly the footnote-marker digits one strips by design — consistent, fully accounted). ## What this skill is not - Not a linter for duplicated code. It targets **one value with two definitions**, which is a correctness problem, not a tidiness one. - Not a licence to unify by fiat. A producer with landed consumers gets its own FIX/PROPOSAL cycle. - Not complete when the value is fixed. It is complete when there is **one implementation**, the discriminating shapes are **pinned**, and the artifacts carry a **bounded-diff proof**.