From d76bf698d79dc565dd20ce4f1b5594581d193d15 Mon Sep 17 00:00:00 2001 From: AmirHossein Roozbahani Date: Sat, 5 Sep 2026 22:23:06 +0200 Subject: [PATCH 1/8] the loop's first dated report: what moved between readings, and on whose gate Three iterations against the goal document, read through the 2026-09-02 baseline's yardsticks. Every number is quoted from the independent gate leg that rebuilt both master and the branch, never from an implementer's claim, and each is named to its gate; where only a review measured something, the item says so. Closed: the corpus match slip (three unsigned join statements, 550 was partly a wrong answer, floor now 547, merged); the seed-1804 parity defect (one formatter line, then the neighbours it exposed - unary minus is a sign flip, not a subtraction); the static-only tie order, generalized from an ORDER-BY-ties rule to the principle that the fold is a pure function of the query and the statics. Also recorded: the gate state of each branch with its campaign delta and corpus counts, the facts that belong in the next full reading (acceptance moved and the denominator's composition moved with it; the Rust unit gate is red on master and CI cannot see it), the spend ledger, and the queue in the goal's own order. Co-Authored-By: Claude Fable 5.1 --- .../docs/reports/2026-09-02-loop-report-1.md | 350 ++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 packages/confit/docs/reports/2026-09-02-loop-report-1.md diff --git a/packages/confit/docs/reports/2026-09-02-loop-report-1.md b/packages/confit/docs/reports/2026-09-02-loop-report-1.md new file mode 100644 index 00000000..cee43d91 --- /dev/null +++ b/packages/confit/docs/reports/2026-09-02-loop-report-1.md @@ -0,0 +1,350 @@ +# The loop's first report: iterations 1-3 (2026-09-02) + +**What this is.** A dated report of one loop's work against `packages/confit/docs/goal.md`, +read through the yardsticks the baseline reading measured +(`packages/confit/docs/reports/2026-09-02-goal-baseline.md`, reading **N=1**). It is not a +new reading: no census, floor or bench was re-taken here for its own sake. What it records is +**which distances from the target moved, by how much, and on whose measurement** — every +number below is quoted from a **gate record**, the independent leg that rebuilt master and +the branch itself, never from an implementer's own claim, and each is named to its gate. +Where only a review measured something, the item says "review record" and the claim stands as +a review's, not a gate's. + +**Slugs.** `gap:` and `finding:` citations resolve in the baseline reading; `goal:`, +`kpi:`, `exclusion:` and `ask:` in `goal.md`; `claim:` and `divergence:` without a local +definition in `packages/confit/docs/oracle/`. Sections here carry kebab-case anchors and are +cited by slug, never by number. + +--- + +## 1. What the loop is {#the-loop} + +The owner's mandate, as it stood over these three iterations: **make confit match `goal.md`** +— close the distance the baseline reading measured, in the order the goal document's own +priorities give. Its working rules: + +- **No tickets.** Work is dispatched against a `gap:` or `finding:` slug, not a ticket + number, and nothing in this loop filed one. +- **No implementation check-ins.** The owner is not consulted mid-branch. What reaches him is + a gated branch and a dated report. +- **Gated PRs and dated reports are the only outputs.** A branch is not done when its author + says so; it is done when an independent gate has rebuilt both legs and reproduced or + refuted every clause of its promise. +- **A merge needs the owner's own GitHub approval.** Nothing in this loop merged itself. One + branch merged in this window, on his click. + +The shape each iteration took: one implementer per item, then an independent **review** and +an independent **gate**, each in its own worktree, each building master and the branch from +source rather than trusting a shipped artifact. That structure is the reason this report can +name a gate for every number: the gate legs are what produced them. + +**One measurement about the loop itself, worth keeping.** Every master leg the loop ran — +**seven of them across three iterations** — reproduced the baseline's campaign census +exactly: `AGREE` 1013 / `REFUSED` 944 / `AGREE_TRAP` 21 / `UNSHIPPED` 14 / `DIVERGE_OPT` 7 / +`DIVERGE_VALUE` 1 over seeds 0-1999 at `--workers 8 --timeout 20`. Master advanced twice in +the window (`2ba96e5` -> `2c7c05c` -> `f81e17c`, docs plus one added assertion) and the census +did not move. claim: campaign-verdicts-today is reproducible on this machine, and a single +seed flipping is therefore signal, not scheduling noise. + +--- + +## 2. What closed {#closed} + +Three of the baseline reading's items were worked. Each is stated as its distance from the +target, then what closed it. + +### 2.1 gap: corpus-match-slip + +*Target:* kpi: coverage-ladder is a **drive** — the mined-corpus match count grows and never +silently shrinks. +*Was:* **547** replayed bit-exact where **550** was quoted at six unhedged sites; the count +was printed and never asserted, so three statements had gone missing with nothing noticing. +*Now:* the three are **named and classified**, and the count is **floored**. + +**The diagnosis, reproduced by the gate rather than relayed.** Exactly three statements +flipped, all the same SQL from DuckDB's own `equality_join_limits` test at corpus indices +**250 / 251 / 252**, whose key columns are declared `UTINYINT`, `USMALLINT` and `UINTEGER`. +The gate rebuilt the engine at four historical commits and replayed all 678 cases at each: +**550** match / 128 clean-unsupported / 0 FAIL before the arrow surface, **547 / 131 / 0** at +the arrow-schema commit and at both later static-side commits, with the per-case diff over +all 678 showing **exactly those three** changing outcome and never to FAIL. The gate also +refuted its own first suspicion — that the branch had misattributed the cause to the row path +— by probing the flip commit and reading the refusal text there. + +**The classification matters more than the count.** DuckDB answers those three at their +declared unsigned widths; the engine has no unsigned lane. The pre-flip "match" stood because +the replay compared `repr`'d **values** and never the **output schema** — so **550 was partly +a wrong answer**, and refusing is goal: two-outcome-contract working, not a regression. The +loss is a correctness gain that the ladder had no way to say out loud. + +*What closed it:* `MATCH_FLOOR = 547` in `test_corpus_replay.py`, asserted inside the +existing three-outcome test (a new assertion, not a new node — junit node ids read **3287** +on both legs), plus two live docs corrected from 550 to 547. +*Gate:* suites **identical** on both legs (3281 passed, 1 skipped, 3 xfailed, 2 errors — the +two errors are the pre-existing absent-`pyspark` collection pair); corpus **547 / 131 / 0** +on both; campaign **zero flips**, the two 2000-record maps equal as whole objects; `cargo +test` 266 passed / 5 failed on both, the same five names. Mutation checked in **both** +directions: the floor at 548 goes red, an injected one-match drop goes red with the assertion +and stays **silent** without it — which is the ratchet's whole claim, demonstrated. +*Merged:* yes, as PR #200; master is `f81e17c`. It is the only merge in this window. +*Left open by the gate:* the doc cleanup is **4 of 6 sites short**. Two report files still +read 550 in the present tense and carry no date, and `known-limitations.md` still says "550 +of 678 statements as of stage B" — the document the branch's own corrected README points at. +That is the precise failure the oracle spec's correction predicted for a partial remedy. + +### 2.2 finding: seed-1804 + +*Target:* kpi: engine-parity is a **control**, fixed at 100% on the accepted surface. +*Was:* one live `DIVERGE_VALUE` in 2000 — a NaN **sign** reaching a string. +*Now:* **`DIVERGE_VALUE` 0 of 2000**, on three separate branch legs, with the root cause and +three of its neighbours named. + +**Root cause.** `impl Display for DuckF64` (`specializer/exec/kernels.rs`) opened with an +unconditional `if self.0.is_nan() { return f.write_str("nan") }`, discarding the sign bit. +One IR instruction backs every double-to-text path on both backends, so the explicit `CAST`, +the implicit casts under `||` and `concat`, and struct fields all dropped the sign together. +The rule was read from the pinned DuckDB v1.5.5 source, not inferred from a probe: the +`DOUBLE -> VARCHAR` cast is `duckdb_fmt::format("{}", v)`, and the bundled writer takes the +sign from `std::signbit` **before** the finiteness branch. Both gates that checked this +citation confirmed it in the checkout. + +**The neighbours it exposed** — this is the part worth carrying forward, because each was +invisible while every NaN rendered alike: + +1. **Unary minus was not an IEEE sign flip.** The engine lowered `-x` on a DOUBLE as + `-0.0 - x`, which returns a NaN operand with its own sign; DuckDB's is a plain negate. The + first gate caught this as a caveat and named it exactly: one shape **agreed on master by + accident** and diverged on the fixed branch. The second iteration replaced the subtraction + with a total unary `Fneg` carried end to end (frontend, lowering, both backends, the + constant fold, and the IR opcode, parser and generator). +2. **A classifier regression the whole suite was blind to.** The new node broke + `scan_residual`, so a JOIN ON residual containing a negated DOUBLE refused where master + compiled it. **A review found it, not a gate and not the suite** (3289 tests green over + the defect). The third iteration closed it with one arm added to an existing or-pattern, + red-first over four residual shapes. +3. **The double-modulo sign**, which the same rendering change made visible, went to its own + branch rather than being folded in here. + +*What closed it:* `fix-nan-sign-varchar`, three iterations deep — the formatter, then the +`Fneg` lowering plus eight review findings, then eight more (the residual classifier, a +bit-level two-backend `fneg` test, `-nan` surviving the IR text round trip, the cast-trap +text pinned as DuckDB's message truncated, and the stale docs). One review finding was +**rejected with measurement** rather than absorbed: the pin the review read as +platform-dependent uses a defined constant's bit pattern, which the repo rule exempts, and +the new Rust pin builds both NaNs from explicit bit patterns anyway. +*Gate (branch tip `57a05ff`, master `f81e17c`):* **PASS, no defects found.** Suite 3281 -> +**3297** passed (junit 3287 -> 3303, **16 new ids all passed, 0 shared-id outcome changes, 0 +disappeared**); the debug build gives the identical set from a genuinely different engine +(23.2 MB against 17.6 MB); `cargo test` 266 -> **268** passed with the failing **set** +identical; campaign `DIVERGE_VALUE` **1 -> 0** with **exactly one seed flipped**, compared at +full-record granularity, not at the summary level; corpus **547 / 131 / 0** and dialect L2 +**288/678** on both legs; four independent mutations each caught and each restored by +re-edit; the public API diff is empty. + +### 2.3 finding: static-only-tie-order + +*Target:* exclusion: whole-relation-shapes — inside the static-tables-only carve-out, what a +whole-relation construct selects is frozen **only when it is a function of the query**. +*Was:* nothing refused a tie-producing `ORDER BY`; two builds of the same function could +freeze different orders. A silent-wrongness class. +*Now:* a rule that refuses it, generalized well past the shape it started as — and gated to +one named defect. + +**The rule generalized.** The first iteration read the query with `sqlparser` and asked about +`ORDER BY` ties. Its gate returned **FAIL** on two independently reproduced defects: one pair +of parentheses around the whole query turned the check off (the probe read only the outermost +node, while its own sibling row-limit rule recursed), and a `DISTINCT` shape that master +served now refused while the branch's doc edit claimed it still served. A review added the +deeper one: any query `sqlparser` cannot parse **silently skipped the check entirely** — and +DuckDB-only dialect is exactly what the carve-out exists to serve. + +The second iteration did not patch that; it **dropped all three commits** and rebuilt the +rule on the principle rather than the shape: *the fold is a pure function of the query and the +statics.* The reading is now **DuckDB's own parse**, via `json_serialize_sql` on the same +connection that folds the query, with `sqlparser` deleted from this path; where even DuckDB +will not serialize a statement, a tokenizer fallback **refuses** rather than falling through. + +*What now refuses, and why:* row limits (`LIMIT` / `OFFSET` / `FETCH` / `SELECT TOP`) before +the fold, keeping master's messages byte-identical; then, after the fold, `USING SAMPLE` / +`TABLESAMPLE`, `DISTINCT ON`, `QUALIFY`, and the row-position window functions +(`row_number`/`ntile`/`lead`/`lag`/`first_value`/`last_value`/`nth_value`) — each of which +**picks** a row out of a group; then the tie probe on a top-level `ORDER BY`, run only when +the frozen result has more than one row. Plain `DISTINCT` still serves (it collapses a set, +it does not pick), and so does the rank family (a function of the key, deterministic under +ties). An `ORDER BY` below the top serves on a stated basis rather than an assumption: row +order on the constant path is not part of the contract, and a 60k-row measurement under five +DuckDB settings a build machine picks for itself gave **one** answer for the row set and +**five** for the sequence. One ceiling is stated rather than hidden: a window aggregate with +its own `ORDER BY` is order-dependent under ties and is not caught. + +*The generator's planted coverage, and the over-refusal detector.* The campaign could not +reach this shape at all — over seeds 0-39999 the gate found 28 static-only `ORDER BY` cases +and **none of them can tie**. So the generator plants both twins on an auxiliary stream +consulted before the main one: **1% tie, 1% unique**, 44 of 2000 seeds claimed, the other +1956 byte-identical. Grading covers both directions — a unique twin refused under the tie +class is a `DIVERGE_BUILD` **tie-over-refusal**, a tie twin that agrees is a +**tie-under-refusal**. That matters beyond this rule: as the first review established, the +campaign is **structurally blind to over-refusal** — `REFUSED` is terminal and is never +compared against the oracle — so this is the first check in the fuzzer that can see one. + +*Gate (branch `4eee7f3`):* **PASS WITH ONE DEFECT.** Suite 3281 -> **3326** passed (45 new +ids, all passed, **0 shared-id outcome changes**); debug identical; `cargo test` 266 / 5, the +pre-existing set; campaign `AGREE` 1013 -> **1011**, `REFUSED` 944 -> **947**, `AGREE_TRAP` +21 -> **20**, with **35 flips, every one a seed the planted stream claims and zero on any +unclaimed seed**; all 24 tie twins `REFUSED` under the tie class, all 20 unique twins +`AGREE`, **zero** grader findings in either direction; mutation caught (18 tests red). The +gate's own 33 hand probes found **no silent bypass**, including seven DuckDB-only forms +outside `sqlparser`'s grammar. +*The defect:* a trailing `;` or a trailing `--` comment makes the probe's wrapper a parse +error, which the code converts into a refusal — so `... ORDER BY t;` over a **unique** key +refuses where master served, and the message names a cause that is not the real one. Fails +closed, so nothing wrong is frozen; the fix is already in the branch (the hidden-key path +round-trips through DuckDB and drops the trailing token for free). +*Status:* the third iteration's work on this item **returned no record to the loop's +journal**. `origin/refuse-static-tie-order` has since advanced one commit past the gated tip, +**ungated in this loop**. Its last gated state is the one above. + +--- + +## 3. Gate state, branch by branch {#gate-state} + +| branch | tip | gate verdict | campaign delta vs master (2000 seeds) | corpus | state | +|---|---|---|---|---|---| +| `fix-corpus-slip` | `a7c5798` | PASS WITH FINDINGS | 0 flips; census identical | 547 / 131 / 0 both legs | **merged** (PR #200) | +| `fix-nan-sign-varchar` | `57a05ff` | **PASS**, no defects | 1 flip: seed 1804 `DIVERGE_VALUE` -> `AGREE`; `DIVERGE_VALUE` 1 -> 0 | 547 / 131 / 0 both legs | awaiting the owner's approval | +| `fix-fmod-sign` | `ec71979` | PASS, with a scope correction | 1 flip: the same seed 1804; `DIVERGE_VALUE` 1 -> 0 | 547 / 131 / 0 both legs | awaiting the owner's approval | +| `refuse-static-tie-order` | `4eee7f3` gated; tip now `21e3fdc` | PASS WITH ONE DEFECT (at `4eee7f3`) | 35 flips, all planted seeds; `AGREE` 1013 -> 1011, `REFUSED` 944 -> 947, `AGREE_TRAP` 21 -> 20 | 547 / 131 / 0 both legs | not ready; tip ungated | + +Suite counts, each from the gate that produced it: master **3281** passed / 1 skipped / 3 +xfailed / 2 errors on every leg; `fix-nan-sign-varchar` **3297**; `fix-fmod-sign` **3284**; +`refuse-static-tie-order` **3326**. Every branch was also run on a separately built **debug** +engine with identical results and no `debug_assert` firing. Dialect L2 reads **288/678** with +0 FAIL wherever it was taken. The public API diff is empty on all four. + +**Two branches contend for one line.** `fix-fmod-sign` and `fix-nan-sign-varchar` both change +the same `DuckF64` NaN arm, and both flip the same single seed. `fix-fmod-sign` is the +narrower one: its own gate records that the promise it was given ("`%` on DOUBLE matches") is +**wider than the change** — the modulo values already matched bit-for-bit on both backends, +and only the text was wrong. What it adds beyond the shared line is a 29-row sign grid over +`%` / `mod` / `fmod` on a value leg and a text leg, on both backends. `fix-nan-sign-varchar` +carries the same fix plus the `Fneg` lowering, the residual classifier and the IR text form. +They are not both mergeable as they stand; the second subsumes the first's production change. + +**A review of the gated `fix-nan-sign-varchar` commit measured one HIGH the gate did not.** +The `Fneg` lowering drops the fold-then-null-operand short circuit that `-0.0 - x` had, so +`- ` no longer binds to `NullOf` and the `||` SQLNULL collapse breaks: **five of +eight probed shapes diverge from DuckDB on the branch where all eight matched on master, and +two constructs master served are now bind refusals**. The full suite is blind to all six +(3297 passed over it). The review states the one-line restoration and reports it keeps the +branch's own tests green. That is a review record, not a gate's — but it is measured, and it +is the reason this branch is not simply ready. + +--- + +## 4. Measured facts for the next full reading {#for-the-next-reading} + +Facts this loop produced that belong in reading **N=2**, not in this report's conclusions. + +**Acceptance changed, and not only by fixing things.** `refuse-static-tie-order` moves +`AGREE` 1013 -> 1011 and `REFUSED` 944 -> 947 on the same seed range — but the comparison is +**not like-for-like**: 44 of the 2000 seeds are now planted twins rather than grammar draws, +so roughly **2% of every campaign** is two fixed queries. One displaced seed carried an +`AGREE_TRAP` that nothing else covers (which is the whole of the 21 -> 20 move), and seed +numbers cited in older repros silently change meaning. Any next census over this generator is +measuring a slightly different population; the baseline's validity caveat under +acceptance-reading now has a second reason to bite. + +**Five new refusal messages exist and only one is graded.** The tie rule's over-refusal +detector recognizes the tie class alone, so an over-refusal arriving under any of its other +four messages is filed as a plain `REFUSED` and never reported (review record, reproduced). +Every new message carries the documented `unsupported:` prefix, so gap: +undocumented-refusal-prefixes does not grow — but the naming half of kpi: no-third-mode gains +five more untested claims. + +**The corpus count did not move again.** 547 / 131 / 0 on every leg of every branch this loop +gated. `MATCH_FLOOR` now holds at exactly the current count, **with zero headroom** — so the +next correct new refusal trips it on the day it lands, by design. Two gates named that +explicitly. + +**The Rust unit gate is red on master and CI cannot see it.** `cargo test` is 266 passed / 5 +failed on every master leg the loop ran, the same five names each time, and CI runs only +`pytest`. Three separate gate records say so independently. A regression inside +`exec::tests` would pass a green-bar check today. This is an **enforcement fault**, the shape +the baseline reading calls a finding rather than a gap, and no item in this loop owned it. + +**Still open, each measured, none acted on:** + +- The `- ` collapse on `fix-nan-sign-varchar` (the-gate-state section above). +- `x % y`'s NaN sign is **unmatchable in principle**, not merely unfixed: two gates + independently reproduced DuckDB answering **43 identical rows two ways in one query** — + the vectorized lanes give one bit pattern, the scalar tail another — stable across 20 + repeats. No engine value can be right there, and the fmod branch's grid correctly excludes + the domain instead of pinning it. +- A **single-side** negated DOUBLE in a JOIN ON residual is now a **provable over-refusal**: + a sign flip is total, but `may_trap`'s catch-all still counts it as trapping. One line + closes it; it was deliberately left out because it accepts SQL master refused, and widening + acceptance unmeasured inside a review-closure branch is the wrong place for it. +- The tie rule's residual classes, all measured by review against the branch build: + `ORDER BY COLUMNS(...)` read as `ORDER BY ALL`; `ORDER BY *` never reaching the star arm; + a nondeterministic sort key (`random()`) frozen — **eight builds gave four different + sequences**; selection-by-position **aggregates** (`first` / `any_value` / `arg_max` / + `string_agg` / `list`) absent from the refusal set and measured to move under five DuckDB + settings; and the inner-`ORDER BY` carve-out unsound when an order-sensitive consumer sits + above it. +- The out-of-range cast trap stops one word short of DuckDB's text (`... destination type` + against `... destination type INT64`). Naming the type means plumbing the SQL destination + width to the trap site; the branch pins ours as DuckDB's message **truncated** and says so + in `known-limitations.md` rather than overclaiming. +- The unsigned-column refusal class — the reason three mined statements now refuse — is named + **nowhere** in `known-limitations.md`. Same bookkeeping shape as gap: + undocumented-boolean-comparison, and it belongs with that entry. +- The four stale `550` sites and the `known-limitations.md` line the corrected README points + at (gap: corpus-match-slip's remaining half). + +--- + +## 5. Spend {#spend} + +| iteration | agent tokens | +|---|---| +| 1 (corpus slip, seed 1804, tie order — three implementers, three gates, two reviews) | ~1.45M | +| 2 (seed 1804 rework, tie order rebuild — two implementers, two gates, two reviews) | ~1.33M | +| 3 (seed 1804 closure, modulo sign — two implementers, two gates, two reviews) | see next report | + +Iteration 3's figure is not recorded on the same basis in this run's journal, so it is left +for the next report rather than restated on a basis that would not compare. What is visible: +iteration 3 ran the same three-role shape at the same fan-out as iteration 1. + +**The standing stop rule is unchanged: roughly 70% of the owner's weekly credit, and it is +owner-signalled** — the loop does not infer it from its own accounting. + +--- + +## 6. Next, in the goal's order {#next} + +`goal.md` orders controls before drives, so the queue does too. Nothing here is chosen; it is +what the loop's own measurements rank. + +1. **Finish the parity control in flight.** kpi: engine-parity has `DIVERGE_VALUE` at 0 on + two branches that cannot both land. The decision the owner owns: take + `fix-nan-sign-varchar` (which subsumes the other's production change) with the + `- ` regression closed first, and take `fix-fmod-sign`'s grid as tests only — + or reverse it and lose the `Fneg` work. This is the one item where a merge is blocked on a + choice rather than on more measurement. +2. **finding: static-only-tie-order is still a silent-wrongness class.** The gated branch + fails closed on one named over-refusal and leaves five measured shapes serving a frozen + arbitrary answer. A control violation is never a gap to live with, so this outranks every + `gap:` entry below it — but the branch's tip is ungated and its residuals are a rule + question (which order-dependent constructs must refuse), not a bug list. +3. **The enforcement faults nobody owns.** The red Rust unit gate above, and finding: + c1-depth, untouched by this loop and still routed to ask: kpi-set-change. +4. **gap: bench-baseline-flip's cheapest cause is still untested.** One re-run after + `--reinstall-package` rules out the stale-wheel signature (d). No iteration in this loop + touched it, and kpi: bench-refresh-cadence should not be adopted before it is settled. +5. **Then the gap ledger, in whatever order ask: next-query-classes gets answered.** That + question is the owner's and remains open; the loop has added no evidence that reorders its + candidates, only evidence that gap: corpus-match-slip's ratchet half is now real and its + bookkeeping half is not. + +**Reading N=2 replaces none of this.** This report is what moved between readings; the next +full reading is what the numbers are. From 3c7c59418947d17204c02a7301c8680937eb1c35 Mon Sep 17 00:00:00 2001 From: AmirHossein Roozbahani Date: Sun, 6 Sep 2026 00:44:17 +0200 Subject: [PATCH 2/8] the loop report gains iterations 3 and 4: one branch redundant, one blocked The 2026-09-02 text was written while two of its three items were still moving. This amends it in place rather than starting a second report, because what changed is the same distances, freshly measured. fix-fmod-sign is dropped, not queued: its production change was one line already carried on fix-nan-sign-varchar, and the modulo kernel matched DuckDB bit-for-bit all along. Its sign grid survives as tests on the branch that kept the line. fix-nan-sign-varchar closes the review HIGH the first text left open - the Fneg node had dropped the fold-then-null short circuit - and gates PASS at 550f949 with DIVERGE_VALUE 0 and one seed flipped of 2000. Seven review findings remain, none of them a wrong answer. refuse-static-tie-order gains the record it never returned: gated at 21e3fdc, three fail-open HIGHs found by review, nine of eleven closed at a08147e with two rejected on measured facts, gate PASS. It is blocked on one HIGH found after that gate - ORDER BY #N measures the wrong key and serves a frozen tied order, which is the class the branch exists to close. Spend: iteration 3 is now recorded at ~1.76M, cumulative ~4.5M across iterations 1-3. Iteration 4 is left to the next report rather than restated on a basis that would not compare. Co-Authored-By: Claude Fable 5.1 --- .../docs/reports/2026-09-02-loop-report-1.md | 435 +++++++++++++----- 1 file changed, 319 insertions(+), 116 deletions(-) diff --git a/packages/confit/docs/reports/2026-09-02-loop-report-1.md b/packages/confit/docs/reports/2026-09-02-loop-report-1.md index cee43d91..2dafddf9 100644 --- a/packages/confit/docs/reports/2026-09-02-loop-report-1.md +++ b/packages/confit/docs/reports/2026-09-02-loop-report-1.md @@ -1,4 +1,4 @@ -# The loop's first report: iterations 1-3 (2026-09-02) +# The loop's first report: iterations 1-4 (2026-09-02, amended 2026-09-06) **What this is.** A dated report of one loop's work against `packages/confit/docs/goal.md`, read through the yardsticks the baseline reading measured @@ -10,6 +10,14 @@ the branch itself, never from an implementer's own claim, and each is named to i Where only a review measured something, the item says "review record" and the claim stands as a review's, not a gate's. +**What the amendment changed.** The 2026-09-02 text was written while two of its three items +were still moving: the modulo branch's own gate had not yet been read against the NaN +branch's, and the tie rule's rebuild had returned no record to the loop's journal. Iteration +4 closed both, and iteration 3's missing record has since been read. Every number added below +is quoted from a gate or review record taken after that text was written; wherever a figure +replaces one the first text carried, the old figure is named beside it so the two are not +silently conflated. + **Slugs.** `gap:` and `finding:` citations resolve in the baseline reading; `goal:`, `kpi:`, `exclusion:` and `ask:` in `goal.md`; `claim:` and `divergence:` without a local definition in `packages/confit/docs/oracle/`. Sections here carry kebab-case anchors and are @@ -19,7 +27,7 @@ cited by slug, never by number. ## 1. What the loop is {#the-loop} -The owner's mandate, as it stood over these three iterations: **make confit match `goal.md`** +The owner's mandate, as it stood over these four iterations: **make confit match `goal.md`** — close the distance the baseline reading measured, in the order the goal document's own priorities give. Its working rules: @@ -39,11 +47,13 @@ source rather than trusting a shipped artifact. That structure is the reason thi name a gate for every number: the gate legs are what produced them. **One measurement about the loop itself, worth keeping.** Every master leg the loop ran — -**seven of them across three iterations** — reproduced the baseline's campaign census +**eleven of them across four iterations** — reproduced the baseline's campaign census exactly: `AGREE` 1013 / `REFUSED` 944 / `AGREE_TRAP` 21 / `UNSHIPPED` 14 / `DIVERGE_OPT` 7 / `DIVERGE_VALUE` 1 over seeds 0-1999 at `--workers 8 --timeout 20`. Master advanced twice in the window (`2ba96e5` -> `2c7c05c` -> `f81e17c`, docs plus one added assertion) and the census -did not move. claim: campaign-verdicts-today is reproducible on this machine, and a single +did not move; it has stood at `f81e17c` since, across the four further master legs iterations +3 and 4 ran, each of which also re-checked the sanity seed and found 1804 the +`DIVERGE_VALUE`. claim: campaign-verdicts-today is reproducible on this machine, and a single seed flipping is therefore signal, not scheduling noise. --- @@ -123,23 +133,74 @@ invisible while every NaN rendered alike: the defect). The third iteration closed it with one arm added to an existing or-pattern, red-first over four residual shapes. 3. **The double-modulo sign**, which the same rendering change made visible, went to its own - branch rather than being folded in here. - -*What closed it:* `fix-nan-sign-varchar`, three iterations deep — the formatter, then the + branch rather than being folded in here. Iteration 4 established that branch was + **redundant** and dropped it; see below. + +**The `||` collapse the `Fneg` node broke, and its closure.** A review of the gated +`57a05ff` measured one HIGH the gate had not: the new total `Fneg` dropped the +fold-then-null-operand short circuit that `-0.0 - x` had carried for free, so +`- ` stopped binding to `NullOf` and the `||` SQLNULL collapse broke behind it. +Iteration 4 closed it red-first, and the red was wider than the review's report — **10 shapes +x 2 backends through the oracle, comparing the output SCHEMA as well as the rows, 12 failed +and 8 passed**, which is the reviewer's four divergences plus the two constructs master +served that had become bind refusals. The fix is arith's own rule spelled for the unary form: +fold the operand, and return `null_of(F64)` on a folded `NullOf` before reaching +`math1_node`. Pinned at `tests/known_divergences/test_literal_typing.py`. + +**The modulo branch was redundant.** `fix-fmod-sign` was opened on the reading that `%` on +DOUBLE diverged. It did not. Iteration 4 established that **the kernel already matched DuckDB +bit-for-bit on both backends**, and the whole of its apparent divergence was the same +`DuckF64` NaN-sign short circuit this item fixes — one line, already carried here. The branch +is **dropped**: none of its production diff survives, because its `kernels.rs` change was +already on `fix-nan-sign-varchar` and its interpreter-test edit was not taken. + +*What was salvaged from it:* the parity grid only — `MOD_SIGN_GRID` and +`test_double_mod_sign_grid_value_and_text`, moved into +`tests/test_duckdb_wave3_mathtail.py` and adjusted to project `x` and `y` so a mismatch names +its row (the comparison is a multiset). One test was deliberately **not** taken: +`test_mod_by_zero_nan_sign_is_not_a_property_of_the_row` asserts that the *oracle* is +self-inconsistent, which is a UCRT-dependent claim; its substance survives as the grid's +header comment naming why the invalid-operation domain (divisor zero, infinite dividend) is +excluded. The grid is mutation-proved where it now lives: drop `DuckF64`'s NaN sign and it +fails, naming the `fmod`-by-infinity rows; unmutated it passes on both backends. + +*What closed it:* `fix-nan-sign-varchar`, four iterations deep — the formatter, then the `Fneg` lowering plus eight review findings, then eight more (the residual classifier, a bit-level two-backend `fneg` test, `-nan` surviving the IR text round trip, the cast-trap -text pinned as DuckDB's message truncated, and the stale docs). One review finding was -**rejected with measurement** rather than absorbed: the pin the review read as -platform-dependent uses a defined constant's bit pattern, which the repo rule exempts, and -the new Rust pin builds both NaNs from explicit bit patterns anyway. -*Gate (branch tip `57a05ff`, master `f81e17c`):* **PASS, no defects found.** Suite 3281 -> -**3297** passed (junit 3287 -> 3303, **16 new ids all passed, 0 shared-id outcome changes, 0 -disappeared**); the debug build gives the identical set from a genuinely different engine -(23.2 MB against 17.6 MB); `cargo test` 266 -> **268** passed with the failing **set** -identical; campaign `DIVERGE_VALUE` **1 -> 0** with **exactly one seed flipped**, compared at -full-record granularity, not at the summary level; corpus **547 / 131 / 0** and dialect L2 -**288/678** on both legs; four independent mutations each caught and each restored by -re-edit; the public API diff is empty. +text pinned as DuckDB's message truncated, and the stale docs), then iteration 4's eight as +`c361549` — the `||` collapse above plus seven comment, pin and coverage findings — and the +salvage as `550f949`. One review finding was **rejected with measurement** rather than +absorbed: the pin the review read as platform-dependent uses a defined constant's bit +pattern, which the repo rule exempts, and the new Rust pin builds both NaNs from explicit bit +patterns anyway. +*Gate (branch tip `550f949`, master `f81e17c`):* **PASS.** Suite 3281 -> **3320** passed, 1 +skipped, 3 xfailed, the same two absent-`pyspark` collection errors, with **39 new node ids +all passing, 0 shared-id outcome changes, 0 disappeared**; the separately built **debug** +engine gives the identical set; `cargo test -p confit --lib` 266 -> **269** passed with the +failing **set** identical, the same five names; campaign `DIVERGE_VALUE` **1 -> 0** with +**exactly one seed flipped** of 2000 — seed 1804, its +`struct_pack(f0 := CAST(pow(-0.25e0, 0.1e0) AS VARCHAR))` where master wrote `nan` and DuckDB +writes `-nan` — every other 1999 seeds identical in (kind, klass), compared at full-record +granularity rather than at the summary level; corpus **547 / 131 / 0** and dialect L2 +**288/678** on both legs; the public API diff is empty. Five mutations, each caught and each +restored by re-edit: the `DuckF64` sign -> 12 red, `fneg` back to `-0.0 - x` on both backends +-> 6, the fold plus the `NullOf` early return -> 20, unteaching `scan_residual` about `Fneg` +-> 4, the old residual message -> 1. The **+23** over the branch's own previous 3297 is 20 +null-typing cases (10 shapes x 2 backends), the 2 salvaged mod-grid rows and 1 residual +refusal. +*Two caveats the gate names, neither an API or format change:* the out-of-range +DOUBLE-to-int cast trap **text** now spells the value DuckDB's way (`NaN` -> `nan` / `-nan`), +and JOIN residual refusals gained a second wording; both keep their `ValueError +unsupported:` / `Conversion Error` classes, and `known-limitations.md` records the remaining +` INT64` gap. +*Left open by a review of the gated tip:* **seven findings, none HIGH.** Three medium — +`Lit`'s `PartialEq` still compares opposite-signed NaNs equal, so the print/parse round trip +this branch strengthened cannot itself see a dropped sign; the IR generator gained `Fneg` but +no negative-NaN constant, so the fuzz round trip has zero coverage of the sign added; and the +new "classifier does not recognise" refusal selects on `known || !(left && right)`, which +still misdiagnoses a two-sided residual whose column references sit inside the unrecognised +node — and four low. Every one is a naming, coverage or comment defect over a production diff +the gate found clean; none is a wrong answer. ### 2.3 finding: static-only-tie-order @@ -147,8 +208,8 @@ re-edit; the public API diff is empty. whole-relation construct selects is frozen **only when it is a function of the query**. *Was:* nothing refused a tie-producing `ORDER BY`; two builds of the same function could freeze different orders. A silent-wrongness class. -*Now:* a rule that refuses it, generalized well past the shape it started as — and gated to -one named defect. +*Now:* a rule that refuses it, rebuilt on the principle rather than the shape and generalized +well past both — gated **PASS**, and blocked on one node type the rule still reads wrong. **The rule generalized.** The first iteration read the query with `sqlparser` and asked about `ORDER BY` ties. Its gate returned **FAIL** on two independently reproduced defects: one pair @@ -177,6 +238,17 @@ DuckDB settings a build machine picks for itself gave **one** answer for the row **five** for the sequence. One ceiling is stated rather than hidden: a window aggregate with its own `ORDER BY` is order-dependent under ties and is not caught. +*What the rebuild added on top of that*, and what the first text could not see because the +branch returned no record: two **catalogue** rules, read off the same connection that folds +the query. A function whose `duckdb_functions().stability` is `VOLATILE` or +`CONSISTENT_WITHIN_QUERY` refuses (`random`, the clock family); and an aggregate refuses **by +name** unless it is one of the eleven DuckDB's own source flags `NOT_ORDER_DEPENDENT`, which +is **65 of the 88 distinct aggregate names**, `sum` and `avg` among them. The coarseness is +deliberate and fail-closed — DuckDB's flag is the authority and it defaults to +`ORDER_DEPENDENT` — and the upgrade path (read the bound overload off the result type) is +written down rather than guessed at. It is also the whole of the branch's acceptance loss; +the campaign delta below prices it. + *The generator's planted coverage, and the over-refusal detector.* The campaign could not reach this shape at all — over seeds 0-39999 the gate found 28 static-only `ORDER BY` cases and **none of them can tie**. So the generator plants both twins on an auxiliary stream @@ -187,22 +259,96 @@ class is a `DIVERGE_BUILD` **tie-over-refusal**, a tie twin that agrees is a campaign is **structurally blind to over-refusal** — `REFUSED` is terminal and is never compared against the oracle — so this is the first check in the fuzzer that can see one. -*Gate (branch `4eee7f3`):* **PASS WITH ONE DEFECT.** Suite 3281 -> **3326** passed (45 new -ids, all passed, **0 shared-id outcome changes**); debug identical; `cargo test` 266 / 5, the -pre-existing set; campaign `AGREE` 1013 -> **1011**, `REFUSED` 944 -> **947**, `AGREE_TRAP` -21 -> **20**, with **35 flips, every one a seed the planted stream claims and zero on any -unclaimed seed**; all 24 tie twins `REFUSED` under the tie class, all 20 unique twins -`AGREE`, **zero** grader findings in either direction; mutation caught (18 tests red). The -gate's own 33 hand probes found **no silent bypass**, including seven DuckDB-only forms -outside `sqlparser`'s grammar. -*The defect:* a trailing `;` or a trailing `--` comment makes the probe's wrapper a parse -error, which the code converts into a refusal — so `... ORDER BY t;` over a **unique** key -refuses where master served, and the message names a cause that is not the real one. Fails -closed, so nothing wrong is frozen; the fix is already in the branch (the hidden-key path -round-trips through DuckDB and drops the trailing token for free). -*Status:* the third iteration's work on this item **returned no record to the loop's -journal**. `origin/refuse-static-tie-order` has since advanced one commit past the gated tip, -**ungated in this loop**. Its last gated state is the one above. +*Gate (branch `21e3fdc`, iteration 3's rebuild):* **PASS WITH FINDINGS.** Suite 3281 -> +**3358** passed on release and on the debug build, 1 skipped, 3 xfailed, the same two +`pyspark` errors; `cargo test` 266 / 5, master's exact set; corpus **547 / 131 / 0** on both +legs; **84 flips**, every one attributed; mutation caught (`if false &&` on the tie-refusal +return -> 20 red), and a second mutation on the generator's own determinism caught too — +tying a unique twin while keeping its determined tag raises `DIVERGE_BUILD` on seeds +25 / 86 / 193. 22 adversarial probes beyond the suite all land right. The gate named two +over-refusals, both fail-closed, both disclosed in `known-limitations.md`: +`first(v ORDER BY k)` with a unique `k` refuses whole (the branch never reads an aggregate's +own `ORDER BY`), and `sum` refuses by name even over integers. + +*The review that followed found eleven, three of them HIGH* — and all three were the same +shape: a **fail-open** path wherever the reading gave up. A `LIMIT`/`OFFSET` whose argument +is not a bare constant (`LIMIT 1+1`, `CAST(2 AS BIGINT)`, `(SELECT 2)`) read as a no-op limit +and **served** a scan-order row subset, a regression against master, whose deleted +`sqlparser` walk refused any limit clause. A string holding more than one statement returned +early and skipped **every** value rule, so `"SELECT 1 AS o; SELECT random() AS o FROM s"` +served three frozen draws. And a statement DuckDB will not serialize but will run — +`PIVOT s ON g USING first(v)` — took the same early return and served, which is exactly the +case the docs claimed "refuses rather than falling silent". + +*What iteration 4's fix round closed:* **nine of the eleven; two rejected on the facts.** The +three HIGHs close by splitting the two things one predicate had conflated — JSON `null` (a +genuinely absent limit side, still a no-op) from a node that is present but unreadable (now a +real limit) — and by refusing on the **statement count** and on the serialization failure +alone, with no ordering word required. Four mediums close with them: built-in **macros** that +read the clock (`stability` is `NULL` for all 131 scalar-macro rows, so nine clock macros +served a frozen value), the catalogue being read before the query binds, `rowid` projecting a +physical scan position as an ordinary value, and the class the review named as `stability` +answering "constant within one query" rather than "a function of the query text". That last +one was **worse than reported**, and the fix round found the extra case in the pinned source +rather than by probing: `current_localtimestamp` is registered in +`extension/icu/icu-timezone.cpp` with no `SetStability`, so it inherits `CONSISTENT` — and +its value **moved between two connections 50 ms apart**, measured. The bare words +`localtime` / `localtimestamp` were already refused; the call spelling of the same function +was not. + +*The two rejections, both measured rather than argued.* Adding `sum_no_overflow` to the +order-free list would be dead code: it is in the catalogue but it does not bind +(`sum_no_overflow is for internal use only!`), and a name no query can call cannot be +over-refused. And widening the over-refusal detector past the planted twin was rejected as +**unsound**, not deferred: the tag means "every value here is fixed by the query and the +statics", and the generator cannot assert that of a case it has just built without +reimplementing the rule under test — tagging a generated case that carries `first`, `avg` or +a real `LIMIT` would manufacture false findings, which is worse than the silence complained +of. + +*Gate (branch `a08147e`, iteration 4):* **PASS**, with two over-refusals and a build-time +cost, all three intended and disclosed. Suite 3281 -> **3366** passed on release and on the +debug build, 1 skipped, 3 xfailed, the same two `pyspark` errors, with **85 new node ids, 0 +disappeared, 0 shared-id outcome changes**; `cargo test` 266 / 5, master's exact set; corpus +**547 / 131 / 0**, `MATCH_FLOOR` met exactly; the public API diff is empty; two mutations +caught (22 red and 5 red), each restored by re-edit with a clean tree afterwards; nine hand +probes and five controls all land right, and all five of `goal.md`'s code claims reproduce +verbatim. + +*Campaign delta, and the honest reading of it.* `AGREE` 1013 -> **1003**, `REFUSED` 944 -> +**955**, `AGREE_TRAP` 21 -> **20**, `UNSHIPPED` 14, `DIVERGE_OPT` 7, `DIVERGE_VALUE` 1, with +**0 `DIVERGE_BUILD`** and seed 1804 still the `DIVERGE_VALUE` on both legs. **84 flips, 33 of +them kind flips, every one attributed** — and the fix round adds **zero** of them: the 84 are +identical to the pre-fix branch's 84, and the SQL text is byte-identical on all 1956 +unclaimed seeds. They split three ways. (A) **35** are seeds the planted static-order stream +claims — all 24 tie twins `REFUSED` under the tie class, all 20 unique twins `AGREE`, the +nine that did not flip already agreeing. (B) **41** are `REFUSED` -> `REFUSED` message-class +changes on identical SQL, every one verified **dynamic**: master mislabelled a row-path limit +error as a static-tables-only refusal, and the branch returns the row path's own wording. A +strict improvement. (C) **8** are `AGREE` -> `REFUSED` on identical SQL, all static-only +aggregates — `sum` at seeds 130 / 545 / 975 / 1484 / 1967 and `avg` at 113 / 1036 / 1314 — +which is the by-name coarsening above arriving as acceptance loss. The arithmetic reconciles +exactly: -13 +11 -8 = -10 on `AGREE`, +14 -11 +8 = +11 on `REFUSED`, -1 on `AGREE_TRAP`. +These figures replace the first text's for this branch (`4eee7f3`: 35 flips, `AGREE` 1011, +`REFUSED` 947); the count grew because the rule did, not because the campaign moved. What the +fix round newly refuses, the generator never produces, so the campaign is silent about it by +construction and each shape is pinned by a unit test instead. + +*The cost, measured rather than estimated.* One static-only fold goes **15.6 -> 64.5 ms** +(`max(v)`) and **13.4 -> 72.5 ms** (`ORDER BY k`), roughly 4-5x, over 50 runs each, from the +extra `json_serialize_sql` / `duckdb_functions` / tie-probe round trips. The row path is +unchanged (0.20 -> 0.17 ms) and serving is untouched. That is the whole full-suite delta +(258s -> 365s), and it is identical on the debug build — which is what proves it is DuckDB +round trips and not our codegen. + +*Status:* **blocked**, on one HIGH a review measured after the gate passed. `ORDER BY #N` — +DuckDB's positional **output** reference — is not one of the three node types the tie probe +reads, so it falls into the hidden-key arm and is re-emitted into the probe's `SELECT` list, +where `#N` binds to the **N-th input column** instead. The probe then measures the wrong key: +`SELECT g AS o, 1 AS c FROM s ORDER BY #2` serves a frozen order over a key that ties all +three rows, while the same query spelled `ORDER BY 2` refuses. That is the silent-wrongness +class this branch exists to close, still open inside it, and it is why the branch is not a +merge candidate as it stands. Four lesser findings ride with it (for-the-next-reading below). --- @@ -211,33 +357,34 @@ journal**. `origin/refuse-static-tie-order` has since advanced one commit past t | branch | tip | gate verdict | campaign delta vs master (2000 seeds) | corpus | state | |---|---|---|---|---|---| | `fix-corpus-slip` | `a7c5798` | PASS WITH FINDINGS | 0 flips; census identical | 547 / 131 / 0 both legs | **merged** (PR #200) | -| `fix-nan-sign-varchar` | `57a05ff` | **PASS**, no defects | 1 flip: seed 1804 `DIVERGE_VALUE` -> `AGREE`; `DIVERGE_VALUE` 1 -> 0 | 547 / 131 / 0 both legs | awaiting the owner's approval | -| `fix-fmod-sign` | `ec71979` | PASS, with a scope correction | 1 flip: the same seed 1804; `DIVERGE_VALUE` 1 -> 0 | 547 / 131 / 0 both legs | awaiting the owner's approval | -| `refuse-static-tie-order` | `4eee7f3` gated; tip now `21e3fdc` | PASS WITH ONE DEFECT (at `4eee7f3`) | 35 flips, all planted seeds; `AGREE` 1013 -> 1011, `REFUSED` 944 -> 947, `AGREE_TRAP` 21 -> 20 | 547 / 131 / 0 both legs | not ready; tip ungated | +| `fix-nan-sign-varchar` | `550f949` | **PASS** | 1 flip: seed 1804 `DIVERGE_VALUE` -> `AGREE`; `DIVERGE_VALUE` 1 -> 0 | 547 / 131 / 0 both legs | **awaiting the owner's approval click** | +| `fix-fmod-sign` | `ec71979` | PASS, then read as redundant | its one flip was the same seed 1804, already carried by the branch above | 547 / 131 / 0 both legs | **dropped**; grid salvaged | +| `refuse-static-tie-order` | `a08147e` | PASS (two disclosed over-refusals, 4-5x static-only build cost) | 84 flips, all attributed; `AGREE` 1013 -> 1003, `REFUSED` 944 -> 955, `AGREE_TRAP` 21 -> 20 | 547 / 131 / 0 both legs | **blocked** on one HIGH | Suite counts, each from the gate that produced it: master **3281** passed / 1 skipped / 3 -xfailed / 2 errors on every leg; `fix-nan-sign-varchar` **3297**; `fix-fmod-sign` **3284**; -`refuse-static-tie-order` **3326**. Every branch was also run on a separately built **debug** -engine with identical results and no `debug_assert` firing. Dialect L2 reads **288/678** with -0 FAIL wherever it was taken. The public API diff is empty on all four. - -**Two branches contend for one line.** `fix-fmod-sign` and `fix-nan-sign-varchar` both change -the same `DuckF64` NaN arm, and both flip the same single seed. `fix-fmod-sign` is the -narrower one: its own gate records that the promise it was given ("`%` on DOUBLE matches") is -**wider than the change** — the modulo values already matched bit-for-bit on both backends, -and only the text was wrong. What it adds beyond the shared line is a 29-row sign grid over -`%` / `mod` / `fmod` on a value leg and a text leg, on both backends. `fix-nan-sign-varchar` -carries the same fix plus the `Fneg` lowering, the residual classifier and the IR text form. -They are not both mergeable as they stand; the second subsumes the first's production change. - -**A review of the gated `fix-nan-sign-varchar` commit measured one HIGH the gate did not.** -The `Fneg` lowering drops the fold-then-null-operand short circuit that `-0.0 - x` had, so -`- ` no longer binds to `NullOf` and the `||` SQLNULL collapse breaks: **five of -eight probed shapes diverge from DuckDB on the branch where all eight matched on master, and -two constructs master served are now bind refusals**. The full suite is blind to all six -(3297 passed over it). The review states the one-line restoration and reports it keeps the -branch's own tests green. That is a review record, not a gate's — but it is measured, and it -is the reason this branch is not simply ready. +xfailed / 2 errors on every leg; `fix-nan-sign-varchar` **3320**; `refuse-static-tie-order` +**3366**; `fix-fmod-sign` stood at **3284** when it was gated, and is moot now. Every branch +was also run on a separately built **debug** engine with identical results and no +`debug_assert` firing. Dialect L2 reads **288/678** with 0 FAIL wherever it was taken. The +public API diff is empty on all four, and `cargo test`'s failing **set** is master's five on +all four. + +**The contention resolved by measurement, not by a choice.** `fix-fmod-sign` and +`fix-nan-sign-varchar` both changed the same `DuckF64` NaN arm and both flipped the same +single seed, and the first text left the pick to the owner. Iteration 4 removed the pick: +`fix-fmod-sign`'s production change **was** that one line, already carried on the other +branch, so there was nothing to choose between. Its 29-row sign grid over `%` / `mod` / +`fmod` now lives on `fix-nan-sign-varchar` as tests, and the branch is dropped. One candidate +remains for kpi: engine-parity, and it is a click away rather than a decision away. + +**Both live branches carry review findings their gates did not raise, and the two are not the +same weight.** On `fix-nan-sign-varchar` the seven are naming, coverage and comment defects +over a production diff the gate found clean — an equality that cannot see a NaN sign, a +generator that never emits one, a refusal selector that misdiagnoses a shape it already +refuses correctly. None of them answers a query wrongly. On `refuse-static-tie-order` one of +the five **does**: `ORDER BY #N` freezes a tied order and serves it. A control violation +inside the branch chartered to close that control is a different class of open item from a +stale comment, and the table above states the two differently on purpose. --- @@ -246,55 +393,99 @@ is the reason this branch is not simply ready. Facts this loop produced that belong in reading **N=2**, not in this report's conclusions. **Acceptance changed, and not only by fixing things.** `refuse-static-tie-order` moves -`AGREE` 1013 -> 1011 and `REFUSED` 944 -> 947 on the same seed range — but the comparison is -**not like-for-like**: 44 of the 2000 seeds are now planted twins rather than grammar draws, -so roughly **2% of every campaign** is two fixed queries. One displaced seed carried an -`AGREE_TRAP` that nothing else covers (which is the whole of the 21 -> 20 move), and seed -numbers cited in older repros silently change meaning. Any next census over this generator is -measuring a slightly different population; the baseline's validity caveat under -acceptance-reading now has a second reason to bite. - -**Five new refusal messages exist and only one is graded.** The tie rule's over-refusal -detector recognizes the tie class alone, so an over-refusal arriving under any of its other -four messages is filed as a plain `REFUSED` and never reported (review record, reproduced). -Every new message carries the documented `unsupported:` prefix, so gap: +`AGREE` 1013 -> **1003** and `REFUSED` 944 -> **955** on the same seed range — but the +comparison is **not like-for-like**, for two reasons that pull differently. 44 of the 2000 +seeds are now planted twins rather than grammar draws, so roughly **2%** of every campaign is +two fixed queries; one displaced seed carried an `AGREE_TRAP` that nothing else covers (the +whole of the 21 -> 20 move), and seed numbers cited in older repros silently change meaning. +Separately, **8** of the flips are a deliberate over-refusal — `sum` and `avg` on the +static-only path — so part of the acceptance loss is a rule the loop chose, not a population +artefact and not a defect. Any next census over this generator is measuring a slightly +different population under a slightly stricter rule; the baseline's validity caveat under +acceptance-reading now has two reasons to bite rather than one. + +**The over-refusal detector is class-agnostic; the generator is not, and that is now an +argued position rather than an oversight.** A test pins the detector as reading every refusal +class, but the determined tag is set only on the planted unique twin, so **no generated** +static-only case is ever graded for over-refusal — and the branch's new 65-name aggregate +refusal surface therefore has no campaign-level regression detector at all. The fix round +**rejected** widening it: the tag asserts "every value here is fixed by the query and the +statics", and a generator cannot assert that of a case it just built without reimplementing +the rule under test, which would manufacture false findings. So the silence stands, with a +reason. Every new message still carries the documented `unsupported:` prefix, so gap: undocumented-refusal-prefixes does not grow — but the naming half of kpi: no-third-mode gains -five more untested claims. +its untested claims regardless. **The corpus count did not move again.** 547 / 131 / 0 on every leg of every branch this loop -gated. `MATCH_FLOOR` now holds at exactly the current count, **with zero headroom** — so the -next correct new refusal trips it on the day it lands, by design. Two gates named that -explicitly. +gated, iterations 3 and 4 included — and the tie branch is the interesting case, because it +adds a whole new refusal surface and still meets the floor **exactly**, neither tripping it +nor growing past it. `MATCH_FLOOR` holds at the current count with **zero headroom**, so the +next correct new refusal trips it on the day it lands, by design. Six gates have now named +that explicitly. **The Rust unit gate is red on master and CI cannot see it.** `cargo test` is 266 passed / 5 -failed on every master leg the loop ran, the same five names each time, and CI runs only -`pytest`. Three separate gate records say so independently. A regression inside -`exec::tests` would pass a green-bar check today. This is an **enforcement fault**, the shape -the baseline reading calls a finding rather than a gap, and no item in this loop owned it. +failed on every master leg the loop ran, the same five names each time +(`pin_ftoi_rounding_and_traps`, `pin_ssubstr_window_arithmetic`, +`pin_stoi_trims_whitespace_like_duckdb_cast`, `table_and_custom_partition_the_catalogue`, +`substr_window_arithmetic_via_sql`), and CI runs only `pytest`. **Seven** separate gate +records now say so independently. A regression inside `exec::tests` would pass a green-bar +check today. This is an **enforcement fault**, the shape the baseline reading calls a finding +rather than a gap, and no item in this loop owned it — iterations 3 and 4 included, which +added four more records of it and no owner. + +**Closed since the first text, each by a gate leg rather than by an author's claim:** the +`- ` collapse; the trailing-`;` and trailing-comment false refusals; the three +fail-open paths in the tie rule (expression limits, multi-statement strings, unserializable +statements); the clock functions DuckDB's own flag calls `CONSISTENT`; `rowid`; and the tie +rule's residual classes the first text listed — `ORDER BY COLUMNS(...)`, `ORDER BY *`, a +frozen `random()` sort key and the selection-by-position aggregates all refuse now, each +spot-checked on the built branch by a gate. **Still open, each measured, none acted on:** -- The `- ` collapse on `fix-nan-sign-varchar` (the-gate-state section above). +- **`ORDER BY #N` on `refuse-static-tie-order`** — the one open item that answers a query + wrongly rather than refusing or misnaming. Positional **output** references are not among + the node types the tie probe reads, so the probe re-emits `#N` into its own `SELECT` list + where it binds to the N-th **input** column and measures the wrong key. `ORDER BY 2` + refuses; `ORDER BY #2` over the same tied key serves. +- **Seven review findings on `fix-nan-sign-varchar`**, none HIGH: `Lit`'s `PartialEq` + compares opposite-signed NaNs equal, so the print/parse round trip cannot see a dropped + sign; the IR generator emits no negative-NaN constant, so the fuzz round trip never + exercises the sign this branch added; the "classifier does not recognise" refusal selects + on `known || !(left && right)` and still misdiagnoses a two-sided residual whose columns + sit inside the unrecognised node; plus four low (a `snapshot_bits` doc that forbids what a + test in the same crate correctly pins, a `known-limitations.md` cast-message claim that + understates the divergence, a bind-time `fold` that reproduces arith's over-fold, and half + the mod grid's excluded domain — the infinite-dividend half — covered by nothing). +- **The by-name aggregate coarsening**, disclosed rather than fixed: **65 of 88** aggregate + names refuse on the static-only path, including `count_if`, `regr_count`, + `approx_count_distinct`, `entropy` and the compensated accumulators `fsum` / `kahan_sum` / + `favg` that exist to **be** order-stable. `first(v ORDER BY k)` with a unique `k` refuses + whole for the same reason. The upgrade path (read the bound overload off the result type) + is written down; the trade is the owner's to price. +- **A table function that reads the machine** (`SELECT ... FROM duckdb_settings()`) is the + same class as the clock functions the fix round closed and is **not** covered. Named in + `known-limitations.md`, not fixed: covering it means a reading of all 127 table-function + rows, which is a different piece of work. +- **Four `known-limitations.md` line citations in `goal.md` are wrong** — rebased by +73 when + the real shift is +161, so each now points at unrelated text. `goal.md`'s whole verified-by + mechanism is line citations, which makes this a small edit against a load-bearing claim. +- **Two lesser tie-branch findings:** a refusal that names `SELECT TOP` when the query merely + has a static column named `top`, and a `readable` guard that can never be false (one field, + one initializer per arm, one dead `&&`). - `x % y`'s NaN sign is **unmatchable in principle**, not merely unfixed: two gates independently reproduced DuckDB answering **43 identical rows two ways in one query** — the vectorized lanes give one bit pattern, the scalar tail another — stable across 20 - repeats. No engine value can be right there, and the fmod branch's grid correctly excludes - the domain instead of pinning it. -- A **single-side** negated DOUBLE in a JOIN ON residual is now a **provable over-refusal**: - a sign flip is total, but `may_trap`'s catch-all still counts it as trapping. One line - closes it; it was deliberately left out because it accepts SQL master refused, and widening - acceptance unmeasured inside a review-closure branch is the wrong place for it. -- The tie rule's residual classes, all measured by review against the branch build: - `ORDER BY COLUMNS(...)` read as `ORDER BY ALL`; `ORDER BY *` never reaching the star arm; - a nondeterministic sort key (`random()`) frozen — **eight builds gave four different - sequences**; selection-by-position **aggregates** (`first` / `any_value` / `arg_max` / - `string_agg` / `list`) absent from the refusal set and measured to move under five DuckDB - settings; and the inner-`ORDER BY` carve-out unsound when an order-sensitive consumer sits - above it. -- The out-of-range cast trap stops one word short of DuckDB's text (`... destination type` - against `... destination type INT64`). Naming the type means plumbing the SQL destination - width to the trap site; the branch pins ours as DuckDB's message **truncated** and says so - in `known-limitations.md` rather than overclaiming. + repeats. The salvaged grid correctly excludes the domain instead of pinning it, and + iteration 4 confirmed the exclusion rather than narrowing it. +- A **single-side** negated DOUBLE in a JOIN ON residual is still a **provable + over-refusal**: a sign flip is total, but `may_trap`'s catch-all still counts it as + trapping. One line closes it; iteration 4 deliberately left it out again, and split the + refusal's message instead, so acceptance did not widen inside a review-closure branch. +- The out-of-range cast trap's text gap is **wider than the first text said**: a review + measured DuckDB's message as `... destination type INT64 when casting from source column f` + — six extra words, one of them a column name — and the branch's own gate asserts only a + `startswith`, so nothing in it can catch the overstatement. - The unsigned-column refusal class — the reason three mined statements now refuse — is named **nowhere** in `known-limitations.md`. Same bookkeeping shape as gap: undocumented-boolean-comparison, and it belongs with that entry. @@ -309,14 +500,20 @@ the baseline reading calls a finding rather than a gap, and no item in this loop |---|---| | 1 (corpus slip, seed 1804, tie order — three implementers, three gates, two reviews) | ~1.45M | | 2 (seed 1804 rework, tie order rebuild — two implementers, two gates, two reviews) | ~1.33M | -| 3 (seed 1804 closure, modulo sign — two implementers, two gates, two reviews) | see next report | +| 3 (seed 1804 closure, the modulo branch, the tie rule's rebuild and its gate) | **~1.76M** | +| 4 (seed 1804 review closure and salvage, the tie fix round — two implementers, two gates, two reviews) | see next report | -Iteration 3's figure is not recorded on the same basis in this run's journal, so it is left -for the next report rather than restated on a basis that would not compare. What is visible: -iteration 3 ran the same three-role shape at the same fan-out as iteration 1. +Iteration 3's figure is now recorded: **~1.76M**, the largest of the three, which is what a +gate that rebuilds both legs from source and a review that probes a built branch cost when +three items run at once. **Cumulative across iterations 1-3: ~4.5M agent tokens.** Iteration +4's figure is not recorded on the same basis in this run's journal, so it is left for the +next report rather than restated on a basis that would not compare. What is visible of it: +two branches, two independent gates, two reviews and one fix round, on the same three-role +shape. **The standing stop rule is unchanged: roughly 70% of the owner's weekly credit, and it is -owner-signalled** — the loop does not infer it from its own accounting. +owner-signalled** — the loop does not infer it from its own accounting, and has not been +signalled to stop. --- @@ -325,17 +522,23 @@ owner-signalled** — the loop does not infer it from its own accounting. `goal.md` orders controls before drives, so the queue does too. Nothing here is chosen; it is what the loop's own measurements rank. -1. **Finish the parity control in flight.** kpi: engine-parity has `DIVERGE_VALUE` at 0 on - two branches that cannot both land. The decision the owner owns: take - `fix-nan-sign-varchar` (which subsumes the other's production change) with the - `- ` regression closed first, and take `fix-fmod-sign`'s grid as tests only — - or reverse it and lose the `Fneg` work. This is the one item where a merge is blocked on a - choice rather than on more measurement. -2. **finding: static-only-tie-order is still a silent-wrongness class.** The gated branch - fails closed on one named over-refusal and leaves five measured shapes serving a frozen - arbitrary answer. A control violation is never a gap to live with, so this outranks every - `gap:` entry below it — but the branch's tip is ungated and its residuals are a rule - question (which order-dependent constructs must refuse), not a bug list. +1. **The parity control needs a click, not a decision.** kpi: engine-parity has + `DIVERGE_VALUE` at **0** on exactly one branch now — `fix-nan-sign-varchar` at `550f949`, + gate **PASS**, the `- ` regression closed, `fix-fmod-sign`'s grid carried as + tests and that branch dropped. What the first text posed as a choice between two branches + is settled by measurement: the loser had no production change of its own. The only + question left is whether to merge as it stands or hold for the seven review findings, and + none of those answers a query wrongly. +2. **finding: static-only-tie-order is one node type away from closed.** The branch is gated + **PASS** at `a08147e` with three fail-open paths shut, but `ORDER BY #N` still freezes a + tied order and serves it — the same silent-wrongness class the branch exists to close, + now a bug with a named location rather than a rule question. A control violation is never + a gap to live with, so this outranks every `gap:` entry below it. + The rule question that remains is separate and is the owner's: the by-name aggregate + refusal spends **65 of 88** aggregate names and **8 of 2000** campaign seeds to buy the + control, and a static-only fold costs **4-5x** more build time for the same reason. The + policy (never trade a control for a drive gain) says take it; the size is now measured, so + the trade can be priced rather than assumed. 3. **The enforcement faults nobody owns.** The red Rust unit gate above, and finding: c1-depth, untouched by this loop and still routed to ask: kpi-set-change. 4. **gap: bench-baseline-flip's cheapest cause is still untested.** One re-run after From 985145ce585727f94dc47a3e58e7f366e9830c8f Mon Sep 17 00:00:00 2001 From: AmirHossein Roozbahani Date: Sun, 6 Sep 2026 05:46:05 +0200 Subject: [PATCH 3/8] the loop report gains iterations 5-7: one branch shipped, one enumeration that has not ended the parity branch reached its ship gate and is PR #202, open on the owner's click, with its final gate numbers recorded: DIVERGE_VALUE 0, AGREE 1014, one flip at seed 1804, corpus at its floor, api unchanged. its PR head sits one docs commit past the gated tip, which no gate record names, so the report says so. the tie branch gets its rounds 3 and 4. round 3 closed ORDER BY #N, the first-vs-last alias binding and POSITIONAL JOIN, and replaced the by-name sum refusal with DuckDB's own overload resolution, which bought back four campaign seeds. round 4 closed ASOF, collated min/max, the OrderBinder fallback and machine-state table functions, by allow-list in both cases. gate PASS at 2d24744: suite 3415 on release and debug, 134 new node ids and none removed, 89 flips all attributed, and a corpus floor that moved 547 -> 546 on exactly one statement with the reason reproduced - the first amendment's own prediction, landing. and the structural reading the loop surfaced, stated as a fork rather than a decision: four review rounds of enumerating non-pure shapes over DuckDB's surface have not terminated, the findings get narrower in kind each round, and the newest ones carry no name for a name-reading rule to see. that is evidence bearing on the oracle spec's ask: engine-fold-reading and ask: threads-and-value-order, which are the owner's. also: the spend ledger through iteration 6, the disk cost of the worktree-per-role shape, and a next queue reordered by the goal's own ordering - the parity click, a refusal registry for kpi: named-refusal-share, the boolean-comparison bookkeeping, then the fork. Co-Authored-By: Claude Fable 5.1 --- .../docs/reports/2026-09-02-loop-report-1.md | 501 ++++++++++++++---- 1 file changed, 393 insertions(+), 108 deletions(-) diff --git a/packages/confit/docs/reports/2026-09-02-loop-report-1.md b/packages/confit/docs/reports/2026-09-02-loop-report-1.md index 2dafddf9..86e445ae 100644 --- a/packages/confit/docs/reports/2026-09-02-loop-report-1.md +++ b/packages/confit/docs/reports/2026-09-02-loop-report-1.md @@ -1,4 +1,4 @@ -# The loop's first report: iterations 1-4 (2026-09-02, amended 2026-09-06) +# The loop's first report: iterations 1-7 (2026-09-02, amended 2026-09-06) **What this is.** A dated report of one loop's work against `packages/confit/docs/goal.md`, read through the yardsticks the baseline reading measured @@ -18,6 +18,14 @@ is quoted from a gate or review record taken after that text was written; wherev replaces one the first text carried, the old figure is named beside it so the two are not silently conflated. +**What the second amendment changed.** Iterations 5, 6 and 7 (2026-09-05/06) ran the same +three-role shape on the two branches still live. The parity branch reached a ship gate and is +now PR #202. The tie branch went through two further fix-and-review rounds, is gated **PASS** +again, and was found fail-open again — by a fourth review, over a surface nobody has finished +enumerating. That repetition is itself a measurement and has its own section below +(enumeration-not-terminated); it is stated there as a **fork put to the owner**, not as a +decision this loop took. + **Slugs.** `gap:` and `finding:` citations resolve in the baseline reading; `goal:`, `kpi:`, `exclusion:` and `ask:` in `goal.md`; `claim:` and `divergence:` without a local definition in `packages/confit/docs/oracle/`. Sections here carry kebab-case anchors and are @@ -27,7 +35,7 @@ cited by slug, never by number. ## 1. What the loop is {#the-loop} -The owner's mandate, as it stood over these four iterations: **make confit match `goal.md`** +The owner's mandate, as it stood over these seven iterations: **make confit match `goal.md`** — close the distance the baseline reading measured, in the order the goal document's own priorities give. Its working rules: @@ -47,14 +55,15 @@ source rather than trusting a shipped artifact. That structure is the reason thi name a gate for every number: the gate legs are what produced them. **One measurement about the loop itself, worth keeping.** Every master leg the loop ran — -**eleven of them across four iterations** — reproduced the baseline's campaign census -exactly: `AGREE` 1013 / `REFUSED` 944 / `AGREE_TRAP` 21 / `UNSHIPPED` 14 / `DIVERGE_OPT` 7 / -`DIVERGE_VALUE` 1 over seeds 0-1999 at `--workers 8 --timeout 20`. Master advanced twice in -the window (`2ba96e5` -> `2c7c05c` -> `f81e17c`, docs plus one added assertion) and the census -did not move; it has stood at `f81e17c` since, across the four further master legs iterations -3 and 4 ran, each of which also re-checked the sanity seed and found 1804 the -`DIVERGE_VALUE`. claim: campaign-verdicts-today is reproducible on this machine, and a single -seed flipping is therefore signal, not scheduling noise. +**eleven of them across iterations 1-4, and every leg since** — reproduced the baseline's +campaign census exactly: `AGREE` 1013 / `REFUSED` 944 / `AGREE_TRAP` 21 / `UNSHIPPED` 14 / +`DIVERGE_OPT` 7 / `DIVERGE_VALUE` 1 over seeds 0-1999 at `--workers 8 --timeout 20`. Master +advanced twice in the window (`2ba96e5` -> `2c7c05c` -> `f81e17c`, docs plus one added +assertion) and the census did not move; it has stood at `f81e17c` since, across the four +further master legs iterations 3 and 4 ran, each of which also re-checked the sanity seed and +found 1804 the `DIVERGE_VALUE`. Iteration 7's gate reproduced the same six counts at the same +tip four days later, seed 1804 included. claim: campaign-verdicts-today is reproducible on +this machine, and a single seed flipping is therefore signal, not scheduling noise. --- @@ -202,6 +211,28 @@ still misdiagnoses a two-sided residual whose column references sit inside the u node — and four low. Every one is a naming, coverage or comment defect over a production diff the gate found clean; none is a wrong answer. +*Shipped (iteration 5).* All three mediums are the subject of `5819c3a` — `Lit` equality and +the IR round trip can both see a NaN's sign now, the generator emits both signs and `-0.0`, +and the join-residual refusal names the node it actually failed to recognise — and the +**ship gate** read that tip and returned **PASS**. The branch is **PR #202, open, awaiting the +owner's approval click**, and it is the loop's only candidate for kpi: engine-parity. Its +final gate numbers over the same seeds 0-1999 at `--workers 8 --timeout 20`: master `AGREE` +1013 / `REFUSED` 944 / `AGREE_TRAP` 21 / `UNSHIPPED` 14 / `DIVERGE_OPT` 7 / +`DIVERGE_VALUE` 1, against branch `DIVERGE_VALUE` **0** and `AGREE` **1014** — **one flip**, +seed 1804 — with the suite outcome-identical on every shared node id on release **and** debug, +`cargo +test`'s failing set unchanged, the corpus **547 of 678** at its floor, and the public API +surface unchanged. + +*One process note on it, the same shape the tie branch's gate raised below.* PR #202's head is +`81e8fa2`, **one commit past the gated `5819c3a`**: a docs-and-comments commit that +reclassifies the out-of-range cast trap's **text** as a **diagnostics pin rather than a +divergence** — both engines erroring at run time is `AGREE_TRAP` and the two messages are +never compared, so that pin was never recording a divergence — and drops the +`known-limitations.md` row that listed it as a limitation. It touches no production code and +it closes one of the four lows above by reclassifying it, but no gate record in this loop's +journal names that tip. + ### 2.3 finding: static-only-tie-order *Target:* exclusion: whole-relation-shapes — inside the static-tables-only carve-out, what a @@ -341,7 +372,8 @@ unchanged (0.20 -> 0.17 ms) and serving is untouched. That is the whole full-sui (258s -> 365s), and it is identical on the debug build — which is what proves it is DuckDB round trips and not our codegen. -*Status:* **blocked**, on one HIGH a review measured after the gate passed. `ORDER BY #N` — +*Status at the end of iteration 4:* **blocked**, on one HIGH a review measured after the +gate passed. `ORDER BY #N` — DuckDB's positional **output** reference — is not one of the three node types the tie probe reads, so it falls into the hidden-key arm and is re-emitted into the probe's `SELECT` list, where `#N` binds to the **N-th input column** instead. The probe then measures the wrong key: @@ -350,6 +382,139 @@ three rows, while the same query spelled `ORDER BY 2` refuses. That is the silen class this branch exists to close, still open inside it, and it is why the branch is not a merge candidate as it stands. Four lesser findings ride with it (for-the-next-reading below). +**Round 3 (iterations 5-6, `9f16246` then `e85aa09`): the positional key, the last alias, and +a typed sum.** `ORDER BY #N` closed first — a positional **output** reference is read as a +position in the output now, so the probe measures the key the query names rather than the +N-th input column. That round's own ship gate then found a second one no review had: the +branch resolved an `ORDER BY` alias to the **first** match where DuckDB's binder takes the +**last**, so a repeated output name measured the wrong column. And the round-3 review added +two more of the by-then-familiar shape — `POSITIONAL JOIN`, which pairs row *i* with row *i* +and is therefore a scan-order construct, and the question left unasked for every other join +reference type. Its gate returned **PASS** with a battery of roughly **110 probes** clean. + +*The typed-sum rule, and what it bought back.* Iteration 4 refused `sum` **by name**, which +cost eight campaign seeds and was the whole of the branch's acceptance loss. Round 3 replaced +the name with **DuckDB's own overload resolution**: `DESCRIBE` the folded statement, read the +result type, and let an **exact** accumulator — integer, hugeint, decimal — serve while the +floating overload keeps refusing. That is the upgrade path iteration 3 wrote down rather than +guessed at, taken. The effect reconciles exactly against the flips iteration 4 named: the four +exact-`sum` seeds it lost (130 / 545 / 975 / 1484) come back, and the fifth, 1967, does not, +because that seed's generated schema types `c0` as `DOUBLE`. + +**Round 4 (iteration 7, `2d24744`): four more fail-opens, closed by naming what serves.** The +round-3 review found four, each the same shape — a reading that gave up and served. `ASOF +JOIN` draws **one** of the tied inequality matches: measured at **15 distinct answers** for a +single scalar sum, 3000 x 150000 rows with 50 ties each, seven DuckDB settings x three +connections. `min` / `max` under a **collation** pick a representative among values the +collation calls equal. The `OrderBinder` fallback re-emitted an unresolved key into the +probe's own `SELECT` list, where it bound over the **input** columns and measured the wrong +thing. And a **table function that reads the machine** — the item the first amendment listed +as open and not covered. + +*Both fixes are allow-lists, and that is the point.* Join reference types are now decided for +all **six** of DuckDB's `JoinRefType` values: `REGULAR` / `NATURAL` / `CROSS` serve, because +they pair by **values** under any `join_type`; `POSITIONAL` and `ASOF` refuse by name; +`DEPENDENT` is unspellable (`LATERAL` serializes as one of the first three); and anything +DuckDB adds later refuses, because the list enumerates what **serves**. The table-function +rule inverts its polarity the same way — `range` / `generate_series` / `unnest` / `repeat` / +`repeat_row` serve and **every other table function refuses by name**, which covers all **39** +nullary catalogue entries (`duckdb_settings`, `pragma_version`, `duckdb_functions`, +`test_all_types`, ...) and `read_csv` / `read_parquet` / `glob` / `query` / `query_table` for +free. The name is read from `$..function.function_name`, so a scalar `repeat()` or `range()` +sharing a spelling is untouched. + +*Gate (branch `2d24744`, master `f81e17c`):* **PASS**, with one process finding worth keeping +as a loop fact: **the branch tip was one commit ahead of the tip the dispatch named**, and the +gate verified the real tip. It also built the named `e85aa09` to check that commit's own +claim — the two are identical seed-for-seed on the campaign, so the claim holds, but +`e85aa09`'s corpus is 547 and `2d24744`'s is 546, so reviewing the named tip would have missed +four rules and a floor move. Suite **3281 -> 3415** passed on release and on the separately +built debug engine, 1 skipped, 3 xfailed, the same two absent-`pyspark` errors, with **134 new +node ids and not one id removed, renamed or reparametrised** — `comm` over the two collected +lists is empty, so every shared id has the same outcome. `cargo test` 266 / 5, master's exact +five names. The public API diff is empty and the live surface is unchanged +(`confit.__all__ == ['BUILD_PROFILE', 'DuckDBInferFn']`); five build legs, each rebuilt from +source with the installed `.pyd` verified by path, mtime and size. Mutation caught: putting +`ASOF` back in the serve list turns exactly **two** named tests red and the gate's own battery +**60/60 -> 58/60**, restored by re-edit. **92 hand probes**, written against the promise rather +than against the branch's tests — every serving case additionally required to be on backend +`constant` **and** to match DuckDB's own rows as an unordered multiset — all land right, and +the second battery of 32 is weighted deliberately towards **over**-refusal, the expensive +failure mode here. All five of `goal.md`'s code claims reproduce verbatim, printed outputs +included. + +*Campaign delta, and the one number that moved the good way.* `AGREE` 1013 -> **1007**, +`REFUSED` 944 -> **951**, `AGREE_TRAP` 21 -> **20**, `UNSHIPPED` 14, `DIVERGE_OPT` 7, +`DIVERGE_VALUE` 1, `DIVERGE_BUILD` 0 — against iteration 4's `AGREE` **1003** / `REFUSED` +**955**, so the typed-sum rule bought back four seeds of acceptance without spending any part +of the control. **89 flips, every one attributed.** 44 are the planted twins, whose SQL itself +changed (24 tie twins all `REFUSED`, 20 unique twins all `AGREE` — the rule firing in both +directions inside the campaign). 41 are `REFUSED` -> `REFUSED` message-class renames on +identical SQL, every one verified **dynamic** and not one claiming the static-only path: +master's mislabelled row-limit refusal becomes the row path's own wording, which is exactly +the promise that dynamic queries keep the row path's errors. And **4** are `AGREE` -> +`REFUSED`: `avg` at 113 / 1036 / 1314, and `sum` at 1967 over that `DOUBLE`-typed column. +Zero flips the other way outside the planted set; the `DIVERGE_OPT` seed set is identical on +both legs, the single `DIVERGE_VALUE` is 1804 on both, and the `AGREE_TRAP` 21 -> 20 move is +itself a planted-twin seed (1435), so no trap was lost. **Round 4 adds none of the 89**: its +own delta against round 3 is **zero**, seed-by-seed on SQL, kind and refusal class, with zero +occurrences of the four new messages — the generator emits no `ASOF`, no `POSITIONAL`, no +`COLLATE` and no table function, so each new rule is pinned by a unit test instead. + +*One correction the gate makes to the branch's own claim.* "Existing seeds byte-identical" is +**1906 byte-identical + 5 whitespace-fixed**, not 1911. Master's row-limit refusal format +string literally contains a long run of spaces (`on a`, twenty-six spaces, then +`static-tables-only query`); the branch's helper emits clean text. Same kind, same class, +whitespace-normalised details identical on all five seeds — a real fix, but one the branch's +attribution did not name. + +*The corpus floor moved, once, and it was earned.* **547 -> 546**, and the gate reproduced the +justification rather than relaying it. Exactly one statement of 678 changed outcome — index +626, `select round(100::INTEGER, int) from test_all_types();` from DuckDB's own +`test_round_integers.test` — and it is precisely the statement `MATCH_FLOOR`'s own comment +names: `test_all_types()` carries a `TIMESTAMPTZ` column that renders in the **build +machine's** session time zone, and `max(timestamp_tz)::VARCHAR` gives four different values +under `TimeZone` unset / UTC / Asia/Tehran / America/New_York. Zero other statements changed +outcome and zero changed detail, 677 of 678 identical. This is the ratchet working as +designed: a correct new refusal trips the floor on the day it lands, and the floor moves only +with a reproduced reason beside it. + +*Status:* gated **PASS** at `2d24744`, and **not a merge candidate** — for the fourth round +running, because the review of the gated tip found **six** more, four of them HIGH, and all +four are the same fail-open shape the three previous rounds closed elsewhere. `FROM ''`, +DuckDB's implicit file scan, serializes as a `BASE_TABLE` whose `table_name` **is** the file +path, so the table-function rule never sees a function name and a CSV, a parquet file or a +**glob** freezes the build machine's file system into the constant — while `read_csv()` on the +same file refuses with the message that names exactly that class. A **macro** whose body calls +an order-dependent aggregate serves, because the aggregate scan matches +`function_type = 'aggregate'` and a macro is `'macro'`: `json_group_array`, +`json_group_object`, `weighted_avg` and `geomean` leak at 2, 2, 4 and 7 distinct answers +across settings, each wrapping an aggregate that refuses when spelled directly. One-argument +`age(TIMESTAMP)` reads the transaction clock, but DuckDB's catalogue calls it `CONSISTENT`, so +it passes every stability rule — `AgeFunctionStandard` takes +`MetaTransaction::Get(...).start_timestamp` and never calls `SetStability`, read in the pinned +source, unlike `now()` — and two builds a day apart freeze two different constants. And +`SUMMARIZE` serializes as an opaque `SHOW_REF` node that names none of the `avg` / `stddev` / +`approx_quantile` aggregates it actually runs, so no value rule reaches them: seven settings, +**seven** distinct answers. A medium and a low ride with them — **any `TIMESTAMPTZ` rendered +or decomposed** freezes the build machine's session time zone with no function name involved +anywhere, the same effect `known-limitations.md` already names as disqualifying for +`test_all_types()`, now reaching ordinary queries; and `SHOW TABLES` / `DESCRIBE` leak the +harness's own internal registration name (`__arrow_s`) into a user-visible constant. + +*That is a review record, and it earned the weight.* Its method was to rebuild the branch's +five reading functions as a Python replica, validate the replica against **29** known verdicts +from the branch's own test file (29/29 agree), sweep candidate shapes, measure every serving +case across seven DuckDB settings x 2-3 reps on 200k-500k-row statics, and then re-confirm +each finding **end to end against the built branch** with a refusing control on identical +data. The same sweep verified a long list **clean** by that method: `LATERAL` and correlated +subqueries, `unnest` ordering, list / array / struct constructors, `PIVOT` and `UNPIVOT`, +recursive CTEs including the `USING KEY` form, sampling inside CTEs and derived tables, +`EXPLAIN` / `PRAGMA` / `CALL`, the window-only function escape hatch, the full 88-name +aggregate catalogue, deep nesting and 300-call select lists, and values that compare **equal** +but stay **distinguishable** (`0.0` vs `-0.0`, `'a'` vs `'A' COLLATE NOCASE`, 200k of each) — +each measured across all seven settings, each giving a single answer. + --- ## 3. Gate state, branch by branch {#gate-state} @@ -357,17 +522,20 @@ merge candidate as it stands. Four lesser findings ride with it (for-the-next-re | branch | tip | gate verdict | campaign delta vs master (2000 seeds) | corpus | state | |---|---|---|---|---|---| | `fix-corpus-slip` | `a7c5798` | PASS WITH FINDINGS | 0 flips; census identical | 547 / 131 / 0 both legs | **merged** (PR #200) | -| `fix-nan-sign-varchar` | `550f949` | **PASS** | 1 flip: seed 1804 `DIVERGE_VALUE` -> `AGREE`; `DIVERGE_VALUE` 1 -> 0 | 547 / 131 / 0 both legs | **awaiting the owner's approval click** | +| `fix-nan-sign-varchar` | `5819c3a` (PR head `81e8fa2`) | **PASS** (ship gate) | 1 flip: seed 1804 `DIVERGE_VALUE` -> `AGREE`; `DIVERGE_VALUE` 1 -> 0, `AGREE` 1013 -> 1014 | 547 / 131 / 0 both legs | **PR #202, open, awaiting the owner's approval click** | | `fix-fmod-sign` | `ec71979` | PASS, then read as redundant | its one flip was the same seed 1804, already carried by the branch above | 547 / 131 / 0 both legs | **dropped**; grid salvaged | -| `refuse-static-tie-order` | `a08147e` | PASS (two disclosed over-refusals, 4-5x static-only build cost) | 84 flips, all attributed; `AGREE` 1013 -> 1003, `REFUSED` 944 -> 955, `AGREE_TRAP` 21 -> 20 | 547 / 131 / 0 both legs | **blocked** on one HIGH | +| `refuse-static-tie-order` | `2d24744` | **PASS** (two disclosed over-refusals, 4-5x static-only build cost) | 89 flips, all attributed; `AGREE` 1013 -> 1007, `REFUSED` 944 -> 951, `AGREE_TRAP` 21 -> 20 | 546 / 132 / 0; floor moved once, earned | **not a merge candidate**: six review findings, four HIGH | Suite counts, each from the gate that produced it: master **3281** passed / 1 skipped / 3 -xfailed / 2 errors on every leg; `fix-nan-sign-varchar` **3320**; `refuse-static-tie-order` -**3366**; `fix-fmod-sign` stood at **3284** when it was gated, and is moot now. Every branch -was also run on a separately built **debug** engine with identical results and no -`debug_assert` firing. Dialect L2 reads **288/678** with 0 FAIL wherever it was taken. The -public API diff is empty on all four, and `cargo test`'s failing **set** is master's five on -all four. +xfailed / 2 errors on every leg; `fix-nan-sign-varchar` **3320** at `550f949` and +outcome-identical on every shared node id at its ship gate; `refuse-static-tie-order` **3366** +at `a08147e` and **3415** at `2d24744`; `fix-fmod-sign` stood at **3284** when it was gated, +and is moot now. Every branch was also run on a separately built **debug** engine with +identical results and no `debug_assert` firing. Dialect L2 reads **288/678** with 0 FAIL +wherever it was taken. The public API diff is empty on all four, and `cargo test`'s failing +**set** is master's five on all four. The corpus reads 547 / 131 / 0 on every leg of every +branch except `refuse-static-tie-order` at `2d24744`, which is **546 / 132 / 0** — one +statement, moved for a reproduced reason. **The contention resolved by measurement, not by a choice.** `fix-fmod-sign` and `fix-nan-sign-varchar` both changed the same `DuckF64` NaN arm and both flipped the same @@ -377,32 +545,93 @@ branch, so there was nothing to choose between. Its 29-row sign grid over `%` / `fmod` now lives on `fix-nan-sign-varchar` as tests, and the branch is dropped. One candidate remains for kpi: engine-parity, and it is a click away rather than a decision away. -**Both live branches carry review findings their gates did not raise, and the two are not the -same weight.** On `fix-nan-sign-varchar` the seven are naming, coverage and comment defects -over a production diff the gate found clean — an equality that cannot see a NaN sign, a -generator that never emits one, a refusal selector that misdiagnoses a shape it already -refuses correctly. None of them answers a query wrongly. On `refuse-static-tie-order` one of -the five **does**: `ORDER BY #N` freezes a tied order and serves it. A control violation +**Both live branches carry review findings their gates did not raise, and the two are still +not the same weight.** On `fix-nan-sign-varchar` the seven were naming, coverage and comment +defects over a production diff the gate found clean — an equality that could not see a NaN +sign, a generator that never emitted one, a refusal selector that misdiagnosed a shape it +already refused correctly. None answered a query wrongly; the three mediums closed in +iteration 5 and one low closed by reclassification, and what is left of the branch is a click. +On `refuse-static-tie-order`, **four of the six do** answer a query wrongly: a file scan, a +macro over an order-dependent aggregate, a clock read the catalogue calls `CONSISTENT`, and +`SUMMARIZE` each freeze something that is not a function of the query. A control violation inside the branch chartered to close that control is a different class of open item from a -stale comment, and the table above states the two differently on purpose. +stale comment, and the table above states the two differently on purpose. What is new at +iteration 7 is that the **repetition**, rather than any one of the four, is the finding. + +--- + +## 4. The enumeration has not terminated {#enumeration-not-terminated} + +This is a measurement about the tie branch's **method**, not a finding against it, and it is +the reason a design question already put to the owner now has evidence under it. + +**What four review rounds did.** Each round closed every fail-open the last one found, and +each next round found more — from the same surface, by the same method: an independent reader +probing DuckDB for shapes whose answer is not a function of the query text and the statics. + +``` +ties -> ORDER BY #N -> the LAST alias -> POSITIONAL JOIN -> ASOF JOIN -> +collated min/max -> the OrderBinder fallback -> machine-state table functions -> +the implicit file scan -> a macro over an order-dependent aggregate -> +one-argument age() -> SUMMARIZE -> any rendered TIMESTAMPTZ +``` + +Every entry is closed or open on its own merits, and every fix is right. What the **sequence** +measures is the shape of the work: four rounds, no round empty, no round's findings predicted +by the one before it, and the newest four reaching a file system, a macro body, a transaction +clock and a statement whose serialization names nothing a rule can read. The rule is chasing a +surface — DuckDB's whole function, join, table-function and session-setting catalogue — that +neither the branch nor four rounds of independent review have been able to enumerate, and +nothing this loop measured says the next round is empty. + +**Why that reads as structural rather than as a run of bad luck.** Two things. The findings get +**narrower in kind** each round — a clause a parser can see, then a binder rule, then a +catalogue flag, then a serialization node — so the question has moved from "did we cover the +shapes" to "can this reading see the shape at all". And the last two rounds both found cases +where **no name appears anywhere in the parse**: `SUMMARIZE`'s `SHOW_REF`, `FROM ''`'s +`BASE_TABLE`, and a `TIMESTAMPTZ` rendered by a plain cast. A rule that decides by reading +names cannot be completed against shapes that carry none. + +**The fork, stated as a fork.** The alternative already on the table is to stop deciding +*which shapes are pure* and instead **pin the build-time fold's configuration through the +oracle**, so that the answer is deterministic by construction: one reading, one settings set, +one thread count, fixed at the door the constant is folded behind — and a query whose answer +moves under that fixed configuration becomes a bug rather than a shape to enumerate. That is +the oracle spec's ask: engine-fold-reading (does the engine's build-time fold move to the +oracle's reading, given it folds optimizer-ON while the oracle is optimizer-OFF) and +ask: threads-and-value-order (does `threads` join the oracle constant, and what disposition +covers order *inside* a value). Both are **stated, not ruled**, and both are the owner's. + +What this loop is claiming, and what it is not. It is **not** claiming the fork is decided, +that enumeration is the wrong approach, or that the tie branch should be abandoned — the +branch closes a real silent-wrongness class, every rule in it is measured, and its gate is +PASS. It **is** recording that four consecutive rounds of enumeration have not terminated, +that what they find trends away from what a name-reading rule can see, and that this is the +first evidence the loop has produced bearing on those two asks. The fork's own claim is that +pinning the configuration changes what the rule must enumerate from "every impure shape DuckDB +offers" to "every shape that moves under a configuration we control"; it would not make this +branch redundant, because a frozen tie order still is not a function of the query. Pricing +that claim is the decision, and it is not this report's. --- -## 4. Measured facts for the next full reading {#for-the-next-reading} +## 5. Measured facts for the next full reading {#for-the-next-reading} Facts this loop produced that belong in reading **N=2**, not in this report's conclusions. **Acceptance changed, and not only by fixing things.** `refuse-static-tie-order` moves -`AGREE` 1013 -> **1003** and `REFUSED` 944 -> **955** on the same seed range — but the -comparison is **not like-for-like**, for two reasons that pull differently. 44 of the 2000 +`AGREE` 1013 -> **1007** and `REFUSED` 944 -> **951** on the same seed range (the first +amendment read 1003 / 955 at `a08147e`; round 3's typed-sum rule recovered four seeds) — but +the comparison is **not like-for-like**, for two reasons that pull differently. 44 of the 2000 seeds are now planted twins rather than grammar draws, so roughly **2%** of every campaign is -two fixed queries; one displaced seed carried an `AGREE_TRAP` that nothing else covers (the -whole of the 21 -> 20 move), and seed numbers cited in older repros silently change meaning. -Separately, **8** of the flips are a deliberate over-refusal — `sum` and `avg` on the -static-only path — so part of the acceptance loss is a rule the loop chose, not a population -artefact and not a defect. Any next census over this generator is measuring a slightly -different population under a slightly stricter rule; the baseline's validity caveat under -acceptance-reading now has two reasons to bite rather than one. +two fixed queries; one displaced seed (1435) carried an `AGREE_TRAP` that nothing else covers, +which is the whole of the 21 -> 20 move, and seed numbers cited in older repros silently +change meaning. Separately, **4** of the flips are a deliberate over-refusal — `avg` at +113 / 1036 / 1314 and `sum` over a `DOUBLE` at 1967 — so part of the acceptance loss is still +a rule the loop chose, not a population artefact and not a defect. Any next census over this +generator is measuring a slightly different population under a slightly stricter rule; the +baseline's validity caveat under acceptance-reading now has two reasons to bite rather than +one. **The over-refusal detector is class-agnostic; the generator is not, and that is now an argued position rather than an oversight.** A test pins the detector as reading every refusal @@ -412,23 +641,30 @@ refusal surface therefore has no campaign-level regression detector at all. The **rejected** widening it: the tag asserts "every value here is fixed by the query and the statics", and a generator cannot assert that of a case it just built without reimplementing the rule under test, which would manufacture false findings. So the silence stands, with a -reason. Every new message still carries the documented `unsupported:` prefix, so gap: -undocumented-refusal-prefixes does not grow — but the naming half of kpi: no-third-mode gains -its untested claims regardless. - -**The corpus count did not move again.** 547 / 131 / 0 on every leg of every branch this loop -gated, iterations 3 and 4 included — and the tie branch is the interesting case, because it -adds a whole new refusal surface and still meets the floor **exactly**, neither tripping it -nor growing past it. `MATCH_FLOOR` holds at the current count with **zero headroom**, so the -next correct new refusal trips it on the day it lands, by design. Six gates have now named -that explicitly. +reason — and it is wider now than when that reason was written, because rounds 3 and 4 added +refusals by name for three join reference types, every table function outside a five-name +allow-list, collations and macros, none of which the generator emits and none of which the +campaign can therefore grade in either direction. Every new message still carries the +documented `unsupported:` prefix, so gap: undocumented-refusal-prefixes does not grow — but +the naming half of kpi: no-third-mode gains its untested claims regardless. + +**The corpus count moved, and it was the first amendment's own prediction that moved it.** +That text said `MATCH_FLOOR` held at 547 with **zero headroom**, so the next correct new +refusal would trip it on the day it landed. Round 4 landed one and it did: **547 -> 546**, +exactly one statement of 678, `select round(100::INTEGER, int) from test_all_types();` — +`test_all_types()` carries a `TIMESTAMPTZ` column that renders in the **build machine's** +session time zone, four time zones giving four answers, reproduced by the gate rather than +relayed. Every other leg of every branch this loop gated still reads 547 / 131 / 0. The +mechanism is now demonstrated rather than argued: the ladder cannot shrink in silence, and a +correct new refusal costs one documented line each time it lands. **The Rust unit gate is red on master and CI cannot see it.** `cargo test` is 266 passed / 5 failed on every master leg the loop ran, the same five names each time (`pin_ftoi_rounding_and_traps`, `pin_ssubstr_window_arithmetic`, `pin_stoi_trims_whitespace_like_duckdb_cast`, `table_and_custom_partition_the_catalogue`, `substr_window_arithmetic_via_sql`), and CI runs only `pytest`. **Seven** separate gate -records now say so independently. A regression inside `exec::tests` would pass a green-bar +records said so by iteration 4, and every gate leg since has added another — iteration 7's +reads the same five names on both legs. A regression inside `exec::tests` would pass a green-bar check today. This is an **enforcement fault**, the shape the baseline reading calls a finding rather than a gap, and no item in this loop owned it — iterations 3 and 4 included, which added four more records of it and no owner. @@ -441,38 +677,58 @@ rule's residual classes the first text listed — `ORDER BY COLUMNS(...)`, `ORDE frozen `random()` sort key and the selection-by-position aggregates all refuse now, each spot-checked on the built branch by a gate. +**Closed in iterations 5-7, again each by a gate leg rather than an author's claim:** +`ORDER BY #N`; the first-versus-last alias binding in `ORDER BY`; `POSITIONAL JOIN` and +`ASOF JOIN`, with all six `JoinRefType` values decided; `min` / `max` under a collation; the +`OrderBinder` fallback that measured an input column; **machine-state table functions** — the +item the first amendment listed as open and not covered, closed by inverting the rule's +polarity; the by-name `sum` coarsening, for every exact accumulator; the `SELECT TOP` refusal +that fired on a static column merely named `top`; and, on the parity branch, the sign-blind +`Lit` equality, the generator that emitted no negative NaN, and the misdiagnosing residual +selector. One item is **reclassified** rather than fixed: the out-of-range cast trap's text +sits outside the comparison contract — both engines erroring at run time is `AGREE_TRAP` and +the messages are never compared — so that pin was never recording a divergence, and the +`known-limitations.md` row went with it. + **Still open, each measured, none acted on:** -- **`ORDER BY #N` on `refuse-static-tie-order`** — the one open item that answers a query - wrongly rather than refusing or misnaming. Positional **output** references are not among - the node types the tie probe reads, so the probe re-emits `#N` into its own `SELECT` list - where it binds to the N-th **input** column and measures the wrong key. `ORDER BY 2` - refuses; `ORDER BY #2` over the same tied key serves. -- **Seven review findings on `fix-nan-sign-varchar`**, none HIGH: `Lit`'s `PartialEq` - compares opposite-signed NaNs equal, so the print/parse round trip cannot see a dropped - sign; the IR generator emits no negative-NaN constant, so the fuzz round trip never - exercises the sign this branch added; the "classifier does not recognise" refusal selects - on `known || !(left && right)` and still misdiagnoses a two-sided residual whose columns - sit inside the unrecognised node; plus four low (a `snapshot_bits` doc that forbids what a - test in the same crate correctly pins, a `known-limitations.md` cast-message claim that - understates the divergence, a bind-time `fold` that reproduces arith's over-fold, and half - the mod grid's excluded domain — the infinite-dividend half — covered by nothing). -- **The by-name aggregate coarsening**, disclosed rather than fixed: **65 of 88** aggregate - names refuse on the static-only path, including `count_if`, `regr_count`, - `approx_count_distinct`, `entropy` and the compensated accumulators `fsum` / `kahan_sum` / - `favg` that exist to **be** order-stable. `first(v ORDER BY k)` with a unique `k` refuses - whole for the same reason. The upgrade path (read the bound overload off the result type) - is written down; the trade is the owner's to price. -- **A table function that reads the machine** (`SELECT ... FROM duckdb_settings()`) is the - same class as the clock functions the fix round closed and is **not** covered. Named in - `known-limitations.md`, not fixed: covering it means a reading of all 127 table-function - rows, which is a different piece of work. +- **Four fail-open shapes on `refuse-static-tie-order` at `2d24744`**, each measured by a + review of the gated tip, each answering a query wrongly rather than refusing: DuckDB's + implicit file scan `FROM ''`, a `BASE_TABLE` whose name **is** the path, so CSV, + parquet and **globs** freeze the build machine's file system while `read_csv()` on the same + file refuses; a **macro** whose body calls an order-dependent aggregate + (`json_group_array`, `json_group_object`, `weighted_avg`, `geomean` — 2, 2, 4 and 7 distinct + answers across settings); one-argument **`age()`**, which reads the transaction clock while + DuckDB's catalogue calls it `CONSISTENT`; and **`SUMMARIZE`**, whose `SHOW_REF` node names + none of the aggregates it runs — seven settings, seven answers. +- **Two lesser ones with them:** any **`TIMESTAMPTZ` rendered or decomposed** freezes the + build machine's session time zone with no function name involved anywhere — the effect + `known-limitations.md` already names as disqualifying for `test_all_types()`, now reaching + ordinary queries — and `SHOW TABLES` / `DESCRIBE` leak the harness's own `__arrow_s` + registration name into a user-visible constant. +- **Three low review findings left on `fix-nan-sign-varchar`**, none answering a query + wrongly: a `snapshot_bits` doc that forbids what a test in the same crate correctly pins, a + bind-time `fold` that reproduces arith's over-fold, and the infinite-dividend half of the + mod grid's excluded domain, covered by nothing. The three mediums closed in `5819c3a`; the + fourth low closed by reclassification in `81e8fa2`. +- **The by-name aggregate coarsening, narrowed but not closed.** Round 3 gave `sum` its typed + rule through DuckDB's own overload resolution, so every **exact** accumulator serves. The + by-name list was **65 of 88**, and `sum`'s exact overloads are all that came off it: + `count_if`, `regr_count`, `approx_count_distinct`, `entropy` and the compensated + accumulators `fsum` / `kahan_sum` / `favg` that exist to **be** order-stable all still + refuse by name, and `first(v ORDER BY k)` with a unique `k` still refuses whole. The same + overload reading is the upgrade path for the rest; the trade is the owner's to price. +- **The table-function allow-list is deliberately conservative:** five names serve and + everything else refuses, so a genuinely pure table function DuckDB adds later refuses until + someone lists it. That is the fail-closed direction and it is disclosed — but it is an + over-refusal the campaign cannot see, by the same blindness above. - **Four `known-limitations.md` line citations in `goal.md` are wrong** — rebased by +73 when the real shift is +161, so each now points at unrelated text. `goal.md`'s whole verified-by - mechanism is line citations, which makes this a small edit against a load-bearing claim. -- **Two lesser tie-branch findings:** a refusal that names `SELECT TOP` when the query merely - has a static column named `top`, and a `readable` guard that can never be false (one field, - one initializer per arm, one dead `&&`). + mechanism is line citations, which makes this a small edit against a load-bearing claim. A + gate read one presentation defect beside them: the third code block under `goal.md`'s + `REFUSES:` heading is a **serving** counter-example, correct output and all. +- **One lesser tie-branch finding left:** a `readable` guard that can never be false (one + field, one initializer per arm, one dead `&&`). - `x % y`'s NaN sign is **unmatchable in principle**, not merely unfixed: two gates independently reproduced DuckDB answering **43 identical rows two ways in one query** — the vectorized lanes give one bit pattern, the scalar tail another — stable across 20 @@ -482,10 +738,6 @@ spot-checked on the built branch by a gate. over-refusal**: a sign flip is total, but `may_trap`'s catch-all still counts it as trapping. One line closes it; iteration 4 deliberately left it out again, and split the refusal's message instead, so acceptance did not widen inside a review-closure branch. -- The out-of-range cast trap's text gap is **wider than the first text said**: a review - measured DuckDB's message as `... destination type INT64 when casting from source column f` - — six extra words, one of them a column name — and the branch's own gate asserts only a - `startswith`, so nothing in it can catch the overstatement. - The unsigned-column refusal class — the reason three mined statements now refuse — is named **nowhere** in `known-limitations.md`. Same bookkeeping shape as gap: undocumented-boolean-comparison, and it belongs with that entry. @@ -494,60 +746,93 @@ spot-checked on the built branch by a gate. --- -## 5. Spend {#spend} +## 6. Spend {#spend} | iteration | agent tokens | |---|---| | 1 (corpus slip, seed 1804, tie order — three implementers, three gates, two reviews) | ~1.45M | | 2 (seed 1804 rework, tie order rebuild — two implementers, two gates, two reviews) | ~1.33M | | 3 (seed 1804 closure, the modulo branch, the tie rule's rebuild and its gate) | **~1.76M** | -| 4 (seed 1804 review closure and salvage, the tie fix round — two implementers, two gates, two reviews) | see next report | +| 4 (seed 1804 review closure and salvage, the tie fix round — two implementers, two gates, two reviews) | not recorded on a comparable basis | +| 5 (the parity branch's ship gate and PR, the tie branch's positional-key round) | ~1.0M | +| 6 (the tie branch's typed sum, last alias and positional join — one implementer, one gate, one review) | ~0.75M | +| 7 (the tie branch's round 4, its gate, its review, and this amendment) | this run, not closed | Iteration 3's figure is now recorded: **~1.76M**, the largest of the three, which is what a gate that rebuilds both legs from source and a review that probes a built branch cost when three items run at once. **Cumulative across iterations 1-3: ~4.5M agent tokens.** Iteration -4's figure is not recorded on the same basis in this run's journal, so it is left for the -next report rather than restated on a basis that would not compare. What is visible of it: -two branches, two independent gates, two reviews and one fix round, on the same three-role -shape. +4's figure is not recorded on the same basis in this run's journal, so it is not restated on +a basis that would not compare. What is visible of it: two branches, two independent gates, +two reviews and one fix round, on the same three-role shape. + +Iterations 5 and 6 are recorded: **~1.0M** and **~0.75M**, both smaller than any of the first +four, because each ran a single item through the three roles rather than three items at once. +Iteration 7 is this run and is not closed as this is written. **Cumulative across the loop: +~8.8M+ agent tokens** — the `+` is iterations 4 and 7, the two figures without a comparable +basis. + +**One operational cost, recorded because it is not free.** The loop's worktree-per-role shape +put `C:` at 100% on 2026-09-06, and **35 finished workflow worktrees** were removed to clear +it. Every gate leg keeps a full release build, and a debug build for as long as it runs — +which is the same independence every number in this report depends on, so this is the shape's +own cost rather than an accident. It wants a sweep between iterations rather than after a full +disk. **The standing stop rule is unchanged: roughly 70% of the owner's weekly credit, and it is owner-signalled** — the loop does not infer it from its own accounting, and has not been -signalled to stop. +signalled to stop, through iteration 7 included. --- -## 6. Next, in the goal's order {#next} +## 7. Next, in the goal's order {#next} `goal.md` orders controls before drives, so the queue does too. Nothing here is chosen; it is what the loop's own measurements rank. 1. **The parity control needs a click, not a decision.** kpi: engine-parity has - `DIVERGE_VALUE` at **0** on exactly one branch now — `fix-nan-sign-varchar` at `550f949`, - gate **PASS**, the `- ` regression closed, `fix-fmod-sign`'s grid carried as - tests and that branch dropped. What the first text posed as a choice between two branches - is settled by measurement: the loser had no production change of its own. The only - question left is whether to merge as it stands or hold for the seven review findings, and - none of those answers a query wrongly. -2. **finding: static-only-tie-order is one node type away from closed.** The branch is gated - **PASS** at `a08147e` with three fail-open paths shut, but `ORDER BY #N` still freezes a - tied order and serves it — the same silent-wrongness class the branch exists to close, - now a bug with a named location rather than a rule question. A control violation is never - a gap to live with, so this outranks every `gap:` entry below it. - The rule question that remains is separate and is the owner's: the by-name aggregate - refusal spends **65 of 88** aggregate names and **8 of 2000** campaign seeds to buy the - control, and a static-only fold costs **4-5x** more build time for the same reason. The - policy (never trade a control for a drive gain) says take it; the size is now measured, so - the trade can be priced rather than assumed. -3. **The enforcement faults nobody owns.** The red Rust unit gate above, and finding: + `DIVERGE_VALUE` at **0** on exactly one branch — `fix-nan-sign-varchar`, ship gate **PASS** + at `5819c3a`, now **PR #202**, open. What the first text posed as a choice between two + branches was settled by measurement (the loser had no production change of its own), and + what the first amendment left as a hold is settled too: every review finding that could + answer a query wrongly is closed, and three lows remain. The one thing to look at before + clicking is that the PR head is `81e8fa2`, one docs-and-comments commit past the gated tip, + which no gate record in this journal names. +2. **kpi: named-refusal-share, and the refusal registry it would need.** This loop roughly + doubled the engine's refusal vocabulary and nothing lists it. `refuse-static-tie-order` + alone refuses by name across the aggregate catalogue, three of six join reference types, + every table function outside a five-name allow-list, collations, macros and the clock + family — while the campaign stays **structurally blind to over-refusal**, + `known-limitations.md` is already drifting (four wrong line citations in `goal.md`, four + stale `550` sites, one refusal class named nowhere), and the corpus floor now moves + whenever a refusal lands. One registry — every refusal class with its message, its reason + and the test that pins it — is what makes that vocabulary auditable, and it is exactly what + kpi: named-refusal-share would measure. Adopting the KPI routes through + ask: kpi-set-change; building the registry does not, and the naming half of + kpi: no-third-mode is the loop's largest untested claim without it. +3. **gap: undocumented-boolean-comparison, and the unsigned class that belongs with it.** Same + bookkeeping shape, both small, both against load-bearing claims: boolean comparison is + undocumented, and the **unsigned-column** refusal — the reason three mined statements + stopped matching, and the loop's clearest worked example of a refusal that is a correctness + *gain* — is named nowhere in `known-limitations.md`. The cheapest items on this list, and + the first two rows any registry would want. +4. **The fork: the oracle spec's ask: engine-fold-reading and ask: threads-and-value-order.** + Four rounds of enumeration have not terminated (enumeration-not-terminated above), and the + last two found shapes that carry **no name for a rule to read**. That is measured evidence + bearing on two questions that are stated and not ruled, and the tie branch's disposition + hangs on the answer: enumerate a fifth round, or pin the build-time fold's configuration + through the oracle so the answer is deterministic by construction. This report takes no + position. It states the fork and prices what the enumerating side has cost so far — three + iterations, four review rounds, **4-5x** static-only build time, and a branch that is gated + PASS and still not mergeable. +5. **The enforcement faults nobody owns.** The red Rust unit gate above, and finding: c1-depth, untouched by this loop and still routed to ask: kpi-set-change. -4. **gap: bench-baseline-flip's cheapest cause is still untested.** One re-run after +6. **gap: bench-baseline-flip's cheapest cause is still untested.** One re-run after `--reinstall-package` rules out the stale-wheel signature (d). No iteration in this loop touched it, and kpi: bench-refresh-cadence should not be adopted before it is settled. -5. **Then the gap ledger, in whatever order ask: next-query-classes gets answered.** That +7. **Then the gap ledger, in whatever order ask: next-query-classes gets answered.** That question is the owner's and remains open; the loop has added no evidence that reorders its - candidates, only evidence that gap: corpus-match-slip's ratchet half is now real and its - bookkeeping half is not. + candidates, only evidence that gap: corpus-match-slip's ratchet half is real, that its + bookkeeping half is not, and that the ratchet now has one earned move on the record. **Reading N=2 replaces none of this.** This report is what moved between readings; the next full reading is what the numbers are. From 183d9ed7dbde2bdce1130c03d799e223ea0d933d Mon Sep 17 00:00:00 2001 From: AmirHossein Roozbahani Date: Sun, 6 Sep 2026 06:21:40 +0200 Subject: [PATCH 4/8] a status reading of the loop after eight iterations What shipped, what is open on the static-only carve-out (seven fail-opens and their planned closures), what the loop learned about its own method, the spend, and the decisions that are the owner's. The narrative stays in the iterations 1-7 report; this is the snapshot. Co-Authored-By: Claude Fable 5.1 --- .../docs/reports/2026-09-06-loop-status.md | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 packages/confit/docs/reports/2026-09-06-loop-status.md diff --git a/packages/confit/docs/reports/2026-09-06-loop-status.md b/packages/confit/docs/reports/2026-09-06-loop-status.md new file mode 100644 index 00000000..dcef121d --- /dev/null +++ b/packages/confit/docs/reports/2026-09-06-loop-status.md @@ -0,0 +1,148 @@ +# Loop status, 2026-09-06: where the goal loop stands after eight iterations + +**What this is.** A dated status reading of the standing loop (`make confit behavior match +goal.md`), written on the owner's ask for "the current situation of the loop". It states +what has shipped, what is open, what the loop has learned about its own method, what it has +cost, and which decisions are the owner's. The narrative of iterations 1-7 lives in +`2026-09-02-loop-report-1.md` (amended today to 838 lines); this file is the snapshot, not +the story. Slugs resolve as in that report: `goal:` / `kpi:` / `exclusion:` / `ask:` in +`goal.md`, `finding:` and `gap:` in the baseline reading, `claim:` in the oracle spec. + +--- + +## 1. Shipped, and one click away {#shipped} + +| item | state | what it closed | +|---|---|---| +| PR #200 `fix-corpus-slip` | **merged** (master `f81e17c`) | the corpus floor (`MATCH_FLOOR`), and the finding that the 550 -> 547 slip was a correctness gain (three unsigned join columns had matched only because the replay ignored output type) | +| PR #202 `fix-nan-sign-varchar` | **approved by the owner**; merge blocked on one CI failure, fix in flight | finding: seed-1804 (`-nan` rendered `nan`); unary minus on DOUBLE is a real negation (`fneg`); the IR carries a NaN's sign; `nextafter` hands a NaN operand back as DuckDB does | + +**The CI failure on PR #202, and what it taught.** The last commit pinned which operand +`nextafter` returns when *both* are NaN, measured on Windows. Linux CI measured the other +operand. That choice is the platform compiler's, so the fix routes the kernel through the C +runtime's own `nextafter` (the function DuckDB's wheel calls), which matches by construction +on every platform, and leaves the both-NaN column compared against the machine's own oracle +and never pinned. The lesson joins the branch's doctrine: a sign a libm or a compiler chose +is compared, never pinned, and a local Windows gate is not a Linux gate. + +**kpi: engine-parity after the merge.** Seeds 0-1999: `DIVERGE_VALUE` 0 (master: 1), `AGREE` +1014 / `REFUSED` 944 / `AGREE_TRAP` 21 / `UNSHIPPED` 14 / `DIVERGE_OPT` 7. The seven +`DIVERGE_OPT` seeds are the optimizer-bracket set the goal excludes (exclusion: +optimizer-on-answers). The control reads clean on the accepted surface, with the campaign's +blind spots named rather than assumed: it never feeds a NaN into `nextafter`, its comparison +contract spells every NaN `nan` (claim: repr-equality), and it cannot see over-refusal outside +the two planted twins. + +--- + +## 2. Open: the static-only carve-out {#static-only} + +**Target.** exclusion: whole-relation-shapes: a static-tables-only query is folded once at +build and frozen, and what a whole-relation construct selects is frozen only when it is a +function of the query text and the statics. + +**Branch `refuse-static-tie-order`, tip `ebfbdb2`.** Four fix-and-review rounds have closed, +each gated PASS, in order: tie-producing `ORDER BY` (measured by DuckDB over the frozen +result); every selection by position (`LIMIT`/`OFFSET`/`FETCH`/`SAMPLE`/`DISTINCT ON`/ +`QUALIFY`/row-position window functions); non-deterministic functions by DuckDB's own +stability flag, plus macros read through their definitions and four run-state names the +flag misses; order-dependent aggregates by DuckDB's own `SetOrderDependent`, with `sum` +read per overload through DuckDB's binder; row-counted window frames; `ORDER BY #N`; the +last-alias and OrderBinder-fallback name bindings; `POSITIONAL` and `ASOF` joins (all six +`JoinRefType` values decided, three serve); collated `min`/`max`; table functions by a +five-name allow-list; `rowid`; unreadable and multi-statement strings refused whole. + +**Why it is not a merge candidate.** The review of the gated tip found six more fail-opens, +and the orchestrator's own read of the whole diff found a seventh: + +| # | shape | severity | how it escapes the reading | closure planned | +|---|---|---|---|---| +| 1 | `FROM 'file.csv'` (implicit file scan; also parquet, relative paths, globs) | high | a `BASE_TABLE` whose name is the path; no function name for the table-function rule to see | every `BASE_TABLE` must name a static or a CTE (FROM allow-list) | +| 2 | macros over order-dependent aggregates (`json_group_array`, `json_group_object`, `weighted_avg`, `geomean`) | high | the macro scan reads bodies for stability only | classify a macro body exactly as a call: parse the definition, reuse the same name sets | +| 3 | one-argument `age()` | high | reads the transaction clock under a `CONSISTENT` flag | by arity, through `json_tree` (present in 1.5.5) | +| 4 | `SUMMARIZE` (and `DESCRIBE`/`SHOW`) | high | an opaque `SHOW_REF` node names none of the aggregates it runs | any `SHOW_REF` refuses by name | +| 5 | any `TIMESTAMPTZ` rendered or decomposed | medium | no name anywhere; the build machine's `TimeZone` is read | refuse zoned types by DuckDB's own metadata: static column types, cast and literal types in the parse, and maker functions from the catalogue | +| 6 | `SHOW TABLES` leaks the harness's `__arrow_s` | low | same `SHOW_REF` node | closed with 4 | +| 7 | `SELECT *, a AS k, unnest(st) FROM s ORDER BY k` serves a tied `k` | high | a top-level `unnest(struct)` expands to columns but is `FUNCTION`, not `STAR`, so an alias after a star is placed by counting from the wrong end | `SelectList.expands` (TDD, in progress, measured red) | + +Number 7 is the first fail-open found by reading the branch rather than by probing it, and +it is the one the campaign, four gates and four reviews all missed. + +**The enumeration has not terminated.** That sentence is the report's own section 4, and +nothing in iteration 8 changes it: each round closes everything the last found and the next +round finds more, and the newest shapes carry no name for a rule to read. The structural +alternative, pinning the build-time fold's configuration through the oracle so the answer is +deterministic by construction, is stated in the report as a fork against the oracle spec's +ask: engine-fold-reading and ask: threads-and-value-order. It is the owner's call, and the +loop proceeds under the goal as written until it is made. What iteration 8 does change is the +*shape* of the closures: three of the five are allow-lists or metadata reads (what serves, +DuckDB's own catalogue and parse), not more names. + +**The price already paid, disclosed.** 64 of DuckDB's 88 aggregate names refuse on the +static-only path by DuckDB's flag (including `bit_and`, `histogram`, `count_if` and the +compensated sums that exist to be order-stable); a collation anywhere takes `min`/`max` off +the served list; every table function outside five names refuses; the fold costs 4-5x more +build time; the corpus floor moved 547 -> 546 for one statement whose own answer is fixed (the +allow-list's price, not a skew). The campaign cannot see over-refusal in this class, so these +are pinned by unit tests instead. + +--- + +## 3. What the loop learned about itself {#method} + +- **The orchestrator reads every diff.** The owner's correction of 2026-09-06 ("you must + review the model outputs"). Applied since to both live branches: it produced findings 7 + above and the `nextafter` parity bug, four doctrine slips in `goal.md` (a today-state + sentence and a mechanism paragraph in the target document, a serving example under a + `REFUSES` label, a corpus comment arguing a skew for a statement that has none), and one + over-claiming doc comment. Gates are evidence; they are not the review. +- **Findings are fixed before a PR is presented.** PR #202 went back to draft for the design + pass and the review fixes; the tie branch stays in draft until all seven close and the diff + is re-read. +- **A pin measured on one platform is not a pin.** The both-NaN `nextafter` case above. +- **Reviewers apply the design lens by name.** The `fix-nan-sign-varchar` design pass gave + each duplicated rule one home (`fold_operand`, `out_of_range_trap`, the NaN-sign argument at + `Lit`, one `inf`/`nan` token path); the structural item still open is that node classifiers + are hand-enumerated across five files (a node should own its own properties). +- **Third-party defect descriptions stay out of the tree** until the owner has seen them; the + branch states measured facts and our consequence only. + +--- + +## 4. Spend and operations {#spend} + +| span | agent tokens (approx.) | +|---|---| +| iterations 1-7 (workflows) | ~9.0M+ | +| this session's own review, fixes and gates (orchestrator-driven, not a workflow) | ~0.4M in subagents, plus the orchestrator's own context | + +The stop rule stands at roughly 70% of the owner's weekly credit, owner-signalled; the loop +cannot read that meter and has not been given a percentage. `C:` reached 100% once during +iteration 6; 35 finished workflow worktrees were removed and a sweep between iterations is +now part of the routine. + +--- + +## 5. Decisions that are the owner's {#decisions} + +1. **The fork**: enumerate a sixth round, or pin the build-time fold's configuration through + the oracle (ask: engine-fold-reading, ask: threads-and-value-order). Evidence: four + rounds, seven still open, the last three shapes nameless. +2. **The four goal asks** still open on master: acceptance-target, next-query-classes, + exclusion-ratification, kpi-set-change. +3. **The static-only acceptance price**: 64/88 aggregate names, collations, table functions, + 4-5x build time. The policy (never trade a control for a drive) says take it; the size is + measured, so it can be priced rather than assumed. +4. **The weekly percentage**, whenever the stop rule should bite. + +--- + +## 6. Next, in the goal's order {#next} + +1. Land PR #202 once CI is green (the C-runtime `nextafter` commit). +2. Iteration 8 on the tie branch: close the seven fail-opens by the closures in the table, + re-gate, re-read the whole diff, amend the report, open the PR for the click. +3. Open the report PR (`loop-report-1`: the iterations 1-7 narrative plus this status file). +4. Then, unchanged from the report's queue: the refusal registry behind kpi: + named-refusal-share, gap: undocumented-boolean-comparison with the unsigned class, the + enforcement faults (the red Rust unit gate CI cannot see), and the bench baseline. From 6df06d24027275d0ded50a46863a4470d971f1d1 Mon Sep 17 00:00:00 2001 From: AmirHossein Roozbahani Date: Mon, 7 Sep 2026 21:57:57 +0200 Subject: [PATCH 5/8] the loop report gains iteration 8: the parity branch merged, a fifth tie round, a sixth review Co-Authored-By: Claude Fable 5.1 --- .../docs/reports/2026-09-02-loop-report-1.md | 564 ++++++++++++++---- .../docs/reports/2026-09-06-loop-status.md | 246 +++++--- 2 files changed, 610 insertions(+), 200 deletions(-) diff --git a/packages/confit/docs/reports/2026-09-02-loop-report-1.md b/packages/confit/docs/reports/2026-09-02-loop-report-1.md index 86e445ae..530b77e9 100644 --- a/packages/confit/docs/reports/2026-09-02-loop-report-1.md +++ b/packages/confit/docs/reports/2026-09-02-loop-report-1.md @@ -1,4 +1,4 @@ -# The loop's first report: iterations 1-7 (2026-09-02, amended 2026-09-06) +# The loop's first report: iterations 1-8 (2026-09-02, amended 2026-09-06 and 2026-09-07) **What this is.** A dated report of one loop's work against `packages/confit/docs/goal.md`, read through the yardsticks the baseline reading measured @@ -26,6 +26,16 @@ enumerating. That repetition is itself a measurement and has its own section bel (enumeration-not-terminated); it is stated there as a **fork put to the owner**, not as a decision this loop took. +**What the third amendment changed.** Iteration 8 (2026-09-07) closed the parity item and ran +a fifth round on the tie branch. The parity branch **merged** on the owner's approval, so its +row here is a record rather than a candidate; between its ship gate and that merge a design +pass and the orchestrator's own read of the whole diff found a **real parity bug** no gate had, +so that closure is written up with what found it. The tie branch closed the seventh fail-open +that same reading habit had found, then closed the round-4 review's six — three of the five +closures are **allow-lists and metadata reads** rather than longer name lists — and was gated +**PASS** again. A sixth review then found four more shapes that serve a value which is not a +function of the query. The enumeration section below says what that does to the sequence. + **Slugs.** `gap:` and `finding:` citations resolve in the baseline reading; `goal:`, `kpi:`, `exclusion:` and `ask:` in `goal.md`; `claim:` and `divergence:` without a local definition in `packages/confit/docs/oracle/`. Sections here carry kebab-case anchors and are @@ -46,14 +56,22 @@ priorities give. Its working rules: - **Gated PRs and dated reports are the only outputs.** A branch is not done when its author says so; it is done when an independent gate has rebuilt both legs and reproduced or refuted every clause of its promise. -- **A merge needs the owner's own GitHub approval.** Nothing in this loop merged itself. One - branch merged in this window, on his click. +- **A merge needs the owner's own GitHub approval.** Nothing in this loop merged itself. **Two** + branches merged in this window, each on his click. The shape each iteration took: one implementer per item, then an independent **review** and an independent **gate**, each in its own worktree, each building master and the branch from source rather than trusting a shipped artifact. That structure is the reason this report can name a gate for every number: the gate legs are what produced them. +**A fourth leg was added at the owner's correction, and it paid immediately.** From 2026-09-06 +the orchestrator reads every diff itself rather than treating a PASS gate as the review. That +reading is what found the `nextafter` NaN-sign parity bug on a branch two gates had passed, the +tie branch's seventh fail-open, and four doctrine slips in `goal.md` — none of which a gate or +a probing review had. Gates are evidence; they are not the review. The rule that follows from +it is also now standing practice: findings are closed **before** a PR is presented, so a branch +goes back to draft rather than forward with a list. + **One measurement about the loop itself, worth keeping.** Every master leg the loop ran — **eleven of them across iterations 1-4, and every leg since** — reproduced the baseline's campaign census exactly: `AGREE` 1013 / `REFUSED` 944 / `AGREE_TRAP` 21 / `UNSHIPPED` 14 / @@ -65,6 +83,13 @@ found 1804 the `DIVERGE_VALUE`. Iteration 7's gate reproduced the same six count tip four days later, seed 1804 included. claim: campaign-verdicts-today is reproducible on this machine, and a single seed flipping is therefore signal, not scheduling noise. +**Iteration 8 is where that census finally moves, and it moves for the stated reason.** Master +advanced a third time, to `8796bb2`, and the counts went to `AGREE` **1014** / `DIVERGE_VALUE` +**0** — one seed, 1804, the one the parity branch was chartered to fix. Every other count is +unchanged. So the invariant this loop has been leaning on holds in the strongest form +available: eleven-plus master legs held the census fixed while master moved for docs, and the +one master change that was supposed to move it moved exactly the one seed it named. + --- ## 2. What closed {#closed} @@ -214,9 +239,9 @@ the gate found clean; none is a wrong answer. *Shipped (iteration 5).* All three mediums are the subject of `5819c3a` — `Lit` equality and the IR round trip can both see a NaN's sign now, the generator emits both signs and `-0.0`, and the join-residual refusal names the node it actually failed to recognise — and the -**ship gate** read that tip and returned **PASS**. The branch is **PR #202, open, awaiting the -owner's approval click**, and it is the loop's only candidate for kpi: engine-parity. Its -final gate numbers over the same seeds 0-1999 at `--workers 8 --timeout 20`: master `AGREE` +**ship gate** read that tip and returned **PASS**. The branch became **PR #202** and stood open +for the owner's approval click, the loop's only candidate for kpi: engine-parity. Its +ship-gate numbers over the same seeds 0-1999 at `--workers 8 --timeout 20`: master `AGREE` 1013 / `REFUSED` 944 / `AGREE_TRAP` 21 / `UNSHIPPED` 14 / `DIVERGE_OPT` 7 / `DIVERGE_VALUE` 1, against branch `DIVERGE_VALUE` **0** and `AGREE` **1014** — **one flip**, seed 1804 — with the suite outcome-identical on every shared node id on release **and** debug, @@ -231,7 +256,40 @@ divergence** — both engines erroring at run time is `AGREE_TRAP` and the two m never compared, so that pin was never recording a divergence — and drops the `known-limitations.md` row that listed it as a limitation. It touches no production code and it closes one of the four lows above by reclassifying it, but no gate record in this loop's -journal names that tip. +journal named that tip when it was written. Iteration 8 closed that gap the only way it can be +closed: a gate read the **final** tip, after the design pass and the review fixes, and it is +the histogram quoted below. + +**Merged (iteration 8, 2026-09-06).** PR #202 is on master, rebased, at tip `8796bb2`, on the +owner's approval click. kpi: engine-parity now reads `DIVERGE_VALUE` **0** on master rather +than on a branch. The final-tip gate over seeds 0-1999 reads `AGREE` **1014** / `REFUSED` +**944** / `AGREE_TRAP` **21** / `UNSHIPPED` **14** / `DIVERGE_OPT` **7** / `DIVERGE_VALUE` +**0** — a histogram **identical** to the ship gate's, so nothing the merge window added moved +a verdict — with the root suite at **3326** passed / 1 skipped / 9 xfailed / 2 errors (the +same absent-`pyspark` collection pair) and `cargo test` on release **and** debug showing the +five pre-existing failures and no others. + +**What the merge window found, and it was not the gate that found it.** Between the ship gate +at `5819c3a` and the merge the branch took a **design pass** — one home per duplicated rule: +`fold_operand`, `out_of_range_trap`, the NaN-sign argument stated once at `Lit`, one +`inf`/`nan` token path — and then the orchestrator's own read of the whole diff. That read +returned four items a **PASS** gate had not: a doc over-claim (`fold_operand` said *every* +strict numeric operator, while comparisons deliberately elide no NULL), a stale `cmp` comment, +one production `f64::NAN` literal, and a **real parity bug**. `duck_nextafter` answered every +NaN input with a fresh **positive** NaN, where DuckDB hands a NaN operand back **with its own +sign**: `nextafter(-nan, 1.0)::VARCHAR` printed `nan` against DuckDB's `-nan`. The campaign +could not have caught it — it never feeds a NaN into `nextafter`, and that blind spot is now +named rather than assumed. + +**The fix took two attempts, and the second one is the lesson.** The first pinned *which* +operand is returned when **both** are NaN, measured on Windows; Linux CI measured the **other** +operand, because that choice belongs to the platform's C runtime and its compiler. The kernel +now calls the platform's own C `nextafter` — the function DuckDB's `std::nextafter` calls — so +it matches by construction wherever it is built: lone-NaN rows are pinned, both-NaN rows are +**compared against the machine's own oracle and never pinned**. Three facts carry forward. A +sign a libm or a compiler picked is compared, never pinned. A pin measured on one platform is +not a pin, and **CI is the cross-platform leg**. And a gate that rebuilds both legs is +evidence, not a review: reading the diff is what found this one. ### 2.3 finding: static-only-tie-order @@ -515,6 +573,188 @@ aggregate catalogue, deep nesting and 300-call select lists, and values that com but stay **distinguishable** (`0.0` vs `-0.0`, `'a'` vs `'A' COLLATE NOCASE`, 200k of each) — each measured across all seven settings, each giving a single answer. +**The seventh fail-open, and what found it (`04f113a`).** Before round 5 opened, the +orchestrator's read of the whole diff — not a probe, not a gate — found one more, and it is +the only one in this branch's history found by **reading the rule** rather than by asking +DuckDB questions. An alias behind a star is placed by counting from the wrong end when a +top-level `unnest(struct)` also expands: `SELECT *, a AS k, unnest(st) FROM s ORDER BY k` +served a **tied** `k`. Closed test-first, with the suite at **3419** passed and the campaign +**byte-identical**. Three doctrine slips in `goal.md` went with it — a today-state sentence +and a mechanism paragraph, neither of which belongs in a target document, and the serving +example that sat under a `REFUSES:` heading, now labelled `SERVES:` — and so did the +`MATCH_FLOOR` comment, which argued a skew for a statement whose answer has none: the loss is +the table-function allow-list's price, and the comment now says so. The owner paused the loop +when PR #202 merged and restarted it for this iteration. + +**Round 5 (iteration 8, `36ae02e`): the six the round-4 review found, closed by reading +DuckDB's own metadata rather than by listing more names.** Five rules, and the shape of three +of them is the point — this round stopped extending name lists and started reading the +catalogue and the parse. + +- **The `FROM` allow-list.** Every `BASE_TABLE` must name a **static or a CTE**; everything + else refuses by name. That closes the implicit file scan (a `BASE_TABLE` whose name **is** + the path), the catalogue views reached as tables (`duckdb_tables`, + `information_schema.tables`), and the harness's own `__arrow_s` registration name, with one + rule instead of three lists. +- **`SHOW_REF` refuses by node.** `SUMMARIZE`, `DESCRIBE` and `SHOW` serialize as a node that + names none of the aggregates it runs, so the node itself is the refusal. +- **A macro is read as its call.** The aggregate, stability, clock and maker readings are fed + from the **parsed definitions** of the catalogue macros a statement names, so + `json_group_array`, `json_group_object`, `weighted_avg` and `geomean` refuse through a macro + exactly as they do when spelled directly. +- **One-argument `age()` refuses by arity**, read out of the statement's own parse. +- **Zoned types refuse by DuckDB's own metadata**, in three sightings: a static column's + declared type, a `cast_type` node in the parse, and a maker function's `return_type` from + the catalogue. + +*One reading, not two.* The macro rule's parse-based stability read reproduces the old +regex-based one name for name — `ago`, `current_catalog`, `current_database`, `current_query`, +`current_schema`, `current_schemas`, `pg_conf_load_time`, `pg_postmaster_start_time`, +`pg_sleep` — so the regex is **deleted** and the branch carries a single reading of that +question. Four table-macro bodies do not parse as their definition text +(`duckdb_logs_parsed`, `duckdb_profiling_settings`, `histogram`, `histogram_values`); +`histogram` is caught by the aggregate catalogue under its own name and the other three by the +table-function rule, so that gap costs nothing. One implementation ceiling is recorded rather +than hidden: pyo3's 12-tuple extraction limit put the new names into the existing one-column +name list rather than into columns of their own. + +*Gate (branch `36ae02e`, master `8796bb2`):* **PASS**, with a caveat the gate raised first and +this report keeps first: **the branch is not rebased**. Its merge-base is `f81e17c` and master +has advanced **13 commits** past it, so every number below measures the branch **as pushed**, +not the merged result. Suite: master **3326** passed / 1 skipped / 9 xfailed / 2 errors over +3338 ids, branch **3435** / 1 / 3 / 2 over 3441 ids, with **3286 shared ids and zero outcome +changes**, 155 branch-only ids all passing, and 52 master-only ids every one of which sits in a +file only master touched since the merge-base. The branch deletes no `def test_` and no +`#[test]`. A separately built **debug** engine gives the identical 3441 ids and the identical +outcomes. `cargo test --release --lib` reads master 269 / 5 and branch 266 / 5 with the **same +five names** on both sides — the three-test gap is master's own new Rust tests, not a branch +deletion — `cargo check --all-targets` is byte-identical to the baseline at 2 warnings, `ruff` +is clean, and the public API diff is empty. + +*Campaign, and the one flip that is master's rather than the branch's.* Seeds 0-1999 at +`--workers 8 --timeout 60`: `AGREE` **1007** / `REFUSED` **951** / `AGREE_TRAP` **20** / +`UNSHIPPED` **14** / `DIVERGE_OPT` **7** / `DIVERGE_VALUE` **1**, against master's 7 findings +in 3 classes and the branch's 8 in 4. **Against `04f113a` the delta is zero** — the round adds +no flip, and the findings are byte-identical after sorting, 8 on each side. Against master the +per-seed delta is **81**: 44 planted-twin seeds whose SQL itself changed (35 of them changing +verdict), 41 identical-SQL `REFUSED` -> `REFUSED` message renames, 4 identical-SQL `AGREE` -> +`REFUSED` on the order-sensitive aggregate rule (seeds 113 / 1036 / 1314 / 1967), and **seed +1804** `AGREE` -> `DIVERGE_VALUE`. That last one is **not the branch's**: it reproduces at the +merge-base with the identical `nan` versus `-nan` detail, so it is the NaN-sign work master has +merged and this branch does not yet carry. It is the first time the two lines have met, and it +is the gate's own argument for rebasing before merging. + +*Corpus: **546 -> 540**, with a reproduced reason per statement.* Against master's 547 the gate +attributes **seven** moved, all `match` -> `unsupported`, zero FAIL: one is round 4's +`test_all_types()` statement, and six are new. Five are the same `SELECT COUNT(*) FROM t` from +`test_issue_1812.test` over the **driving** table, and the reason is measured rather than +argued — the replay's own caller frame held a pyarrow table named `t` and the unqualified name +resolved against it, so the build was handed **zero** statics and still produced a constant. +With one more Python frame between, the same statement refuses; the gate reproduced both +directions (`backend='constant'` rows `[6]` equal to the mined answer with that local present, +refused without it). The sixth is `geomean`, whose catalogue body is `exp(avg(ln(x)))` and +whose `avg` is order-dependent — the macro rule reaching a statement the corpus mined. **Zero** +statements moved because of the zoned rule. + +*Mutation, on both legs.* The author reverted each of the five rules by re-edit, rebuilt, +re-ran and restored by re-edit: the `FROM` allow-list gives 2 red, the `SHOW_REF` marker 1, the +aggregate read taken back to the statement's own names 1, the `age` arity arm 1, and the zoned +marker 4. The gate ran its own five against the standing rules and got 2, 16, 4, 37 and 16 red +— every rule fenced, each restored with a zero-byte `git diff` afterwards. **48 hand probes** +beyond the suite, 29 must-serve (required to be on backend `constant` **and** to equal DuckDB's +own rows as an unordered multiset) and 19 must-refuse-by-name: **48/48**. + +*Two over-refusals disclosed, both stated as prices.* `TIME WITH TIME ZONE` is sighted with the +zoned class although it renders **without** the session zone (measured) — because +`DATE + TIMETZ` produces a `TIMESTAMPTZ`, and separating the two would require the reading to +type every expression rather than to read declared types. And a static column carrying the +zoned marker is sighted wherever it sits **among the query's own statics**, not only where the +statement selects it. Both are in `known-limitations.md` and in the test section's own header; +the collation that takes `min` / `max` off the order-free list is carried forward unchanged. + +*The review that followed (round 6) found **five** shapes and one over-refusal, and its method +is why the count carries weight.* It rebuilt the branch's shape reading, its exact-sum reading, +its ordering-word reading and its refusal ladder as a **Python replica**, mined every SQL-shaped +literal out of the branch's own test file (162 candidates, 41 not runnable as a single +statement), and ran the remaining **121 against the built branch across five static-table +shapes**: **121/121 agreement, zero disagreements**. Then it probed for what the replica and the +branch **both** miss. It re-ran the gate itself first: 3435 passed / 1 skipped / 3 xfailed / 2 +errors, the rule's own file **150 passed**, corpus replay green at `MATCH_FLOOR` 540. + +1. **HIGH — a `TIMESTAMPTZ` built from a string argument escapes all three zoned sightings.** + `strptime` / `try_strptime` with `%z`, and `json_transform` / `from_json` and their + `_strict` forms with a `"TIMESTAMP WITH TIME ZONE"` structure string, are typed at **bind** + time from an argument, so the catalogue's `return_type` says `TIMESTAMP` or `ANY`, the parse + carries no `cast_type`, and no static column is involved. Measured: one such select froze + `'2019-12-31 20:00:00+01'` on a Europe/Berlin machine while raw DuckDB over seven settings x + two reps gave **three** answers across zones; `date_part('hour', ...)` freezes 20 against + 19 / 14 / 4. The control is exact — the same instant written as a `TIMESTAMPTZ` literal cast, + same statics, same connection, **refuses**. +2. **HIGH — a macro whose body calls another macro is classified by nothing.** The expansion is + exactly **one level** deep, and the inner name is itself a macro, so the aggregate, stability + and maker reads all see a macro with a `NULL` stability. `geometric_mean` froze one value + where raw DuckDB gave **4** distinct answers over 300k rows, and `wavg` froze one where raw + gave **5**; the controls one level shallower — `geomean`, `weighted_avg` — refuse by name on + identical data. +3. **HIGH — the `FROM` allow-list is flat and unscoped.** CTE names are gathered by recursive + descent over the whole parse, so a CTE declared inside **any** subquery whitelists that bare + name for an outer `FROM` that cannot see it. `FROM 'e2e.csv'` with an inner CTE quoted to the + same name serves and freezes the **file system**: the identical query text froze `o=1` in one + working directory and `o=5` in another. Without the CTE, the same statement refuses in both. +4. **MEDIUM — the allow-list compares only the last path segment.** A static named `tables` + whitelists `information_schema.tables`, and one named `duckdb_tables` whitelists + `system.main.duckdb_tables`; the qualifier the query wrote is dropped before the membership + test, so the frozen counts (4 and 2) move with how many statics the caller happened to + register. Removing the colliding static is the only difference between serving and refusing. +5. **LOW — the `age` arity reading is the one name reading that stops at the statement**, and + its arm reports the **inner** name where the four beside it report the outer one. Nothing + escapes today: all 131 catalogue macro definitions were enumerated and **zero** call `age` + or any zoned maker, and every direct spelling — bare, schema-qualified, named-argument, and + inside a lambda — refuses. +6. **MEDIUM, and the opposite of an escape — one zoned column in any static refuses every query + on that build.** The static-column sighting asks the catalogue about all of the caller's + statics with no reference to the statement and sits above the other arms, so `SELECT 1 AS o` + refuses when an unrelated static carries a `TIMESTAMPTZ`. Drop that static and the same three + statements serve. The disclosure above reads as a projection-level cost; measured, it is a + per-build switch, and this report states it that way. + +*The review's clean list, by the same method.* The whole over-refusal battery serves: naive +`TIMESTAMP` / `DATE` arithmetic, `strptime` **without** `%z`, `epoch_ms`, `INTERVAL`, +two-argument `age`, `list_sum` / `list_avg` / `array_to_string`, schema- and catalog-qualified +statics, quoted and aliased statics, CTEs used only in nested subqueries and in set-operation +branches, `WITH RECURSIVE` self-reference, a CTE named like a file, a static named like a +catalogue view, and a struct field literally spelled `with time zone`. A systematic sweep of +**all 1343 `CONSISTENT` catalogue scalars** across seven environments (three time zones, a +non-Gregorian calendar, one and eight threads, a default collation) x two working directories +found **24** that answer more than one way, and the fold refuses **22** — the two exceptions are +the bind-time zoned pair above. The calendar setting reaches ICU only through `TIMESTAMPTZ`, which refuses, so +naive temporal types are unaffected by it. `DESCRIBE` nested in a subquery still refuses; +`sqlite_master`, `pg_catalog.pg_class`, `__arrow_s`, `query_table()`, `duckdb_settings` and the +file and glob scans all refuse by name; and the exact-sum rule behaves as it did at `04f113a`. + +*Its design findings, kept because they are about maintainability rather than answers.* Six, +one of them clean. The six offenders the shape reading returns travel as a **positional array** +whose position-to-meaning binding lives in three places no compiler checks, where named columns +and a named struct would make a seventh sighting a compile error rather than a swapped message. +The arity arm's odd column is unmarked, so a reader cannot tell a decision from a typo. The +macro paragraph in `known-limitations.md` over-claims completeness — it is the stability, clock, +aggregate and maker reads only, stopping at one level, which is exactly the +macro-inside-a-macro hole — and the +eight maker names beside it are a hand-copied snapshot of a catalogue query that no test pins. +The `age` paragraph argues about a third party's own classification where a **measured** fact is +available and stronger, which is both the house rule and the better sentence. And four comments +carry two different counts of "three" and "four" readings for two different groupings, where +naming the three zoned sightings once and referring to them by that name would never go stale. +The clean one is the comment policy: **zero** ticket or PR references and **zero** dates across +the whole diff, and the `MATCH_FLOOR` comment names its six statements as standing facts rather +than as a changelog. + +*Status:* gated **PASS** at `36ae02e`, and **not a merge candidate**, for two separate reasons +this time. Four of round 6's shapes serve a value that is not a function of the query — three +HIGH and one MEDIUM — which is the same control violation the branch exists to close. And the +branch is 13 commits behind master, so its numbers describe a tree that exists nowhere else: +rebase onto `8796bb2` and re-gate is a precondition, not a tidy-up. + --- ## 3. Gate state, branch by branch {#gate-state} @@ -522,20 +762,22 @@ each measured across all seven settings, each giving a single answer. | branch | tip | gate verdict | campaign delta vs master (2000 seeds) | corpus | state | |---|---|---|---|---|---| | `fix-corpus-slip` | `a7c5798` | PASS WITH FINDINGS | 0 flips; census identical | 547 / 131 / 0 both legs | **merged** (PR #200) | -| `fix-nan-sign-varchar` | `5819c3a` (PR head `81e8fa2`) | **PASS** (ship gate) | 1 flip: seed 1804 `DIVERGE_VALUE` -> `AGREE`; `DIVERGE_VALUE` 1 -> 0, `AGREE` 1013 -> 1014 | 547 / 131 / 0 both legs | **PR #202, open, awaiting the owner's approval click** | +| `fix-nan-sign-varchar` | `5819c3a` gated; merged, master `8796bb2` | **PASS** (ship gate, then a final-tip gate at the merged tree) | 1 flip: seed 1804 `DIVERGE_VALUE` -> `AGREE`; final tip `AGREE` **1014** / `REFUSED` 944 / `AGREE_TRAP` 21 / `UNSHIPPED` 14 / `DIVERGE_OPT` 7 / `DIVERGE_VALUE` **0**, histogram identical to the ship gate's | 547 / 131 / 0 both legs | **merged** (PR #202, 2026-09-06, on the owner's click) | | `fix-fmod-sign` | `ec71979` | PASS, then read as redundant | its one flip was the same seed 1804, already carried by the branch above | 547 / 131 / 0 both legs | **dropped**; grid salvaged | -| `refuse-static-tie-order` | `2d24744` | **PASS** (two disclosed over-refusals, 4-5x static-only build cost) | 89 flips, all attributed; `AGREE` 1013 -> 1007, `REFUSED` 944 -> 951, `AGREE_TRAP` 21 -> 20 | 546 / 132 / 0; floor moved once, earned | **not a merge candidate**: six review findings, four HIGH | +| `refuse-static-tie-order` | `36ae02e` | **PASS** (two disclosed over-refusals, 4-5x static-only build cost; **not rebased** — 13 commits behind master) | 81-seed delta vs master `8796bb2`, all attributed, one of them master's own; **zero** vs the branch's previous tip `04f113a`; `AGREE` **1007** / `REFUSED` **951** / `AGREE_TRAP` **20** | 540 / 138 / 0; floor moved twice, each statement's reason reproduced | **not a merge candidate**: round-6 review found four shapes that answer wrongly, three HIGH; and a rebase is a precondition | Suite counts, each from the gate that produced it: master **3281** passed / 1 skipped / 3 -xfailed / 2 errors on every leg; `fix-nan-sign-varchar` **3320** at `550f949` and +xfailed / 2 errors on every leg up to `f81e17c`, and **3326** / 1 / **9** / 2 at `8796bb2` +once the parity branch merged; `fix-nan-sign-varchar` **3320** at `550f949` and outcome-identical on every shared node id at its ship gate; `refuse-static-tie-order` **3366** -at `a08147e` and **3415** at `2d24744`; `fix-fmod-sign` stood at **3284** when it was gated, -and is moot now. Every branch was also run on a separately built **debug** engine with -identical results and no `debug_assert` firing. Dialect L2 reads **288/678** with 0 FAIL -wherever it was taken. The public API diff is empty on all four, and `cargo test`'s failing -**set** is master's five on all four. The corpus reads 547 / 131 / 0 on every leg of every -branch except `refuse-static-tie-order` at `2d24744`, which is **546 / 132 / 0** — one -statement, moved for a reproduced reason. +at `a08147e`, **3415** at `2d24744`, **3419** at `04f113a` and **3435** at `36ae02e`; +`fix-fmod-sign` stood at **3284** when it was gated, and is moot now. Every branch was also run +on a separately built **debug** engine with identical results and no `debug_assert` firing. +Dialect L2 reads **288/678** with 0 FAIL wherever it was taken. The public API diff is empty on +all four, and `cargo test`'s failing **set** is master's five on all four. The corpus reads +547 / 131 / 0 on every leg of every branch except `refuse-static-tie-order`, which reads +**546 / 132 / 0** at `2d24744` and **540 / 138 / 0** at `36ae02e` — seven statements against +master, each moved for a reason the gate reproduced rather than relayed. **The contention resolved by measurement, not by a choice.** `fix-fmod-sign` and `fix-nan-sign-varchar` both changed the same `DuckF64` NaN arm and both flipped the same @@ -543,20 +785,22 @@ single seed, and the first text left the pick to the owner. Iteration 4 removed `fix-fmod-sign`'s production change **was** that one line, already carried on the other branch, so there was nothing to choose between. Its 29-row sign grid over `%` / `mod` / `fmod` now lives on `fix-nan-sign-varchar` as tests, and the branch is dropped. One candidate -remains for kpi: engine-parity, and it is a click away rather than a decision away. - -**Both live branches carry review findings their gates did not raise, and the two are still -not the same weight.** On `fix-nan-sign-varchar` the seven were naming, coverage and comment -defects over a production diff the gate found clean — an equality that could not see a NaN -sign, a generator that never emitted one, a refusal selector that misdiagnosed a shape it -already refused correctly. None answered a query wrongly; the three mediums closed in -iteration 5 and one low closed by reclassification, and what is left of the branch is a click. -On `refuse-static-tie-order`, **four of the six do** answer a query wrongly: a file scan, a -macro over an order-dependent aggregate, a clock read the catalogue calls `CONSISTENT`, and -`SUMMARIZE` each freeze something that is not a function of the query. A control violation -inside the branch chartered to close that control is a different class of open item from a -stale comment, and the table above states the two differently on purpose. What is new at -iteration 7 is that the **repetition**, rather than any one of the four, is the finding. +remained for kpi: engine-parity, and iteration 8 spent the click: it is master's now. + +**One live branch is left, and its open items are still a different class from the parity +branch's were.** On `fix-nan-sign-varchar` the review findings were naming, coverage and +comment defects over a production diff the gate found clean — an equality that could not see a +NaN sign, a generator that never emitted one, a refusal selector that misdiagnosed a shape it +already refused correctly. None answered a query wrongly. The one thing on that branch that +did was found by neither a gate nor a review but by **reading the diff**, and it was fixed +before the merge (the NaN-sign `nextafter` bug above). On `refuse-static-tie-order`, four of +round 6's items **do** answer a query wrongly: a `TIMESTAMPTZ` built from a string argument, a +macro one level too deep, a CTE name escaping its scope, and a catalogue view reached past its +qualifier each freeze something that is not a function of the query. A control violation inside +the branch chartered to close that control is a different class of open item from a stale +comment, and the table above states the two differently on purpose. What was new at iteration 7 +is that the **repetition**, rather than any one finding, is the measurement; iteration 8 adds a +fifth round to it without ending it. --- @@ -565,32 +809,57 @@ iteration 7 is that the **repetition**, rather than any one of the four, is the This is a measurement about the tie branch's **method**, not a finding against it, and it is the reason a design question already put to the owner now has evidence under it. -**What four review rounds did.** Each round closed every fail-open the last one found, and -each next round found more — from the same surface, by the same method: an independent reader +**What five review rounds did.** Each round closed every fail-open the last one found, and each +next round found more — from the same surface, by the same method: an independent reader probing DuckDB for shapes whose answer is not a function of the query text and the statics. ``` ties -> ORDER BY #N -> the LAST alias -> POSITIONAL JOIN -> ASOF JOIN -> collated min/max -> the OrderBinder fallback -> machine-state table functions -> the implicit file scan -> a macro over an order-dependent aggregate -> -one-argument age() -> SUMMARIZE -> any rendered TIMESTAMPTZ +one-argument age() -> SUMMARIZE -> any rendered TIMESTAMPTZ -> +an alias behind a star that unnest(struct) displaces -> +a TIMESTAMPTZ typed at bind time from a string argument -> +a macro whose body calls another macro -> +a CTE name escaping its subquery -> a catalogue view reached past its qualifier ``` +**Round 6 adds to the sequence; it is not the round that came back empty.** Of its five +shapes, three are HIGH and one is MEDIUM, and each of those four **serves** a value that is not +a function of the query — a frozen session time zone, a frozen order-dependent aggregate, a +frozen file system, a frozen catalogue count. Only the fifth is different in kind: the +one-argument `age` reading is statement-only, but all 131 catalogue macro definitions were +enumerated and none calls `age`, so nothing escapes through it **today** — a latent hole, not a +wrong answer. The sixth item is an over-refusal, which is the fail-closed direction. So the +count of rounds that came back with nothing wrongly served is still **zero**. + Every entry is closed or open on its own merits, and every fix is right. What the **sequence** -measures is the shape of the work: four rounds, no round empty, no round's findings predicted -by the one before it, and the newest four reaching a file system, a macro body, a transaction -clock and a statement whose serialization names nothing a rule can read. The rule is chasing a -surface — DuckDB's whole function, join, table-function and session-setting catalogue — that -neither the branch nor four rounds of independent review have been able to enumerate, and -nothing this loop measured says the next round is empty. - -**Why that reads as structural rather than as a run of bad luck.** Two things. The findings get -**narrower in kind** each round — a clause a parser can see, then a binder rule, then a -catalogue flag, then a serialization node — so the question has moved from "did we cover the -shapes" to "can this reading see the shape at all". And the last two rounds both found cases -where **no name appears anywhere in the parse**: `SUMMARIZE`'s `SHOW_REF`, `FROM ''`'s -`BASE_TABLE`, and a `TIMESTAMPTZ` rendered by a plain cast. A rule that decides by reading -names cannot be completed against shapes that carry none. +measures is the shape of the work: five rounds, no round empty, no round's findings predicted +by the one before it. The rule is chasing a surface — DuckDB's whole function, join, +table-function, macro and session-setting catalogue — that neither the branch nor five rounds of +independent review have been able to enumerate, and nothing this loop measured says the next +round is empty. + +**Why that reads as structural rather than as a run of bad luck.** Three things now. The +findings get **narrower in kind** each round — a clause a parser can see, then a binder rule, +then a catalogue flag, then a serialization node, then a type the catalogue does not carry — so +the question has moved from "did we cover the shapes" to "can this reading see the shape at +all". Rounds 4 and 5 both found cases where **no name appears anywhere in the parse**: +`SUMMARIZE`'s `SHOW_REF`, `FROM ''`'s `BASE_TABLE`, and a `TIMESTAMPTZ` rendered by a +plain cast. And round 6 goes one step past that: `strptime('...','%z')` carries a name the +reading **does** find, in a catalogue that reports its return type as naive, because the zoned +type is chosen at bind time from a **string argument**. A rule that decides by reading names and +declared types cannot be completed against shapes whose type is a value. + +**One thing round 5 does change, and it is a point for the enumerating side.** Three of its +five closures are **allow-lists and metadata reads** rather than longer name lists: every +`BASE_TABLE` must name a static or a CTE, macros are read through their own parsed definitions, +and zoned types are read off DuckDB's declared types. Each of those covers a class rather than a +list, and the corpus and campaign price them exactly. Round 6's two allow-list findings are then +**defects in that reading itself** — scope and qualification — not new shapes to enumerate, +which is a smaller kind of open item than the shapes rounds 1-4 kept producing. The +two HIGHs that are not of that kind (bind-time zoned types, macros one level too deep) are the +ones that keep the sequence going. **The fork, stated as a fork.** The alternative already on the table is to stop deciding *which shapes are pure* and instead **pin the build-time fold's configuration through the @@ -605,7 +874,7 @@ covers order *inside* a value). Both are **stated, not ruled**, and both are the What this loop is claiming, and what it is not. It is **not** claiming the fork is decided, that enumeration is the wrong approach, or that the tie branch should be abandoned — the branch closes a real silent-wrongness class, every rule in it is measured, and its gate is -PASS. It **is** recording that four consecutive rounds of enumeration have not terminated, +PASS. It **is** recording that five consecutive rounds of enumeration have not terminated, that what they find trends away from what a name-reading rule can see, and that this is the first evidence the loop has produced bearing on those two asks. The fork's own claim is that pinning the configuration changes what the rule must enumerate from "every impure shape DuckDB @@ -631,7 +900,11 @@ change meaning. Separately, **4** of the flips are a deliberate over-refusal — a rule the loop chose, not a population artefact and not a defect. Any next census over this generator is measuring a slightly different population under a slightly stricter rule; the baseline's validity caveat under acceptance-reading now has two reasons to bite rather than -one. +one. A third arrives with iteration 8: **master itself moved**. At `8796bb2` master reads +`AGREE` **1014** and `DIVERGE_VALUE` **0**, so the tie branch's 1007 is a delta against a +baseline the parity merge changed, and the 81-seed comparison the gate ran includes one flip +(seed 1804) that belongs to master rather than to the branch. Whichever leg reading N=2 takes, +it should name the master tip beside the number. **The over-refusal detector is class-agnostic; the generator is not, and that is now an argued position rather than an oversight.** A test pins the detector as reading every refusal @@ -658,16 +931,29 @@ relayed. Every other leg of every branch this loop gated still reads 547 / 131 / mechanism is now demonstrated rather than argued: the ladder cannot shrink in silence, and a correct new refusal costs one documented line each time it lands. +**And it moved again, six times at once, which is the first test of that mechanism at scale.** +Round 5 takes the floor **546 -> 540**. Five of the six are the same `SELECT COUNT(*) FROM t` +whose constant the build produced with **zero** statics in hand, because the replay's own caller +frame carried a pyarrow table of that name; the sixth is `geomean`, whose catalogue body is +`exp(avg(ln(x)))`. Every one has a reproduced reason beside it and none is a FAIL. Two things +follow for reading N=2. The ratchet's cost is **not** one line per refusal in general — a rule +that covers a class costs however many mined statements that class holds — and five of these six +say as much about the **replay harness** as about the engine, since the same statement refuses +when one more Python frame stands between the replay and the build. kpi: coverage-ladder is +measured through that harness, so the harness's own name resolution is part of what the number +means. + **The Rust unit gate is red on master and CI cannot see it.** `cargo test` is 266 passed / 5 failed on every master leg the loop ran, the same five names each time (`pin_ftoi_rounding_and_traps`, `pin_ssubstr_window_arithmetic`, `pin_stoi_trims_whitespace_like_duckdb_cast`, `table_and_custom_partition_the_catalogue`, `substr_window_arithmetic_via_sql`), and CI runs only `pytest`. **Seven** separate gate -records said so by iteration 4, and every gate leg since has added another — iteration 7's -reads the same five names on both legs. A regression inside `exec::tests` would pass a green-bar -check today. This is an **enforcement fault**, the shape the baseline reading calls a finding -rather than a gap, and no item in this loop owned it — iterations 3 and 4 included, which -added four more records of it and no owner. +records said so by iteration 4, and every gate leg since has added another — iteration 8's +reads the same five names on both legs, at master `8796bb2` (269 passed / 5 failed) and on the +tie branch (266 / 5). A regression inside `exec::tests` would pass a green-bar check today. +This is an **enforcement fault**, the shape the baseline reading calls a finding rather than a +gap, and no item in this loop owned it — eight iterations in, which is now itself the +measurement. **Closed since the first text, each by a gate leg rather than by an author's claim:** the `- ` collapse; the trailing-`;` and trailing-comment false refusals; the three @@ -690,27 +976,57 @@ sits outside the comparison contract — both engines erroring at run time is `A the messages are never compared — so that pin was never recording a divergence, and the `known-limitations.md` row went with it. +**Closed in iteration 8, each by a gate leg or by a reading of the diff rather than an author's +claim:** on the parity branch, the `nextafter` NaN-sign bug and the doc, comment and literal +items the design pass and the diff read found — and the branch itself, **merged**. On the tie +branch, all seven the round-4 review and the diff read had left open: DuckDB's implicit file +scan and the catalogue views and `__arrow_s` with it (one `FROM` allow-list); `SUMMARIZE` / +`DESCRIBE` / `SHOW` (one node); macros over order-dependent aggregates (macro bodies parsed and +fed into the same readings); one-argument `age()` (by arity); every `TIMESTAMPTZ` a static +column, a cast node or a catalogue return type can name; and the alias behind a star that a +top-level `unnest(struct)` displaced. One duplicate reading is **deleted** rather than fixed: +the regex-based macro stability read reproduced the parse-based one name for name, so the branch +carries one reading of that question instead of two. + **Still open, each measured, none acted on:** -- **Four fail-open shapes on `refuse-static-tie-order` at `2d24744`**, each measured by a - review of the gated tip, each answering a query wrongly rather than refusing: DuckDB's - implicit file scan `FROM ''`, a `BASE_TABLE` whose name **is** the path, so CSV, - parquet and **globs** freeze the build machine's file system while `read_csv()` on the same - file refuses; a **macro** whose body calls an order-dependent aggregate - (`json_group_array`, `json_group_object`, `weighted_avg`, `geomean` — 2, 2, 4 and 7 distinct - answers across settings); one-argument **`age()`**, which reads the transaction clock while - DuckDB's catalogue calls it `CONSISTENT`; and **`SUMMARIZE`**, whose `SHOW_REF` node names - none of the aggregates it runs — seven settings, seven answers. -- **Two lesser ones with them:** any **`TIMESTAMPTZ` rendered or decomposed** freezes the - build machine's session time zone with no function name involved anywhere — the effect - `known-limitations.md` already names as disqualifying for `test_all_types()`, now reaching - ordinary queries — and `SHOW TABLES` / `DESCRIBE` leak the harness's own `__arrow_s` - registration name into a user-visible constant. -- **Three low review findings left on `fix-nan-sign-varchar`**, none answering a query - wrongly: a `snapshot_bits` doc that forbids what a test in the same crate correctly pins, a - bind-time `fold` that reproduces arith's over-fold, and the infinite-dividend half of the - mod grid's excluded domain, covered by nothing. The three mediums closed in `5819c3a`; the - fourth low closed by reclassification in `81e8fa2`. +- **Four fail-open shapes on `refuse-static-tie-order` at `36ae02e`**, each measured by the + round-6 review of the gated tip against a refusing control on identical data, each answering + a query wrongly rather than refusing. A **`TIMESTAMPTZ` typed at bind time from a string + argument** — `strptime` / `try_strptime` with `%z`, `json_transform` / `from_json` and their + `_strict` forms with a zoned structure string — escapes all three zoned sightings, because + the catalogue's return type is naive, the parse carries no cast node and no static column is + involved: one frozen value against **three** raw answers across zones. A **macro whose body + calls another macro** is classified by nothing, since expansion stops at one level: + `geometric_mean` froze one value against **4** raw answers and `wavg` one against **5**, + while `geomean` and `weighted_avg` one level shallower refuse. A **CTE declared in any + subquery** whitelists its bare name for an outer `FROM` that cannot see it, so `FROM + 'e2e.csv'` froze `o=1` in one working directory and `o=5` in another. And the allow-list + compares only the **last path segment**, so a static named `tables` or `duckdb_tables` + whitelists the qualified catalogue view and freezes a count of what the build's own database + holds. +- **Two lesser round-6 items with them.** The `age` arity reading is the one name reading that + stops at the statement and never reaches a macro body, and its arm reports the inner name + where the four beside it report the outer one — a latent hole rather than a live one, since + all 131 catalogue macro definitions were enumerated and none calls `age` or a zoned maker. + And its **design** findings: a positional array whose position-to-meaning binding lives in + three unchecked places, an over-claiming macro paragraph in `known-limitations.md` beside an + unpinned hand-copied maker list, an `age` paragraph that argues where a measured fact is + available, and four comments carrying two different counts for two different groupings. +- **One over-refusal that is wider than its disclosure says.** A single zoned column in **any** + static refuses **every** query on that build, `SELECT 1 AS o` included, because the + static-column sighting reads the caller's statics with no reference to the statement. + `known-limitations.md` describes it as a projection-level cost; measured, it is a per-build + switch. +- **Three low review findings left on `fix-nan-sign-varchar`**, now on master, none answering a + query wrongly: a `snapshot_bits` doc that forbids what a test in the same crate correctly + pins, a bind-time `fold` that reproduces arith's over-fold, and the infinite-dividend half of + the mod grid's excluded domain, covered by nothing. The three mediums closed in `5819c3a`; + the fourth low closed by reclassification in `81e8fa2`. +- **The tie branch is 13 commits behind master and must be rebased before it can be gated for + merge.** Its gate proved the two lines have already met: seed 1804 flips `AGREE` -> + `DIVERGE_VALUE` against `8796bb2` and reproduces at the merge-base, so that flip is the + NaN-sign work the branch does not yet carry rather than anything the branch did. - **The by-name aggregate coarsening, narrowed but not closed.** Round 3 gave `sum` its typed rule through DuckDB's own overload resolution, so every **exact** accumulator serves. The by-name list was **65 of 88**, and `sum`'s exact overloads are all that came off it: @@ -718,15 +1034,20 @@ the messages are never compared — so that pin was never recording a divergence accumulators `fsum` / `kahan_sum` / `favg` that exist to **be** order-stable all still refuse by name, and `first(v ORDER BY k)` with a unique `k` still refuses whole. The same overload reading is the upgrade path for the rest; the trade is the owner's to price. -- **The table-function allow-list is deliberately conservative:** five names serve and +- **Three allow-lists are deliberately conservative, and every one of them is an over-refusal + the campaign cannot see**, by the blindness above. Five table-function names serve and everything else refuses, so a genuinely pure table function DuckDB adds later refuses until - someone lists it. That is the fail-closed direction and it is disclosed — but it is an - over-refusal the campaign cannot see, by the same blindness above. + someone lists it; every `BASE_TABLE` must name a static or a CTE, which is what costs the + corpus its `test_all_types()` statement; and `TIME WITH TIME ZONE` is sighted with the zoned + class although it renders **without** the session zone, because `DATE + TIMETZ` produces a + `TIMESTAMPTZ` and separating them would require typing every expression rather than reading + declared types. All three are the fail-closed direction and all three are disclosed. - **Four `known-limitations.md` line citations in `goal.md` are wrong** — rebased by +73 when the real shift is +161, so each now points at unrelated text. `goal.md`'s whole verified-by - mechanism is line citations, which makes this a small edit against a load-bearing claim. A - gate read one presentation defect beside them: the third code block under `goal.md`'s - `REFUSES:` heading is a **serving** counter-example, correct output and all. + mechanism is line citations, which makes this a small edit against a load-bearing claim. The + presentation defect that stood beside them is closed: the serving counter-example under a + `REFUSES:` heading now reads `SERVES:`, along with two other doctrine slips in the target + document. - **One lesser tie-branch finding left:** a `readable` guard that can never be false (one field, one initializer per arm, one dead `&&`). - `x % y`'s NaN sign is **unmatchable in principle**, not merely unfixed: two gates @@ -756,7 +1077,8 @@ the messages are never compared — so that pin was never recording a divergence | 4 (seed 1804 review closure and salvage, the tie fix round — two implementers, two gates, two reviews) | not recorded on a comparable basis | | 5 (the parity branch's ship gate and PR, the tie branch's positional-key round) | ~1.0M | | 6 (the tie branch's typed sum, last alias and positional join — one implementer, one gate, one review) | ~0.75M | -| 7 (the tie branch's round 4, its gate, its review, and this amendment) | this run, not closed | +| 7 (the tie branch's round 4, its gate, its review, and the second amendment) | not closed when it was written; the 2026-09-06 status reading puts iterations 1-7 together at ~9.0M+ | +| 8 (the parity branch's merge with its design pass and diff read, the tie branch's seventh fail-open and round 5, its gate, its review, and this amendment) | this run, not closed | Iteration 3's figure is now recorded: **~1.76M**, the largest of the three, which is what a gate that rebuilds both legs from source and a review that probes a built branch cost when @@ -767,9 +1089,18 @@ two reviews and one fix round, on the same three-role shape. Iterations 5 and 6 are recorded: **~1.0M** and **~0.75M**, both smaller than any of the first four, because each ran a single item through the three roles rather than three items at once. -Iteration 7 is this run and is not closed as this is written. **Cumulative across the loop: -~8.8M+ agent tokens** — the `+` is iterations 4 and 7, the two figures without a comparable -basis. +Iterations 7 and 8 are not: 7 was open when it was written up, and 8 is this run. The nearest +figure on a stated basis is the 2026-09-06 status reading's **~9.0M+ across iterations 1-7**, +plus **~0.4M** in subagents for that session's own orchestrator-driven review, fixes and gates, +which is not a workflow and does not compare with the rows above. **Cumulative across the loop: +~9.4M+ agent tokens**, where the `+` covers iterations 4, 7 and 8 and the orchestrator's own +context throughout. + +**A cost that is not in the table, and iteration 8 is the reason to name it.** The diff read +that found the `nextafter` parity bug and the seventh tie-branch fail-open is the +orchestrator's own context rather than a subagent's, so it is invisible to every figure above +while being the leg that found the two items no gate did. Any future accounting of this loop's +shape should say so rather than compare gate costs alone. **One operational cost, recorded because it is not free.** The loop's worktree-per-role shape put `C:` at 100% on 2026-09-06, and **35 finished workflow worktrees** were removed to clear @@ -779,8 +1110,9 @@ own cost rather than an accident. It wants a sweep between iterations rather tha disk. **The standing stop rule is unchanged: roughly 70% of the owner's weekly credit, and it is -owner-signalled** — the loop does not infer it from its own accounting, and has not been -signalled to stop, through iteration 7 included. +owner-signalled** — the loop does not infer it from its own accounting. It has not been +signalled to stop, through iteration 8 included; it was **paused** once, when PR #202 merged, +and restarted for this iteration. --- @@ -789,15 +1121,18 @@ signalled to stop, through iteration 7 included. `goal.md` orders controls before drives, so the queue does too. Nothing here is chosen; it is what the loop's own measurements rank. -1. **The parity control needs a click, not a decision.** kpi: engine-parity has - `DIVERGE_VALUE` at **0** on exactly one branch — `fix-nan-sign-varchar`, ship gate **PASS** - at `5819c3a`, now **PR #202**, open. What the first text posed as a choice between two - branches was settled by measurement (the loser had no production change of its own), and - what the first amendment left as a hold is settled too: every review finding that could - answer a query wrongly is closed, and three lows remain. The one thing to look at before - clicking is that the PR head is `81e8fa2`, one docs-and-comments commit past the gated tip, - which no gate record in this journal names. -2. **kpi: named-refusal-share, and the refusal registry it would need.** This loop roughly +1. **The parity control is closed; what is left of it is three lows.** kpi: engine-parity reads + `DIVERGE_VALUE` **0** on **master** at `8796bb2`, not on a branch: PR #202 merged on the + owner's click and a final-tip gate reproduced the ship gate's histogram exactly. The three + low review findings above ride on master now and none answers a query wrongly. Two facts + from the merge window belong in the next reading rather than in a queue item: the campaign + never feeds a NaN into `nextafter`, so that class of parity bug is invisible to it, and a + sign a platform's C runtime picked is compared, never pinned. +2. **The tie branch needs a rebase before it needs anything else.** It is 13 commits behind + `8796bb2`, and its own gate showed the two lines have met — seed 1804 flips against master + for a reason that is master's. Rebase and re-gate is a precondition for reading any of its + numbers as a merge candidate's; closing round 6's four shapes is the work after that. +3. **kpi: named-refusal-share, and the refusal registry it would need.** This loop roughly doubled the engine's refusal vocabulary and nothing lists it. `refuse-static-tie-order` alone refuses by name across the aggregate catalogue, three of six join reference types, every table function outside a five-name allow-list, collations, macros and the clock @@ -808,31 +1143,38 @@ what the loop's own measurements rank. and the test that pins it — is what makes that vocabulary auditable, and it is exactly what kpi: named-refusal-share would measure. Adopting the KPI routes through ask: kpi-set-change; building the registry does not, and the naming half of - kpi: no-third-mode is the loop's largest untested claim without it. -3. **gap: undocumented-boolean-comparison, and the unsigned class that belongs with it.** Same + kpi: no-third-mode is the loop's largest untested claim without it. Round 5 widens the case: + the vocabulary now includes a `FROM` allow-list, a node-level refusal, macro expansion and + three zoned sightings, and the round-6 review had to **rebuild the reading as a replica** + to audit it at all. +4. **gap: undocumented-boolean-comparison, and the unsigned class that belongs with it.** Same bookkeeping shape, both small, both against load-bearing claims: boolean comparison is undocumented, and the **unsigned-column** refusal — the reason three mined statements stopped matching, and the loop's clearest worked example of a refusal that is a correctness *gain* — is named nowhere in `known-limitations.md`. The cheapest items on this list, and the first two rows any registry would want. -4. **The fork: the oracle spec's ask: engine-fold-reading and ask: threads-and-value-order.** - Four rounds of enumeration have not terminated (enumeration-not-terminated above), and the - last two found shapes that carry **no name for a rule to read**. That is measured evidence - bearing on two questions that are stated and not ruled, and the tie branch's disposition - hangs on the answer: enumerate a fifth round, or pin the build-time fold's configuration - through the oracle so the answer is deterministic by construction. This report takes no - position. It states the fork and prices what the enumerating side has cost so far — three - iterations, four review rounds, **4-5x** static-only build time, and a branch that is gated - PASS and still not mergeable. -5. **The enforcement faults nobody owns.** The red Rust unit gate above, and finding: - c1-depth, untouched by this loop and still routed to ask: kpi-set-change. -6. **gap: bench-baseline-flip's cheapest cause is still untested.** One re-run after +5. **The fork: the oracle spec's ask: engine-fold-reading and ask: threads-and-value-order.** + Five rounds of enumeration have not terminated (enumeration-not-terminated above); rounds 4 + and 5 found shapes that carry **no name for a rule to read**, and round 6 found one whose + zoned type is chosen at bind time from a **string argument**, so neither a name nor a + declared type reaches it. That is measured evidence bearing on two questions that are stated + and not ruled, and the tie branch's disposition hangs on the answer: enumerate a sixth round, + or pin the build-time fold's configuration through the oracle so the answer is deterministic + by construction. This report takes no position. It states the fork and prices what the + enumerating side has cost so far — four iterations, five review rounds, **4-5x** static-only + build time, seven mined statements off the corpus floor, and a branch that is gated PASS and + still not mergeable. +6. **The enforcement faults nobody owns.** The red Rust unit gate above — eight iterations of + gate records and no owner — and finding: c1-depth, untouched by this loop and still routed + to ask: kpi-set-change. +7. **gap: bench-baseline-flip's cheapest cause is still untested.** One re-run after `--reinstall-package` rules out the stale-wheel signature (d). No iteration in this loop touched it, and kpi: bench-refresh-cadence should not be adopted before it is settled. -7. **Then the gap ledger, in whatever order ask: next-query-classes gets answered.** That +8. **Then the gap ledger, in whatever order ask: next-query-classes gets answered.** That question is the owner's and remains open; the loop has added no evidence that reorders its candidates, only evidence that gap: corpus-match-slip's ratchet half is real, that its - bookkeeping half is not, and that the ratchet now has one earned move on the record. + bookkeeping half is not, and that the ratchet now has **seven** earned moves on the record — + one statement at round 4 and six at round 5, each with its reason reproduced by a gate. **Reading N=2 replaces none of this.** This report is what moved between readings; the next full reading is what the numbers are. diff --git a/packages/confit/docs/reports/2026-09-06-loop-status.md b/packages/confit/docs/reports/2026-09-06-loop-status.md index dcef121d..34e2506b 100644 --- a/packages/confit/docs/reports/2026-09-06-loop-status.md +++ b/packages/confit/docs/reports/2026-09-06-loop-status.md @@ -1,37 +1,48 @@ -# Loop status, 2026-09-06: where the goal loop stands after eight iterations +# Loop status, 2026-09-06 (refreshed 2026-09-07): where the goal loop stands after eight iterations **What this is.** A dated status reading of the standing loop (`make confit behavior match -goal.md`), written on the owner's ask for "the current situation of the loop". It states -what has shipped, what is open, what the loop has learned about its own method, what it has -cost, and which decisions are the owner's. The narrative of iterations 1-7 lives in -`2026-09-02-loop-report-1.md` (amended today to 838 lines); this file is the snapshot, not -the story. Slugs resolve as in that report: `goal:` / `kpi:` / `exclusion:` / `ask:` in -`goal.md`, `finding:` and `gap:` in the baseline reading, `claim:` in the oracle spec. +goal.md`), written on the owner's ask for "the current situation of the loop" and refreshed at +the close of iteration 8. It states what has shipped, what is open, what the loop has learned +about its own method, what it has cost, and which decisions are the owner's. The narrative of +iterations 1-8 lives in `2026-09-02-loop-report-1.md`; this file is the snapshot, not the +story. Slugs resolve as in that report: `goal:` / `kpi:` / `exclusion:` / `ask:` in `goal.md`, +`finding:` and `gap:` in the baseline reading, `claim:` in the oracle spec. + +**Where the tree stands.** Master is `8796bb2`. The one live branch is +`refuse-static-tie-order` at `36ae02e`, gated PASS and **13 commits behind** that master. --- -## 1. Shipped, and one click away {#shipped} +## 1. Shipped {#shipped} | item | state | what it closed | |---|---|---| | PR #200 `fix-corpus-slip` | **merged** (master `f81e17c`) | the corpus floor (`MATCH_FLOOR`), and the finding that the 550 -> 547 slip was a correctness gain (three unsigned join columns had matched only because the replay ignored output type) | -| PR #202 `fix-nan-sign-varchar` | **approved by the owner**; merge blocked on one CI failure, fix in flight | finding: seed-1804 (`-nan` rendered `nan`); unary minus on DOUBLE is a real negation (`fneg`); the IR carries a NaN's sign; `nextafter` hands a NaN operand back as DuckDB does | - -**The CI failure on PR #202, and what it taught.** The last commit pinned which operand -`nextafter` returns when *both* are NaN, measured on Windows. Linux CI measured the other -operand. That choice is the platform compiler's, so the fix routes the kernel through the C -runtime's own `nextafter` (the function DuckDB's wheel calls), which matches by construction -on every platform, and leaves the both-NaN column compared against the machine's own oracle -and never pinned. The lesson joins the branch's doctrine: a sign a libm or a compiler chose -is compared, never pinned, and a local Windows gate is not a Linux gate. - -**kpi: engine-parity after the merge.** Seeds 0-1999: `DIVERGE_VALUE` 0 (master: 1), `AGREE` -1014 / `REFUSED` 944 / `AGREE_TRAP` 21 / `UNSHIPPED` 14 / `DIVERGE_OPT` 7. The seven -`DIVERGE_OPT` seeds are the optimizer-bracket set the goal excludes (exclusion: -optimizer-on-answers). The control reads clean on the accepted surface, with the campaign's -blind spots named rather than assumed: it never feeds a NaN into `nextafter`, its comparison -contract spells every NaN `nan` (claim: repr-equality), and it cannot see over-refusal outside -the two planted twins. +| PR #202 `fix-nan-sign-varchar` | **merged** 2026-09-06 on the owner's click; master `8796bb2`, rebased | finding: seed-1804 (`-nan` rendered `nan`); unary minus on DOUBLE is a real negation (`fneg`); the IR carries a NaN's sign; `nextafter` hands a NaN operand back as DuckDB does | + +**kpi: engine-parity, on master rather than on a branch.** Seeds 0-1999 at the final-tip gate: +`AGREE` **1014** / `REFUSED` **944** / `AGREE_TRAP` **21** / `UNSHIPPED` **14** / +`DIVERGE_OPT` **7** / `DIVERGE_VALUE` **0** — a histogram identical to the ship gate's, so +nothing the merge window added moved a verdict. Root suite **3326** passed / 1 skipped / 9 +xfailed / 2 errors (absent `pyspark`); `cargo test` on release and debug shows the five +pre-existing failures and no others. The seven `DIVERGE_OPT` seeds are the optimizer-bracket +set the goal excludes (exclusion: optimizer-on-answers). + +**The control reads clean on the accepted surface, with its blind spots named rather than +assumed.** The campaign never feeds a NaN into `nextafter`; its comparison contract spells +every NaN `nan` (claim: repr-equality); and it cannot see over-refusal outside the two planted +twins. + +**What the merge window cost and taught.** Between the ship gate and the merge the branch took +a design pass (one home per duplicated rule) and the orchestrator's own read of the whole diff. +That read found a **real parity bug** two gates had passed over: `duck_nextafter` answered +every NaN input with a fresh positive NaN where DuckDB returns a NaN operand with its own sign. +Its first fix pinned the both-NaN case as measured on Windows; Linux CI measured the other +operand, because that choice is the platform C runtime's. The kernel now calls the platform's +own C `nextafter`, so lone-NaN rows are pinned and both-NaN rows are compared against the +machine's own oracle and never pinned. Three standing lessons: a sign a libm or a compiler +chose is compared, never pinned; a pin measured on one platform is not a pin, and CI is the +cross-platform leg; a gate is evidence, not the review. --- @@ -41,71 +52,122 @@ the two planted twins. build and frozen, and what a whole-relation construct selects is frozen only when it is a function of the query text and the statics. -**Branch `refuse-static-tie-order`, tip `ebfbdb2`.** Four fix-and-review rounds have closed, -each gated PASS, in order: tie-producing `ORDER BY` (measured by DuckDB over the frozen +**Branch `refuse-static-tie-order`, tip `36ae02e`, gated PASS.** Five fix-and-review rounds have +closed, each gated PASS, in order: tie-producing `ORDER BY` (measured by DuckDB over the frozen result); every selection by position (`LIMIT`/`OFFSET`/`FETCH`/`SAMPLE`/`DISTINCT ON`/ -`QUALIFY`/row-position window functions); non-deterministic functions by DuckDB's own -stability flag, plus macros read through their definitions and four run-state names the -flag misses; order-dependent aggregates by DuckDB's own `SetOrderDependent`, with `sum` -read per overload through DuckDB's binder; row-counted window frames; `ORDER BY #N`; the -last-alias and OrderBinder-fallback name bindings; `POSITIONAL` and `ASOF` joins (all six -`JoinRefType` values decided, three serve); collated `min`/`max`; table functions by a -five-name allow-list; `rowid`; unreadable and multi-statement strings refused whole. +`QUALIFY`/row-position window functions); non-deterministic functions by DuckDB's own stability +flag, plus macros read through their definitions and four run-state names the flag misses; +order-dependent aggregates by DuckDB's own flag, with `sum` read per overload through DuckDB's +binder; row-counted window frames; `ORDER BY #N`; the last-alias and OrderBinder-fallback name +bindings; `POSITIONAL` and `ASOF` joins (all six `JoinRefType` values decided, three serve); +collated `min`/`max`; table functions by a five-name allow-list; `rowid`; unreadable and +multi-statement strings refused whole. + +**Iteration 8's closures, all seven confirmed by the gate at `36ae02e`.** Three of the five +rules are allow-lists or metadata reads rather than longer name lists. + +| # | shape | severity | how it escaped the reading | closure | +|---|---|---|---|---| +| 1 | `FROM 'file.csv'` (implicit file scan; also parquet, relative paths, globs) | high | a `BASE_TABLE` whose name is the path; no function name for the table-function rule to see | **closed in iteration 8**: every `BASE_TABLE` must name a static or a CTE (the `FROM` allow-list) | +| 2 | macros over order-dependent aggregates (`json_group_array`, `json_group_object`, `weighted_avg`, `geomean`) | high | the macro scan read bodies for stability only | **closed in iteration 8**: the aggregate, stability, clock and maker readings are fed from the parsed macro definitions | +| 3 | one-argument `age()` | high | reads the transaction clock under a `CONSISTENT` flag | **closed in iteration 8**: by arity, out of the statement's own parse | +| 4 | `SUMMARIZE` (and `DESCRIBE`/`SHOW`) | high | an opaque `SHOW_REF` node names none of the aggregates it runs | **closed in iteration 8**: any `SHOW_REF` refuses by node | +| 5 | any `TIMESTAMPTZ` rendered or decomposed | medium | no name anywhere; the build machine's `TimeZone` is read | **closed in iteration 8**: three sightings off DuckDB's own metadata — a static column's declared type, a `cast_type` node in the parse, a maker's catalogue return type | +| 6 | `SHOW TABLES` leaks the harness's `__arrow_s` | low | same `SHOW_REF` node | **closed in iteration 8** by the `SHOW_REF` rule and by the `FROM` allow-list | +| 7 | `SELECT *, a AS k, unnest(st) FROM s ORDER BY k` serves a tied `k` | high | a top-level `unnest(struct)` expands to columns, so an alias after a star is placed by counting from the wrong end | **closed in iteration 8** (`04f113a`), test-first | + +The displaced alias was the first fail-open found by reading the branch rather than by probing +it, and it is the one the campaign, four gates and four reviews all missed. -**Why it is not a merge candidate.** The review of the gated tip found six more fail-opens, -and the orchestrator's own read of the whole diff found a seventh: +**The new open set: what round six found at the gated tip.** Four shapes serve a value that is +not a function of the query; one is latent; one is an over-refusal wider than its disclosure. | # | shape | severity | how it escapes the reading | closure planned | |---|---|---|---|---| -| 1 | `FROM 'file.csv'` (implicit file scan; also parquet, relative paths, globs) | high | a `BASE_TABLE` whose name is the path; no function name for the table-function rule to see | every `BASE_TABLE` must name a static or a CTE (FROM allow-list) | -| 2 | macros over order-dependent aggregates (`json_group_array`, `json_group_object`, `weighted_avg`, `geomean`) | high | the macro scan reads bodies for stability only | classify a macro body exactly as a call: parse the definition, reuse the same name sets | -| 3 | one-argument `age()` | high | reads the transaction clock under a `CONSISTENT` flag | by arity, through `json_tree` (present in 1.5.5) | -| 4 | `SUMMARIZE` (and `DESCRIBE`/`SHOW`) | high | an opaque `SHOW_REF` node names none of the aggregates it runs | any `SHOW_REF` refuses by name | -| 5 | any `TIMESTAMPTZ` rendered or decomposed | medium | no name anywhere; the build machine's `TimeZone` is read | refuse zoned types by DuckDB's own metadata: static column types, cast and literal types in the parse, and maker functions from the catalogue | -| 6 | `SHOW TABLES` leaks the harness's `__arrow_s` | low | same `SHOW_REF` node | closed with 4 | -| 7 | `SELECT *, a AS k, unnest(st) FROM s ORDER BY k` serves a tied `k` | high | a top-level `unnest(struct)` expands to columns but is `FUNCTION`, not `STAR`, so an alias after a star is placed by counting from the wrong end | `SelectList.expands` (TDD, in progress, measured red) | - -Number 7 is the first fail-open found by reading the branch rather than by probing it, and -it is the one the campaign, four gates and four reviews all missed. - -**The enumeration has not terminated.** That sentence is the report's own section 4, and -nothing in iteration 8 changes it: each round closes everything the last found and the next -round finds more, and the newest shapes carry no name for a rule to read. The structural -alternative, pinning the build-time fold's configuration through the oracle so the answer is -deterministic by construction, is stated in the report as a fork against the oracle spec's -ask: engine-fold-reading and ask: threads-and-value-order. It is the owner's call, and the -loop proceeds under the goal as written until it is made. What iteration 8 does change is the -*shape* of the closures: three of the five are allow-lists or metadata reads (what serves, -DuckDB's own catalogue and parse), not more names. +| 1 | a `TIMESTAMPTZ` typed at bind time from a string argument: `strptime`/`try_strptime` with `%z`, `json_transform`/`from_json` and their `_strict` forms with a zoned structure string | high | the catalogue's return type is naive or `ANY`, the parse carries no cast node, and no static column is involved, so all three zoned sightings miss it | read the folded statement's own result types rather than the declarations around it | +| 2 | a builtin macro whose body calls another builtin macro (`geometric_mean` -> `geomean`, `wavg` -> `weighted_avg`, `json_group_structure` -> `json_group_array`) | high | expansion stops at one level, and the inner name is itself a macro | expand to a fixed point rather than one level | +| 3 | a CTE declared in any subquery whitelists its bare name for an outer `FROM` | high | CTE names are gathered by recursive descent over the whole parse, so lexical scope is not modelled | gather CTE names per scope, not per statement | +| 4 | a static whose name equals a catalogue view's last path segment whitelists the qualified read | medium | the membership test drops the schema and catalog qualifiers the query wrote | compare the qualified name | +| 5 | the `age` arity reading never reaches a macro body, and its arm reports the inner name | low | `tree` is fed from the statement's parse while the name reads are fed from macro bodies too | feed `tree` from the macro parses and report the outer name | +| 6 | **over-refusal**: one zoned column in any static refuses every query on that build, `SELECT 1 AS o` included | medium | the static-column sighting reads the caller's statics with no reference to the statement, above the other arms | sight the column where the statement reaches it, or restate the disclosure as a per-build switch | + +Nothing escapes through the `age` arity gap today: all 131 catalogue macro definitions were +enumerated and none calls `age` or a zoned maker. The review also returned six design findings (a positional +array whose position-to-meaning binding lives in three unchecked places; an unmarked odd column +in the arity arm; an over-claiming macro paragraph beside an unpinned maker list; a paragraph +that argues where a measured fact is available; four comments carrying two different counts; +and one clean: zero ticket or PR references and zero dates across the whole diff). + +**How round six was measured, which is why its count carries weight.** The reviewer rebuilt the +branch's shape, exact-sum, ordering-word and refusal readings as a Python replica, mined 162 +SQL-shaped literals from the branch's own test file, and ran the 121 runnable ones against the +built branch across five static-table shapes: **121/121 agreement**. Every finding is confirmed +end to end with a refusing control on identical data. A sweep of all **1343 `CONSISTENT` +catalogue scalars** across seven environments x two working directories found 24 that answer +more than one way; the fold refuses **22**, the two exceptions being the bind-time zoned pair. + +**The gate's numbers at `36ae02e`.** Suite: branch **3435** passed / 1 skipped / 3 xfailed / 2 +errors over 3441 ids against master's 3326 / 1 / 9 / 2 over 3338, with **3286 shared ids and +zero outcome changes**, 155 branch-only ids all passing, and no test deleted or renamed; a +separately built debug engine gives the identical ids and outcomes. `cargo test --release +--lib`: master 269 / 5, branch 266 / 5, the same five pre-existing names on both sides. Public +API diff empty. Campaign seeds 0-1999: `AGREE` **1007** / `REFUSED` **951** / `AGREE_TRAP` +**20** / `UNSHIPPED` 14 / `DIVERGE_OPT` 7 / `DIVERGE_VALUE` **1** — **zero flips against the +branch's own previous tip**, and of the 81-seed delta against master the single +`DIVERGE_VALUE` is **master's**, the NaN-sign work this branch does not yet carry. Corpus +**540 / 138 / 0** against master's 547 / 131 / 0, seven statements moved with a reproduced +reason each and zero FAIL. Mutation: five rules reverted one at a time, 2 / 16 / 4 / 37 / 16 +red, each restored by re-edit. **48/48** hand probes, 29 must-serve on backend `constant` and +19 must-refuse-by-name. + +**Why it is still not a merge candidate.** Two reasons, and the second is new. Four of round +six's shapes answer a query wrongly, which is the control the branch exists to close. And the +branch is 13 commits behind master, so a rebase onto `8796bb2` and a re-gate are a +precondition, not a tidy-up — the gate proved the two lines have already met. + +**The enumeration has not terminated.** That is the report's own section, and round six adds to +the sequence rather than ending it: four of its five shapes serve wrongly, so the count of +rounds that came back with nothing wrongly served is still zero. The structural alternative, +pinning the build-time fold's configuration through the oracle so the answer is deterministic by +construction, is stated in the report as a fork against ask: engine-fold-reading and +ask: threads-and-value-order. It is the owner's call, and the loop proceeds under the goal as +written until it is made. **The price already paid, disclosed.** 64 of DuckDB's 88 aggregate names refuse on the -static-only path by DuckDB's flag (including `bit_and`, `histogram`, `count_if` and the -compensated sums that exist to be order-stable); a collation anywhere takes `min`/`max` off -the served list; every table function outside five names refuses; the fold costs 4-5x more -build time; the corpus floor moved 547 -> 546 for one statement whose own answer is fixed (the -allow-list's price, not a skew). The campaign cannot see over-refusal in this class, so these -are pinned by unit tests instead. +static-only path by DuckDB's flag (including the compensated sums that exist to be +order-stable); a collation anywhere takes `min`/`max` off the served list; every table function +outside five names refuses, and now every `BASE_TABLE` that is not a static or a CTE; +`TIME WITH TIME ZONE` is sighted with the zoned class although it renders without the session +zone; the fold costs 4-5x more build time; the corpus floor has moved **547 -> 546 -> 540**. +Five of those six new statements are the same `SELECT COUNT(*) FROM t`, whose constant the build +produced with **zero** statics in hand because the replay's own caller frame carried a pyarrow +table of that name — a fact about the replay harness as much as about the engine. The campaign +cannot see over-refusal in this class, so all of it is pinned by unit tests instead. --- ## 3. What the loop learned about itself {#method} -- **The orchestrator reads every diff.** The owner's correction of 2026-09-06 ("you must - review the model outputs"). Applied since to both live branches: it produced findings 7 - above and the `nextafter` parity bug, four doctrine slips in `goal.md` (a today-state - sentence and a mechanism paragraph in the target document, a serving example under a - `REFUSES` label, a corpus comment arguing a skew for a statement that has none), and one +- **The orchestrator reads every diff.** The owner's correction of 2026-09-06 ("you must review + the model outputs"). It has now paid twice: the `nextafter` parity bug on a branch two gates + had passed, and fail-open 7 on the tie branch, plus four doctrine slips in `goal.md` and one over-claiming doc comment. Gates are evidence; they are not the review. - **Findings are fixed before a PR is presented.** PR #202 went back to draft for the design - pass and the review fixes; the tie branch stays in draft until all seven close and the diff - is re-read. + pass and the review fixes before it merged; the tie branch stays in draft until round six's + set closes and the diff is re-read. - **A pin measured on one platform is not a pin.** The both-NaN `nextafter` case above. -- **Reviewers apply the design lens by name.** The `fix-nan-sign-varchar` design pass gave - each duplicated rule one home (`fold_operand`, `out_of_range_trap`, the NaN-sign argument at - `Lit`, one `inf`/`nan` token path); the structural item still open is that node classifiers - are hand-enumerated across five files (a node should own its own properties). +- **Rebase before gating for merge.** The tie branch's gate measured a tree 13 commits behind + master and had to attribute one campaign flip to master to stay honest. A gate that is not on + the merge result is a gate on something else. +- **Allow-lists and metadata reads beat name lists.** Three of iteration 8's five closures read + DuckDB's own catalogue and parse or enumerate what *serves*; each covers a class, and the two + round-six findings against them are defects in the reading (scope, qualification) rather than + new shapes. +- **Reviewers apply the design lens by name.** The `fix-nan-sign-varchar` design pass gave each + duplicated rule one home; the tie branch's standing structural item is a positional array + where a named struct belongs. - **Third-party defect descriptions stay out of the tree** until the owner has seen them; the - branch states measured facts and our consequence only. + branches state measured facts and our consequence only. --- @@ -114,35 +176,41 @@ are pinned by unit tests instead. | span | agent tokens (approx.) | |---|---| | iterations 1-7 (workflows) | ~9.0M+ | -| this session's own review, fixes and gates (orchestrator-driven, not a workflow) | ~0.4M in subagents, plus the orchestrator's own context | +| iteration 8: the merge window's design pass and diff read, the tie branch's round 5, its gate, its review | ~0.4M in subagents on 2026-09-06, plus iteration 8's own workflow legs, not closed as this is written | -The stop rule stands at roughly 70% of the owner's weekly credit, owner-signalled; the loop -cannot read that meter and has not been given a percentage. `C:` reached 100% once during -iteration 6; 35 finished workflow worktrees were removed and a sweep between iterations is -now part of the routine. +The orchestrator's own context is in none of these figures, and it is the leg that found the +two items no gate did. The stop rule stands at roughly 70% of the owner's weekly credit, +owner-signalled; the loop cannot read that meter and has not been given a percentage. It has +not been signalled to stop; it was paused once, when PR #202 merged, and restarted for +iteration 8. `C:` reached 100% once during iteration 6; 35 finished workflow worktrees were +removed and a sweep between iterations is now part of the routine. --- ## 5. Decisions that are the owner's {#decisions} 1. **The fork**: enumerate a sixth round, or pin the build-time fold's configuration through - the oracle (ask: engine-fold-reading, ask: threads-and-value-order). Evidence: four - rounds, seven still open, the last three shapes nameless. + the oracle (ask: engine-fold-reading, ask: threads-and-value-order). Evidence: five rounds, + none empty, four shapes still open, and the newest one carries a type chosen at bind time + from a string argument, which neither a name nor a declared type can reach. 2. **The four goal asks** still open on master: acceptance-target, next-query-classes, exclusion-ratification, kpi-set-change. -3. **The static-only acceptance price**: 64/88 aggregate names, collations, table functions, - 4-5x build time. The policy (never trade a control for a drive) says take it; the size is - measured, so it can be priced rather than assumed. +3. **The static-only acceptance price**: 64/88 aggregate names, collations, table functions, the + `FROM` allow-list, `TIMETZ`, 4-5x build time, seven mined statements off the corpus floor. + The policy (never trade a control for a drive) says take it; the size is measured, so it can + be priced rather than assumed. 4. **The weekly percentage**, whenever the stop rule should bite. --- ## 6. Next, in the goal's order {#next} -1. Land PR #202 once CI is green (the C-runtime `nextafter` commit). -2. Iteration 8 on the tie branch: close the seven fail-opens by the closures in the table, - re-gate, re-read the whole diff, amend the report, open the PR for the click. -3. Open the report PR (`loop-report-1`: the iterations 1-7 narrative plus this status file). +1. Rebase the tie branch onto `8796bb2` and re-gate; its current numbers describe a tree that + exists nowhere else. +2. Iteration 9 on the tie branch: close round six's four serving shapes by the closures in the + table, decide the over-refusal (fix or restate), re-gate, re-read the whole diff. +3. Open the report PR (`loop-report-1`: the iterations 1-8 narrative plus this status file). 4. Then, unchanged from the report's queue: the refusal registry behind kpi: named-refusal-share, gap: undocumented-boolean-comparison with the unsigned class, the - enforcement faults (the red Rust unit gate CI cannot see), and the bench baseline. + enforcement faults (the red Rust unit gate CI cannot see, eight iterations of records and no + owner), and the bench baseline. From 2c9c98e8d73e5d2bcf7d256d363a62a1b81461d2 Mon Sep 17 00:00:00 2001 From: AmirHossein Roozbahani Date: Mon, 7 Sep 2026 22:03:12 +0200 Subject: [PATCH 6/8] the report states the clock measurements, not a reading of another project's source Two sentences described DuckDB's registration code for age and the ICU clocks; the house rule keeps a third party's internals out of the tree and states the measured fact, which is what they say now. Co-Authored-By: Claude Fable 5.1 --- .../docs/reports/2026-09-02-loop-report-1.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/confit/docs/reports/2026-09-02-loop-report-1.md b/packages/confit/docs/reports/2026-09-02-loop-report-1.md index 530b77e9..a52b2e8c 100644 --- a/packages/confit/docs/reports/2026-09-02-loop-report-1.md +++ b/packages/confit/docs/reports/2026-09-02-loop-report-1.md @@ -378,10 +378,9 @@ read the clock (`stability` is `NULL` for all 131 scalar-macro rows, so nine clo served a frozen value), the catalogue being read before the query binds, `rowid` projecting a physical scan position as an ordinary value, and the class the review named as `stability` answering "constant within one query" rather than "a function of the query text". That last -one was **worse than reported**, and the fix round found the extra case in the pinned source -rather than by probing: `current_localtimestamp` is registered in -`extension/icu/icu-timezone.cpp` with no `SetStability`, so it inherits `CONSISTENT` — and -its value **moved between two connections 50 ms apart**, measured. The bare words +one was **worse than reported**, and the fix round found the extra case by reading rather than +by probing: `current_localtimestamp`'s catalogue row says `CONSISTENT`, and its value +**moved between two connections 50 ms apart**, measured. The bare words `localtime` / `localtimestamp` were already refused; the call spelling of the same function was not. @@ -548,10 +547,9 @@ an order-dependent aggregate serves, because the aggregate scan matches `function_type = 'aggregate'` and a macro is `'macro'`: `json_group_array`, `json_group_object`, `weighted_avg` and `geomean` leak at 2, 2, 4 and 7 distinct answers across settings, each wrapping an aggregate that refuses when spelled directly. One-argument -`age(TIMESTAMP)` reads the transaction clock, but DuckDB's catalogue calls it `CONSISTENT`, so -it passes every stability rule — `AgeFunctionStandard` takes -`MetaTransaction::Get(...).start_timestamp` and never calls `SetStability`, read in the pinned -source, unlike `now()` — and two builds a day apart freeze two different constants. And +`age(TIMESTAMP)` answers with the transaction clock (measured: it moves across transactions and +time zones while `age(a, b)` does not) and its catalogue row says `CONSISTENT`, so it passes +every stability rule, and two builds a day apart freeze two different constants. And `SUMMARIZE` serializes as an opaque `SHOW_REF` node that names none of the `avg` / `stddev` / `approx_quantile` aggregates it actually runs, so no value rule reaches them: seven settings, **seven** distinct answers. A medium and a low ride with them — **any `TIMESTAMPTZ` rendered From 39dc91c7db1ea09dd057e5f1e6f88fb9091fab92 Mon Sep 17 00:00:00 2001 From: AmirHossein Roozbahani Date: Mon, 7 Sep 2026 22:11:08 +0200 Subject: [PATCH 7/8] the status reading records the rebased tie tip and its gate Co-Authored-By: Claude Fable 5.1 --- .../docs/reports/2026-09-06-loop-status.md | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/confit/docs/reports/2026-09-06-loop-status.md b/packages/confit/docs/reports/2026-09-06-loop-status.md index 34e2506b..5e839aa2 100644 --- a/packages/confit/docs/reports/2026-09-06-loop-status.md +++ b/packages/confit/docs/reports/2026-09-06-loop-status.md @@ -9,7 +9,8 @@ story. Slugs resolve as in that report: `goal:` / `kpi:` / `exclusion:` / `ask:` `finding:` and `gap:` in the baseline reading, `claim:` in the oracle spec. **Where the tree stands.** Master is `8796bb2`. The one live branch is -`refuse-static-tie-order` at `36ae02e`, gated PASS and **13 commits behind** that master. +`refuse-static-tie-order` at `e6a3cd8`: the gated `36ae02e` rebased onto that master (eleven +commits, no conflicts), with its clock rules restated as measurements, and re-gated there. --- @@ -52,7 +53,7 @@ cross-platform leg; a gate is evidence, not the review. build and frozen, and what a whole-relation construct selects is frozen only when it is a function of the query text and the statics. -**Branch `refuse-static-tie-order`, tip `36ae02e`, gated PASS.** Five fix-and-review rounds have +**Branch `refuse-static-tie-order`, tip `e6a3cd8`, gated PASS on master.** Five fix-and-review rounds have closed, each gated PASS, in order: tie-producing `ORDER BY` (measured by DuckDB over the frozen result); every selection by position (`LIMIT`/`OFFSET`/`FETCH`/`SAMPLE`/`DISTINCT ON`/ `QUALIFY`/row-position window functions); non-deterministic functions by DuckDB's own stability @@ -120,10 +121,19 @@ reason each and zero FAIL. Mutation: five rules reverted one at a time, 2 / 16 / red, each restored by re-edit. **48/48** hand probes, 29 must-serve on backend `constant` and 19 must-refuse-by-name. -**Why it is still not a merge candidate.** Two reasons, and the second is new. Four of round -six's shapes answer a query wrongly, which is the control the branch exists to close. And the -branch is 13 commits behind master, so a rebase onto `8796bb2` and a re-gate are a -precondition, not a tidy-up — the gate proved the two lines have already met. +**The re-gate on the rebased tip `e6a3cd8`, taken by the orchestrator.** Root suite **3480** +passed / 1 skipped / 9 xfailed / 2 errors (absent `pyspark`): master's 3338 collected ids are +all present with their outcomes, and the branch adds exactly **154** (150 in the static-only +test file, 4 in the fuzz smoke file), so the count is master's 3326 plus those. `cargo test +--release --lib` 269 / 5, the five pre-existing names. Corpus **540** holds. Campaign seeds +0-1999: `AGREE` **1008** / `REFUSED` **951** / `AGREE_TRAP` **20** / `UNSHIPPED` 14 / +`DIVERGE_OPT` 7 / `DIVERGE_VALUE` **0** — the seven findings are master's own optimizer-bracket +seeds, and the single seed that moved against the pre-rebase gate is 1804, now `AGREE` because +the rebased tree carries master's NaN-sign fix. + +**Why it is still not a merge candidate.** Four of round six's shapes answer a query wrongly, +which is the control the branch exists to close; the rebase precondition the gate named is +met. **The enumeration has not terminated.** That is the report's own section, and round six adds to the sequence rather than ending it: four of its five shapes serve wrongly, so the count of @@ -205,9 +215,9 @@ removed and a sweep between iterations is now part of the routine. ## 6. Next, in the goal's order {#next} -1. Rebase the tie branch onto `8796bb2` and re-gate; its current numbers describe a tree that - exists nowhere else. -2. Iteration 9 on the tie branch: close round six's four serving shapes by the closures in the +1. The owner's answer to the fork (the RFC put to him at the close of iteration 8): it + decides whether iteration 9 enumerates round six's shapes or pins the fold. +2. Iteration 9 on the tie branch, under whichever answer: close round six's four serving shapes by the closures in the table, decide the over-refusal (fix or restate), re-gate, re-read the whole diff. 3. Open the report PR (`loop-report-1`: the iterations 1-8 narrative plus this status file). 4. Then, unchanged from the report's queue: the refusal registry behind kpi: From 962ca9b8358ab5890f70409e50ec7012dd7bb764 Mon Sep 17 00:00:00 2001 From: AmirHossein Roozbahani Date: Mon, 7 Sep 2026 22:44:12 +0200 Subject: [PATCH 8/8] the status reading leads with lift, marks the crossed control, and shows each change as code Every behavior change is the constructor call with master's answer and the branch's, executed today; the drives carry their before, after and delta; the one control crossed on master heads the page in its own block, and the restored one is noted, not headlined. Co-Authored-By: Claude Fable 5.1 --- .../docs/reports/2026-09-06-loop-status.md | 425 +++++++++--------- 1 file changed, 209 insertions(+), 216 deletions(-) diff --git a/packages/confit/docs/reports/2026-09-06-loop-status.md b/packages/confit/docs/reports/2026-09-06-loop-status.md index 5e839aa2..615be7e0 100644 --- a/packages/confit/docs/reports/2026-09-06-loop-status.md +++ b/packages/confit/docs/reports/2026-09-06-loop-status.md @@ -1,226 +1,219 @@ -# Loop status, 2026-09-06 (refreshed 2026-09-07): where the goal loop stands after eight iterations +# Loop status, 2026-09-07: what moved, what is crossed, what changed **What this is.** A dated status reading of the standing loop (`make confit behavior match -goal.md`), written on the owner's ask for "the current situation of the loop" and refreshed at -the close of iteration 8. It states what has shipped, what is open, what the loop has learned -about its own method, what it has cost, and which decisions are the owner's. The narrative of -iterations 1-8 lives in `2026-09-02-loop-report-1.md`; this file is the snapshot, not the -story. Slugs resolve as in that report: `goal:` / `kpi:` / `exclusion:` / `ask:` in `goal.md`, -`finding:` and `gap:` in the baseline reading, `claim:` in the oracle spec. - -**Where the tree stands.** Master is `8796bb2`. The one live branch is -`refuse-static-tie-order` at `e6a3cd8`: the gated `36ae02e` rebased onto that master (eleven -commits, no conflicts), with its clock rules restated as measurements, and re-gated there. - ---- - -## 1. Shipped {#shipped} - -| item | state | what it closed | -|---|---|---| -| PR #200 `fix-corpus-slip` | **merged** (master `f81e17c`) | the corpus floor (`MATCH_FLOOR`), and the finding that the 550 -> 547 slip was a correctness gain (three unsigned join columns had matched only because the replay ignored output type) | -| PR #202 `fix-nan-sign-varchar` | **merged** 2026-09-06 on the owner's click; master `8796bb2`, rebased | finding: seed-1804 (`-nan` rendered `nan`); unary minus on DOUBLE is a real negation (`fneg`); the IR carries a NaN's sign; `nextafter` hands a NaN operand back as DuckDB does | - -**kpi: engine-parity, on master rather than on a branch.** Seeds 0-1999 at the final-tip gate: -`AGREE` **1014** / `REFUSED` **944** / `AGREE_TRAP` **21** / `UNSHIPPED` **14** / -`DIVERGE_OPT` **7** / `DIVERGE_VALUE` **0** — a histogram identical to the ship gate's, so -nothing the merge window added moved a verdict. Root suite **3326** passed / 1 skipped / 9 -xfailed / 2 errors (absent `pyspark`); `cargo test` on release and debug shows the five -pre-existing failures and no others. The seven `DIVERGE_OPT` seeds are the optimizer-bracket -set the goal excludes (exclusion: optimizer-on-answers). - -**The control reads clean on the accepted surface, with its blind spots named rather than -assumed.** The campaign never feeds a NaN into `nextafter`; its comparison contract spells -every NaN `nan` (claim: repr-equality); and it cannot see over-refusal outside the two planted -twins. - -**What the merge window cost and taught.** Between the ship gate and the merge the branch took -a design pass (one home per duplicated rule) and the orchestrator's own read of the whole diff. -That read found a **real parity bug** two gates had passed over: `duck_nextafter` answered -every NaN input with a fresh positive NaN where DuckDB returns a NaN operand with its own sign. -Its first fix pinned the both-NaN case as measured on Windows; Linux CI measured the other -operand, because that choice is the platform C runtime's. The kernel now calls the platform's -own C `nextafter`, so lone-NaN rows are pinned and both-NaN rows are compared against the -machine's own oracle and never pinned. Three standing lessons: a sign a libm or a compiler -chose is compared, never pinned; a pin measured on one platform is not a pin, and CI is the -cross-platform leg; a gate is evidence, not the review. +goal.md`), refreshed at the close of iteration 8. Drives first with their lift, then any +control that is crossed, then every behavior change as executed code (each block was run on +2026-09-07 against master's engine at `8796bb2` and the tie branch's at `e6a3cd8`), then the +mechanism, the cost and the decisions. The narrative of iterations 1-8 is in +`2026-09-02-loop-report-1.md`. Slugs resolve as there. --- -## 2. Open: the static-only carve-out {#static-only} - -**Target.** exclusion: whole-relation-shapes: a static-tables-only query is folded once at -build and frozen, and what a whole-relation construct selects is frozen only when it is a -function of the query text and the statics. - -**Branch `refuse-static-tie-order`, tip `e6a3cd8`, gated PASS on master.** Five fix-and-review rounds have -closed, each gated PASS, in order: tie-producing `ORDER BY` (measured by DuckDB over the frozen -result); every selection by position (`LIMIT`/`OFFSET`/`FETCH`/`SAMPLE`/`DISTINCT ON`/ -`QUALIFY`/row-position window functions); non-deterministic functions by DuckDB's own stability -flag, plus macros read through their definitions and four run-state names the flag misses; -order-dependent aggregates by DuckDB's own flag, with `sum` read per overload through DuckDB's -binder; row-counted window frames; `ORDER BY #N`; the last-alias and OrderBinder-fallback name -bindings; `POSITIONAL` and `ASOF` joins (all six `JoinRefType` values decided, three serve); -collated `min`/`max`; table functions by a five-name allow-list; `rowid`; unreadable and -multi-statement strings refused whole. +## 1. Controls: one crossed, one restored {#controls} -**Iteration 8's closures, all seven confirmed by the gate at `36ae02e`.** Three of the five -rules are allow-lists or metadata reads rather than longer name lists. +> **CROSSED on master: a frozen fold that is not a function of the query.** exclusion: +> whole-relation-shapes says a static-tables-only query may be frozen only when what it +> selects is a function of the query text and the statics. Master's engine freezes and serves +> eighteen families of shapes that are not: a tie order, a file on the build machine's disk, +> a clock, the session time zone, the arrival order of a floating-point sum. Every block in +> the code section below marked *master* is that control being crossed today. The tie branch +> (`refuse-static-tie-order`, gated, unmerged) closes all eighteen; its own sixth review then +> found **four more** it does not, so the control stays crossed on the branch too until they +> close. -| # | shape | severity | how it escaped the reading | closure | -|---|---|---|---|---| -| 1 | `FROM 'file.csv'` (implicit file scan; also parquet, relative paths, globs) | high | a `BASE_TABLE` whose name is the path; no function name for the table-function rule to see | **closed in iteration 8**: every `BASE_TABLE` must name a static or a CTE (the `FROM` allow-list) | -| 2 | macros over order-dependent aggregates (`json_group_array`, `json_group_object`, `weighted_avg`, `geomean`) | high | the macro scan read bodies for stability only | **closed in iteration 8**: the aggregate, stability, clock and maker readings are fed from the parsed macro definitions | -| 3 | one-argument `age()` | high | reads the transaction clock under a `CONSISTENT` flag | **closed in iteration 8**: by arity, out of the statement's own parse | -| 4 | `SUMMARIZE` (and `DESCRIBE`/`SHOW`) | high | an opaque `SHOW_REF` node names none of the aggregates it runs | **closed in iteration 8**: any `SHOW_REF` refuses by node | -| 5 | any `TIMESTAMPTZ` rendered or decomposed | medium | no name anywhere; the build machine's `TimeZone` is read | **closed in iteration 8**: three sightings off DuckDB's own metadata — a static column's declared type, a `cast_type` node in the parse, a maker's catalogue return type | -| 6 | `SHOW TABLES` leaks the harness's `__arrow_s` | low | same `SHOW_REF` node | **closed in iteration 8** by the `SHOW_REF` rule and by the `FROM` allow-list | -| 7 | `SELECT *, a AS k, unnest(st) FROM s ORDER BY k` serves a tied `k` | high | a top-level `unnest(struct)` expands to columns, so an alias after a star is placed by counting from the wrong end | **closed in iteration 8** (`04f113a`), test-first | - -The displaced alias was the first fail-open found by reading the branch rather than by probing -it, and it is the one the campaign, four gates and four reviews all missed. +**Restored on master (2026-09-06): kpi: engine-parity.** At the loop's start one of 2000 +campaign seeds served a value the oracle did not (seed 1804, `nan` for `-nan`). PR #202 closed +it; master reads `DIVERGE_VALUE` **0** of 2000. Not a headline: a control at its bound is the +expected state. -**The new open set: what round six found at the gated tip.** Four shapes serve a value that is -not a function of the query; one is latent; one is an over-refusal wider than its disclosure. +## 2. Drives, with lift {#drives} -| # | shape | severity | how it escapes the reading | closure planned | +| drive | master (`8796bb2`) | tie branch (`e6a3cd8`) | lift | why | |---|---|---|---|---| -| 1 | a `TIMESTAMPTZ` typed at bind time from a string argument: `strptime`/`try_strptime` with `%z`, `json_transform`/`from_json` and their `_strict` forms with a zoned structure string | high | the catalogue's return type is naive or `ANY`, the parse carries no cast node, and no static column is involved, so all three zoned sightings miss it | read the folded statement's own result types rather than the declarations around it | -| 2 | a builtin macro whose body calls another builtin macro (`geometric_mean` -> `geomean`, `wavg` -> `weighted_avg`, `json_group_structure` -> `json_group_array`) | high | expansion stops at one level, and the inner name is itself a macro | expand to a fixed point rather than one level | -| 3 | a CTE declared in any subquery whitelists its bare name for an outer `FROM` | high | CTE names are gathered by recursive descent over the whole parse, so lexical scope is not modelled | gather CTE names per scope, not per statement | -| 4 | a static whose name equals a catalogue view's last path segment whitelists the qualified read | medium | the membership test drops the schema and catalog qualifiers the query wrote | compare the qualified name | -| 5 | the `age` arity reading never reaches a macro body, and its arm reports the inner name | low | `tree` is fed from the statement's parse while the name reads are fed from macro bodies too | feed `tree` from the macro parses and report the outer name | -| 6 | **over-refusal**: one zoned column in any static refuses every query on that build, `SELECT 1 AS o` included | medium | the static-column sighting reads the caller's statics with no reference to the statement, above the other arms | sight the column where the statement reaches it, or restate the disclosure as a per-build switch | - -Nothing escapes through the `age` arity gap today: all 131 catalogue macro definitions were -enumerated and none calls `age` or a zoned maker. The review also returned six design findings (a positional -array whose position-to-meaning binding lives in three unchecked places; an unmarked odd column -in the arity arm; an over-claiming macro paragraph beside an unpinned maker list; a paragraph -that argues where a measured fact is available; four comments carrying two different counts; -and one clean: zero ticket or PR references and zero dates across the whole diff). - -**How round six was measured, which is why its count carries weight.** The reviewer rebuilt the -branch's shape, exact-sum, ordering-word and refusal readings as a Python replica, mined 162 -SQL-shaped literals from the branch's own test file, and ran the 121 runnable ones against the -built branch across five static-table shapes: **121/121 agreement**. Every finding is confirmed -end to end with a refusing control on identical data. A sweep of all **1343 `CONSISTENT` -catalogue scalars** across seven environments x two working directories found 24 that answer -more than one way; the fold refuses **22**, the two exceptions being the bind-time zoned pair. - -**The gate's numbers at `36ae02e`.** Suite: branch **3435** passed / 1 skipped / 3 xfailed / 2 -errors over 3441 ids against master's 3326 / 1 / 9 / 2 over 3338, with **3286 shared ids and -zero outcome changes**, 155 branch-only ids all passing, and no test deleted or renamed; a -separately built debug engine gives the identical ids and outcomes. `cargo test --release ---lib`: master 269 / 5, branch 266 / 5, the same five pre-existing names on both sides. Public -API diff empty. Campaign seeds 0-1999: `AGREE` **1007** / `REFUSED` **951** / `AGREE_TRAP` -**20** / `UNSHIPPED` 14 / `DIVERGE_OPT` 7 / `DIVERGE_VALUE` **1** — **zero flips against the -branch's own previous tip**, and of the 81-seed delta against master the single -`DIVERGE_VALUE` is **master's**, the NaN-sign work this branch does not yet carry. Corpus -**540 / 138 / 0** against master's 547 / 131 / 0, seven statements moved with a reproduced -reason each and zero FAIL. Mutation: five rules reverted one at a time, 2 / 16 / 4 / 37 / 16 -red, each restored by re-edit. **48/48** hand probes, 29 must-serve on backend `constant` and -19 must-refuse-by-name. - -**The re-gate on the rebased tip `e6a3cd8`, taken by the orchestrator.** Root suite **3480** -passed / 1 skipped / 9 xfailed / 2 errors (absent `pyspark`): master's 3338 collected ids are -all present with their outcomes, and the branch adds exactly **154** (150 in the static-only -test file, 4 in the fuzz smoke file), so the count is master's 3326 plus those. `cargo test ---release --lib` 269 / 5, the five pre-existing names. Corpus **540** holds. Campaign seeds -0-1999: `AGREE` **1008** / `REFUSED` **951** / `AGREE_TRAP` **20** / `UNSHIPPED` 14 / -`DIVERGE_OPT` 7 / `DIVERGE_VALUE` **0** — the seven findings are master's own optimizer-bracket -seeds, and the single seed that moved against the pre-rebase gate is 1804, now `AGREE` because -the rebased tree carries master's NaN-sign fix. - -**Why it is still not a merge candidate.** Four of round six's shapes answer a query wrongly, -which is the control the branch exists to close; the rebase precondition the gate named is -met. - -**The enumeration has not terminated.** That is the report's own section, and round six adds to -the sequence rather than ending it: four of its five shapes serve wrongly, so the count of -rounds that came back with nothing wrongly served is still zero. The structural alternative, -pinning the build-time fold's configuration through the oracle so the answer is deterministic by -construction, is stated in the report as a fork against ask: engine-fold-reading and -ask: threads-and-value-order. It is the owner's call, and the loop proceeds under the goal as -written until it is made. - -**The price already paid, disclosed.** 64 of DuckDB's 88 aggregate names refuse on the -static-only path by DuckDB's flag (including the compensated sums that exist to be -order-stable); a collation anywhere takes `min`/`max` off the served list; every table function -outside five names refuses, and now every `BASE_TABLE` that is not a static or a CTE; -`TIME WITH TIME ZONE` is sighted with the zoned class although it renders without the session -zone; the fold costs 4-5x more build time; the corpus floor has moved **547 -> 546 -> 540**. -Five of those six new statements are the same `SELECT COUNT(*) FROM t`, whose constant the build -produced with **zero** statics in hand because the replay's own caller frame carried a pyarrow -table of that name — a fact about the replay harness as much as about the engine. The campaign -cannot see over-refusal in this class, so all of it is pinned by unit tests instead. - ---- - -## 3. What the loop learned about itself {#method} - -- **The orchestrator reads every diff.** The owner's correction of 2026-09-06 ("you must review - the model outputs"). It has now paid twice: the `nextafter` parity bug on a branch two gates - had passed, and fail-open 7 on the tie branch, plus four doctrine slips in `goal.md` and one - over-claiming doc comment. Gates are evidence; they are not the review. -- **Findings are fixed before a PR is presented.** PR #202 went back to draft for the design - pass and the review fixes before it merged; the tie branch stays in draft until round six's - set closes and the diff is re-read. -- **A pin measured on one platform is not a pin.** The both-NaN `nextafter` case above. -- **Rebase before gating for merge.** The tie branch's gate measured a tree 13 commits behind - master and had to attribute one campaign flip to master to stay honest. A gate that is not on - the merge result is a gate on something else. -- **Allow-lists and metadata reads beat name lists.** Three of iteration 8's five closures read - DuckDB's own catalogue and parse or enumerate what *serves*; each covers a class, and the two - round-six findings against them are defects in the reading (scope, qualification) rather than - new shapes. -- **Reviewers apply the design lens by name.** The `fix-nan-sign-varchar` design pass gave each - duplicated rule one home; the tie branch's standing structural item is a positional array - where a named struct belongs. -- **Third-party defect descriptions stay out of the tree** until the owner has seen them; the - branches state measured facts and our consequence only. - ---- - -## 4. Spend and operations {#spend} - -| span | agent tokens (approx.) | -|---|---| -| iterations 1-7 (workflows) | ~9.0M+ | -| iteration 8: the merge window's design pass and diff read, the tie branch's round 5, its gate, its review | ~0.4M in subagents on 2026-09-06, plus iteration 8's own workflow legs, not closed as this is written | - -The orchestrator's own context is in none of these figures, and it is the leg that found the -two items no gate did. The stop rule stands at roughly 70% of the owner's weekly credit, -owner-signalled; the loop cannot read that meter and has not been given a percentage. It has -not been signalled to stop; it was paused once, when PR #202 merged, and restarted for -iteration 8. `C:` reached 100% once during iteration 6; 35 finished workflow worktrees were -removed and a sweep between iterations is now part of the routine. - ---- - -## 5. Decisions that are the owner's {#decisions} - -1. **The fork**: enumerate a sixth round, or pin the build-time fold's configuration through - the oracle (ask: engine-fold-reading, ask: threads-and-value-order). Evidence: five rounds, - none empty, four shapes still open, and the newest one carries a type chosen at bind time - from a string argument, which neither a name nor a declared type can reach. -2. **The four goal asks** still open on master: acceptance-target, next-query-classes, - exclusion-ratification, kpi-set-change. -3. **The static-only acceptance price**: 64/88 aggregate names, collations, table functions, the - `FROM` allow-list, `TIMETZ`, 4-5x build time, seven mined statements off the corpus floor. - The policy (never trade a control for a drive) says take it; the size is measured, so it can - be priced rather than assumed. -4. **The weekly percentage**, whenever the stop rule should bite. - ---- - -## 6. Next, in the goal's order {#next} - -1. The owner's answer to the fork (the RFC put to him at the close of iteration 8): it - decides whether iteration 9 enumerates round six's shapes or pins the fold. -2. Iteration 9 on the tie branch, under whichever answer: close round six's four serving shapes by the closures in the - table, decide the over-refusal (fix or restate), re-gate, re-read the whole diff. -3. Open the report PR (`loop-report-1`: the iterations 1-8 narrative plus this status file). -4. Then, unchanged from the report's queue: the refusal registry behind kpi: - named-refusal-share, gap: undocumented-boolean-comparison with the unsigned class, the - enforcement faults (the red Rust unit gate CI cannot see, eight iterations of records and no - owner), and the bench baseline. +| campaign acceptance, seeds 0-1999 (constructor returns) | 1056 / 2000 = 52.8% | 1049 / 2000 = 52.45% | **-7 seeds (-0.35 pp)** | 44 seeds are now planted twins of which 24 must refuse by design; the like-for-like loss is 4 seeds (`avg` x3, `sum` over DOUBLE), a control bought with drive | +| mined corpus matches (of 678) | 547 | 540 | **-7** | 5 were never real matches (a harness variable, below); `geomean` and `test_all_types()` are the rule's price | +| dialect ladder L2 (of 678) | 288 | 288 | 0 | untouched | +| serving latency | unchanged | unchanged | 0 | the constant path replays frozen rows; no bench run this iteration | +| build time of a static-only fold | 1x | ~4-5x | **cost** | the parse and tie probes are DuckDB round trips at build | + +The loop has so far **spent drive to restore a control**, which is what goal: parity-first +and the KPI law (never trade a control for a drive) tell it to do. No drive has been lifted +by it yet. + +## 3. What changed, as code {#changed} + +Each block: the call, what master's engine answers, what the tie branch answers. `ROW` is a +one-column `k BIGINT` row schema; `S` is `{"g": ["x","y","z"], "v": [1, 1, 2]}`; `S2` is +`{"st": [{"f1":1,"f2":0}, {"f1":2,"f2":0}], "a": [5, 5]}`. + +**Merged (PR #202): a NaN's sign reaches the text.** + +```python +fn = DuckDBInferFn("SELECT CAST(nextafter(d, 1.0e0) AS VARCHAR) AS o FROM __THIS__", + row_tables={"__THIS__": pa.schema([pa.field("d", pa.float64())])}, static_tables={}) +fn.infer_rows([{"d": -float("nan")}, {"d": 1.0}]) +# before (5819c3a): [{'o': 'nan'}, {'o': '1.0'}] DuckDB: '-nan' +# master now: [{'o': '-nan'}, {'o': '1.0'}] DuckDB: '-nan' +``` + +**On the tie branch: a tied ORDER BY refuses instead of freezing one order.** + +```python +DuckDBInferFn("SELECT g AS o, min(v) AS t FROM s GROUP BY g ORDER BY t", + row_tables={"__THIS__": ROW}, static_tables={"s": S}) +# master: SERVES constant [{'o': 'x', 't': 1}, {'o': 'y', 't': 1}, {'o': 'z', 't': 2}] +# (x and y tie at 1; five settings a build machine picks gave five sequences) +# branch: ValueError: unsupported: tie-producing ORDER BY on a static-tables-only query +# -- which of the tied rows comes first depends on scan order, not the query +``` + +**An order-dependent aggregate refuses.** + +```python +DuckDBInferFn("SELECT avg(v) AS o FROM s", row_tables={"__THIS__": ROW}, static_tables={"s": S}) +# master: SERVES constant [{'o': 1.3333333333333333}] (six answers over 200k rows, measured) +# branch: ValueError: unsupported: order-sensitive aggregate avg on a static-tables-only +# query -- its answer follows scan order, and an ORDER BY inside the aggregate is not +# read as a fix +``` + +**A file on the build machine's disk refuses.** (`demo_e2e.csv` holds two rows here.) + +```python +DuckDBInferFn("SELECT * FROM 'demo_e2e.csv'", row_tables={"__THIS__": ROW}, static_tables={"s": S}) +# master: SERVES constant [{'a': 1, 'b': 'x'}, {'a': 2, 'b': 'y'}] (whatever the file held at build) +# branch: ValueError: unsupported: the table demo_e2e.csv on a static-tables-only query -- it +# is not one of the query's static tables, so its rows are read off the file system +# or the catalogue when the query runs, not fixed by the query +``` + +**A macro whose body is an order-dependent aggregate refuses under its own name.** + +```python +DuckDBInferFn("SELECT json_group_array(v) AS o FROM s", row_tables={"__THIS__": ROW}, static_tables={"s": S}) +# master: SERVES constant [{'o': '[1,1,2]'}] (a scan-order sequence; two answers across settings) +# branch: ValueError: unsupported: order-sensitive aggregate json_group_array on a +# static-tables-only query -- its answer follows scan order, ... +``` + +**A clock read under a CONSISTENT flag refuses.** + +```python +DuckDBInferFn("SELECT age(TIMESTAMP '2020-01-01')::VARCHAR AS o FROM s", + row_tables={"__THIS__": ROW}, static_tables={"s": S}) +# master: SERVES constant [{'o': '6 years 8 months 6 days'}, ...] (today's date, frozen) +# branch: ValueError: unsupported: the non-deterministic function age() on a +# static-tables-only query -- its value is drawn when the query runs, not fixed by the query +``` + +**SUMMARIZE refuses.** + +```python +DuckDBInferFn("SUMMARIZE s", row_tables={"__THIS__": ROW}, static_tables={"s": S}) +# master: SERVES constant [{'column_name': 'g', ..., 'approx_unique': 3, ...}, +# {'column_name': 'v', ..., 'avg': '1.3333333333333333', 'std': '0.577...', ...}] +# (seven settings, seven answers on a large static) +# branch: ValueError: unsupported: a SUMMARIZE, DESCRIBE or SHOW statement on a +# static-tables-only query -- what it computes is chosen by DuckDB and read off the catalogue +``` + +**A value with a time zone refuses.** + +```python +DuckDBInferFn("SELECT (TIMESTAMPTZ '2020-01-01 00:00:00+00')::VARCHAR AS o FROM s", + row_tables={"__THIS__": ROW}, static_tables={"s": S}) +# master: SERVES constant [{'o': '2020-01-01 01:00:00+01'}, ...] (this machine's zone, frozen) +# branch: ValueError: unsupported: a value WITH TIME ZONE (a cast to TIMESTAMP WITH TIME ZONE) +# on a static-tables-only query -- its rendering reads the build machine's time zone +``` + +**An alias displaced by an expanding entry refuses instead of measuring the wrong column.** + +```python +DuckDBInferFn("SELECT *, a AS k, unnest(st) FROM s ORDER BY k", + row_tables={"__THIS__": ROW}, static_tables={"s": S2}) +# master: SERVES constant [{'st': {...}, 'a': 5, 'k': 5, 'f1': 1, 'f2': 0}, {..., 'k': 5, 'f1': 2, ...}] +# (k ties at 5, 5) +# branch: ValueError: unsupported: a sort key whose output position this reading cannot place +# on a static-tables-only query -- a tie among its rows could not be ruled out, ... +``` + +**Still crossed on the branch: what the sixth review measured** (its outputs, confirmed end +to end with a refusing control on identical data). + +```python +# 1. a zoned type made from a STRING at bind time -- no cast, no column, no maker to sight +DuckDBInferFn("SELECT strptime('2020-01-01 00:00:00+05', '%Y-%m-%d %H:%M:%S%z')::VARCHAR AS d FROM s", ...) +# branch: SERVES constant '2019-12-31 20:00:00+01' (UTC '...19:00:00+00', Asia/Tokyo '2020-01-01 04:00:00+09') + +# 2. a macro whose body calls another macro -- expansion stops one level short +DuckDBInferFn("SELECT geometric_mean(abs(d)) AS o FROM s", ...) # -> geomean -> exp(avg(ln(x))) +# branch: SERVES constant 529251629.29637396 (four answers across settings) + +# 3. a CTE declared inside a subquery whitelists its bare name for the outer FROM +DuckDBInferFn("SELECT count(*) AS o FROM 'e2e.csv' WHERE 1 IN (WITH \"e2e.csv\" AS (SELECT 1 AS a) SELECT a FROM \"e2e.csv\")", ...) +# branch: SERVES constant o = 1 in one directory, o = 5 in another + +# 4. a static named like a catalogue view's last segment whitelists the qualified read +DuckDBInferFn("SELECT count(*) AS o FROM information_schema.tables", ..., static_tables={"s": S, "tables": S}) +# branch: SERVES constant o = 4 (a count of this build's catalogue) +``` + +Two more from that review are not fail-opens: the `age` arity read never reaches a macro body +(latent; no builtin macro calls `age`), and one zoned column in **any** static refuses every +query on that build, `SELECT 1` included, which is wider than its disclosure. + +## 4. How the branch reads a statement {#mechanism} + +Every refusal above is read off DuckDB's own parse (`json_serialize_sql`, walked with +`json_tree`) and DuckDB's own catalogue (`duckdb_functions()`, `duckdb_columns()`), asked of +the connection the statement already ran on. Three of iteration 8's five rules are allow-lists +or metadata reads rather than name lists: a base table must be a static or a CTE; a macro body +is parsed and fed into the same name reads as a call; a zoned value is sighted in a column's +declared type, a cast node, or a maker's catalogue return type. The four shapes still open are +defects in those readings (scope, qualification, depth, a type chosen at bind time from a +string), not new families. + +**The corpus move, explained.** Five of the seven statements that left the match count +(`SELECT COUNT(*) FROM t`) had matched only because DuckDB's Python client resolves a bare +table name against the variables of the calling frame, and the replay left a pyarrow table +named `t` there. The FROM allow-list ended that accident; a harness fact as much as an engine +fact, now pinned at `MATCH_FLOOR` with its reason. + +## 5. The gate on the rebased tip {#gate} + +`e6a3cd8` is the gated `36ae02e` rebased onto master `8796bb2` (eleven commits, no +conflicts). Root suite **3480** passed / 1 skipped / 9 xfailed / 2 errors (absent `pyspark`): +every one of master's 3338 collected ids present with its outcome, plus exactly **154** +branch ids. `cargo test --release --lib` 269 / 5, the five pre-existing names. Corpus 540. +Campaign seeds 0-1999: `AGREE` 1008 / `REFUSED` 951 / `AGREE_TRAP` 20 / `UNSHIPPED` 14 / +`DIVERGE_OPT` 7 / `DIVERGE_VALUE` **0**; the one seed that moved against the pre-rebase gate +is 1804, agreeing now because the tree carries master's NaN fix. Mutation: five rules reverted +one at a time, 2 / 16 / 4 / 37 / 16 tests red each time. 48 of 48 hand probes, 29 must-serve +and 19 must-refuse. + +## 6. Method, spend, decisions {#rest} + +**Method.** The orchestrator's own read of every diff found the two items no gate did (the +`nextafter` sign bug; the displaced alias). A pin measured on Windows was not a pin on Linux +(the both-NaN `nextafter` case; the kernel now calls the platform's own C `nextafter`). A +gate on a tree thirteen commits behind master is a gate on something else; the branch is +rebased before it is presented. Two sentences that judged DuckDB's classification were +restated as the measurements they rest on. + +**Spend.** Iterations 1-7 ~9.0M agent tokens; iteration 8 ~1.3M. The stop rule is roughly 70% +of the owner's weekly credit, owner-signalled, not yet given. + +**Decisions that are the owner's.** +1. The fork: enumerate a seventh round, or pin the build-time fold's configuration through the + oracle (ask: engine-fold-reading, ask: threads-and-value-order). The RFC with its + three-question framework was put in the chat at the close of iteration 8. +2. ask: acceptance-target now (recommended: ratchet without a target); exclusion-ratification, + kpi-set-change and next-query-classes wait on the fork. +3. The static-only acceptance price above: the policy says take it; it is measured, so it can + be priced. +4. The weekly percentage. + +**Next, in the goal's order.** The fork's answer; iteration 9 under it (close the four, +scope the over-refusal, re-gate, re-read); the report PR; then the refusal registry, +gap: undocumented-boolean-comparison, the red Rust unit gate CI cannot see, the bench baseline.