# Claims ledger: "A week of outside use: seven findings, five fixes"

Post: `site/blog/a-week-of-outside-use/index.html`, published 2026-10-03. Piece: `launch-site`. Written against `main` `005790c`, whose
record of this week is `.10x/status.md` checkpoint 34. Same purpose and shape as `site/blog/why-undra-is-the-default-choice/claims.md`.
The team is not named, on purpose, and neither is anyone in it. No row quotes the team's messages; the records the rows cite (`adr-060`, `adr-061`, `adr-062`) do quote them, and the fact-check lists that for the integrator.

**Fact-checked adversarially on 2026-10-03** against `main` `a547226`; the pass is recorded in `.10x/reviews/2026-10-03-launch-posts-fact-check.md`. The last column
is the fact-check: the source was opened and the post compared with it, digit for digit and condition for condition. **verified** means the source
says what the post says; **corrected** says what was wrong and how the post changed (the earlier wording is in italics); **added** means a claim
the draft made without a row. Line numbers are the files as of that date; `rev:sse-chunks`, `rev:okhttp-adapters` and `rev:bazel` are the
reviews of 2026-10-02 of those pieces.

**Aliases.** `stat` = `.10x/status.md`. `adr-NNN` = `.10x/adrs/ADR-NNN-*.md`. `rev:x` = `.10x/reviews/2026-10-02-x-review.md`. `roadmap.json` = `site/data/roadmap.json`.
`sde:x` = `.10x/decisions/sde/x.md`. **Checked by**: `A` the author opened the path; `J` the author's judgement, worded as such.

| ID | Claim | Source | Checked by | Fact-check (2026-10-03) |
|---|---|---|---|---|
| W01 | A team tried Undra for a week and sent seven findings | `stat` line 576 ("A team that tried Undra for a week sent seven findings (U1–U7)") | A | **verified** `stat:576` |
| W02 | A team tried Undra for a week in a proof of concept *(was: "... inside its own repository")* | adr-061 lines 17-18 (the feedback refers to a proof of concept; the ledger does not quote it) | A | **corrected**: no record says the proof of concept lived inside the team's own repository (`adr-061:17-19` mentions only "repo-wide lint rules"); dropped, which also says less about the team. The earlier draft of this row quoted the feedback, and no row of this ledger does now |
| W03 | Five are fixed and merged, each through a pull request with a decision record and an adversarial review | `stat` lines 576-589 (each "landed through a pull request with its adversarial review closed"; the table's Review column); records: adr-047 amendment (U1), adr-061 (U3), adr-052 amendment (U4), adr-060 (U5), adr-062 (U7) | A | **verified** `stat:576-589`; the five pieces are #3, #5, #4, #7 and #8, each with its ADR or amendment (ADR-062, 047, 060, 061, 052) |
| W04 | Two were not taken on | `stat` line 591 | A | **verified** `stat:591` |
| W05 | The table of seven findings and outcomes | `stat` lines 583-591 (U1 sse-chunks, U2 not taken, U3 bazel, U4 android-size, U5 okhttp-adapters, U6 not taken, U7 generated-weight) | A | **verified** `stat:583-591` (the post's table lists U1 to U7 in the order U1, U2, U3, U4, U5, U6, U7) |
| W06 | The report measured the iOS SSE ceiling 2.4x below what it should be | adr-047 lines 170-171; `sde:sse-chunks` line 3 | A | **verified** `adr-047:170-171`: "measured the iOS SSE ceiling 2.4x below what it should be, and the code confirmed it" |
| W07 | The Swift adapter pulled `URLSession.AsyncBytes` one byte at a time and handed the parser one byte per call | adr-047 lines 171-172 (`CollectionOfOne(byte)`); `sde:sse-chunks` lines 3-4 | A | **verified** `adr-047:171-172`; `sde:sse-chunks:3-4` |
| W08 | The Kotlin and TypeScript adapters already read chunks | adr-047 line 172 | A | **verified** `adr-047:172` |
| W09 | The adapter is a data task's delegate and the parser gets whole chunks | adr-047 lines 174-176 | A | **verified** `adr-047:174-176` |
| W10 | Backpressure by the Kotlin adapter's rule: the task is suspended while events wait, URLSession stops reading, TCP pushes back | adr-047 lines 180-183 | A | **verified** `adr-047:179-182` (the task is suspended before a chunk is parsed, not after) |
| W11 | Measured on a Mac (Apple M5 Pro, macOS 26.5, Swift 6.3.3), in a release build by a throwaway harness against a local Node server: 4 KB events went from 12,600 to 84,000-99,000 a second over three runs (7.8x, the middle run); 512 B events 2.1x; 64 B events unchanged; a debug build gained 4.2x and 4.3x; the report's 2.4x falls inside that range. *Was: "Release, 4 KB events: ... (7.8x)" with no machine* | adr-047 lines 191-200; `sde:sse-chunks` lines 47-73; `rev:sse-chunks` lines 78-80 | A | **corrected**: the draft gave 7.8x and "release builds" and a table cell "slow on iOS"; the source says the Mac (`adr-047:191`), the harness, the loopback server, and "Not verified: a run on an iOS simulator or device" (`rev:sse-chunks:78-79`). 12,605 to 98,174 events/s is 7.79x, the middle of 83,722 / 98,174 / 99,176 (`sde:sse-chunks:68`); the range is 6.6x to 7.9x. 64 B unchanged: the limit is URLSession's receive path (`adr-047:198`, `202-203`). The table cell now says "release build, on a Mac" |
| W12 | Once the parser worked on bytes instead of characters, it alone went from 310 to 2,246 MB/s on 4 KB events, in a release harness fed 64 KiB chunks. *Was: no harness or chunk size* | adr-047 lines 219-221; `rev:sse-chunks` line 71 | A | **corrected**: `rev:sse-chunks:70-71` ("scratch harnesses outside the repository ... `SseParser` alone (64 KiB chunks)"); `adr-047:220-221`. The review's adapter-level pair on a loaded machine (57,000-67,000 to 95,000-117,000 events/s, line 72-73) is a different comparison, before and after the parser change only, and is not used |
| W13 | Two High defects in the fix itself: a cancelled waiter hung and left the request open; a background URLSession, which takes no task delegate, aborted the app. A background session is now refused with a typed error, a behaviour change from the old adapter, which streamed on one. *Was: "crashed the app", no behaviour change* | `rev:sse-chunks` lines 42-43; adr-047 lines 214-218 (`Refused(status: nil)`); `stat` line 584 | A | **corrected**: `rev:sse-chunks:42-43`: both defects were in the new code (`main` threw `Network("cancelled")` and streamed on a background session); the refusal is `Refused(status: nil)` (`adr-047:215-216`), "a behavior change for that configuration, stated in the amendment" (`rev:sse-chunks:43`). The post now says so |
| W14 | Undra's build was a command line Xcode and Gradle call; a Bazel repository had nothing to put in its graph; lint flagged the generated Kotlin | adr-061 lines 17-27 | A | **verified** `adr-061:17-27`, paraphrased; the post does not quote the feedback |
| W15 | `bazel/` adds `undra_core`, `undra_bindings` and a library rule per language, running the CLI as a pinned, hermetic, offline tool | `stat` line 586; adr-061 lines 1-10 | A | **verified** `adr-061:47-60`, `stat:586`; Bazel 8.8.1 pinned (`adr-061:50`); "a sandbox with the network off builds the cores and the bindings" (`sde:bazel:51`) |
| W16 | `examples/bazel` tests a core from Kotlin on the JVM, TypeScript on Node and, on macOS, Swift | `stat` line 586 ("Kotlin over JNI, TypeScript on wasm, Swift in process"); `README.md` lines 190-192 | A | **verified** `examples/bazel/README.md` (target table: JVM through JNI, Node, Swift on macOS); `README.md:190-192` |
| W17 | Lint exclusions written beside every generated tree took ktlint 1.8 from 90 findings to none on the example's generated Kotlin. *Was: no tool version or tree* | `sde:bazel` lines 37-38; `stat` line 586 | A | **corrected** (conditions): `sde:bazel:37-39` ("90 findings with neither exclusion, 0 with either, 0 with both"); `examples/bazel/README.md` (`//kotlin:lint_test`) |
| W18 | Android under Bazel is declared and not yet verified: the example's Android core stops at analysis for want of a C++ toolchain. *Was: "declared and not yet verified" alone* | `stat` line 593; `sde:bazel` "Not verified" | A | **corrected** (the reason added): `examples/bazel/README.md` ("declared and does not build ... Unable to find a CC toolchain"); `rev:bazel:38` (M3); `sde:bazel:67-70`; the bindings-only `//android:hello` was built by hand, not in CI; roadmap line 113 says "declared; a build and a test run prove it" |
| W19 | Nothing gated a native core's size | adr-052 lines 657-659 ("This ADR gated the web only; nothing gated an Android or an iOS core") | A | **verified** `adr-052:657-659` |
| W20 | Release builds use a profile at `opt-level = "s"` keeping the four call-path crates at full speed | adr-052 "The profile" (the `release-mobile` block and its list: `undra-wire`, `undra-signals`, `undra-runtime`, `undra-ffi` at 3) | A | **verified** `adr-052:675-690` (`release-mobile`, `opt-level = "s"`, `undra-wire`, `undra-signals`, `undra-runtime`, `undra-ffi` at 3, `panic = "unwind"`) |
| W21 | Three gates hold the hello world: 898,176 bytes for arm64-v8a and 960,776 for x86_64 (stripped, uncompressed) against 1.2 MB, and an iOS slice of 784,086 against 900 KB. *Was: arm64-v8a only* | `stat` line 587; `bench/results/native-size.jsonl` lines 1-3; `adr-052` lines 794-806 | A | **corrected** (completed): the three rows are `adr-052:802-804` and the record's lines 1-3 (898,176 / 960,776 / 784,086, budgets 1,200,000 / 1,200,000 / 900,000); the record's `what` says "stripped, 16 KB aligned", so the unit is bytes of the `.so`, not gzipped |
| W22 | `opt_level` in the `[android]` or `[ios]` table of `undra.toml` chooses `"s"` (the default), `"z"` or `"3"`. *Was: "chooses smaller or faster"* | adr-052 "The knob" | A | **verified** `adr-052:878-881` ("`[android] opt_level` and `[ios] opt_level`, `"s"` (the default), `"z"` or `"3"`"); the post names them |
| W23 | The reported core is an estimate, not a measurement: about 1.42 MB per ABI with nothing changed (1.38 to 1.46 MB) and about 1.18 MB at `"z"` (1.14 to 1.21 MB), so `"z"` reaches 1.2 MB narrowly and not for every core of that size. *Was: "lands at"* | adr-052 "Where the user's number lands" | A | **corrected**: `adr-052:915-921`: the estimate splits the 1.6 MB core into the hello world's 987,720 bytes plus about 612 KB of the user's, scales the second by how the playground shrinks, and gives the ranges for the tool's rounding of 1.55-1.65 million bytes to "1.6 MB"; "narrowly and not for every core of that size" is the ADR's. The status line (`stat:587`) says the same, with "about" |
| W24 | `"z"`, on the playground's device benchmark and against `"3"` (the default matches it within noise): a synchronous call 1.33x slower on the iPhone simulator and 1.58x on the Android emulator, not on a phone. *Was: no baseline or device* | adr-052 "The knob", speed table (sync call: 1.58x emulator, 1.33x simulator) | A | **corrected** (conditions): `adr-052:904` (sync call, `"s"`/`"3"` 0.99x on both; `"z"`/`"3"` 1.58x emulator over 8 runs, 1.33x simulator over 5), `adr-052:912`; the earlier 1.50x and 1.34x of line 869 are the piece's first measurement and are superseded; `adr-052:873` "What is not claimed: a physical device" |
| W25 | The default Android adapter used `HttpURLConnection`; an app's refresh, interceptors, tracing and pinning live in its own `OkHttpClient`, which the platform's `HttpURLConnection` cannot reach; a core request carried no token, appeared in no trace, was not pinned | adr-060 lines 15-24 | A | **verified** `adr-060:15-24`, paraphrased (`adr-060:18-21`: the platform's own client is one "which no app code can reach"); **corrected** for privacy: "the team's" became "an app's", so the post describes the problem and not the team's setup |
| W26 | `okhttp-adapters` puts Http, WebSocket and SSE over the app's client with one call | adr-060 lines 55, 64 (`installWithOkHttp`); `stat` line 585 | A | **verified** `adr-060:55`, `64`; `stat:585` |
| W27 | On iOS the app's `URLSession` serves all three; the SSE adapter refuses a background session and the WebSocket adapter does not yet (roadmap). *Was: no caveat* | adr-060 line 7 and its table lines 35-40; `rev:sse-chunks` line 50 (L3); roadmap line 117 | A | **corrected**: `rev:sse-chunks:50` (L3), `stat:593` and `roadmap.json:117` say `URLSessionWebSocketAdapter(session:)` with a background session crashes the way the SSE adapter did before it refused one; open |
| W28 | The review's High: in the old install order a queued offline request could leave through the default adapter without the token and the pin | `rev:okhttp-adapters` line 32; `stat` line 585 | A | **verified** `rev:okhttp-adapters:32` (H1), `stat:585` |
| W29 | Two features had produced about 1,400 lines of bindings | adr-062 line 12 (the feedback) | A | **verified** `adr-062:12`, `adr-062:32-33` |
| W30 | One feature of the playground is 130 to 250 generated lines per platform | adr-062 lines 31-32 | A | **verified** `adr-062:30-33` ("130 to 250 lines per platform") |
| W31 | We kept the code because the project's rules require generated code to read as a platform engineer would write it, and moved review to the schema. *Was: "which reads as native to each platform's engineers" (a quality claim)* | adr-062 lines 21-33 (decision) | A | **corrected**: `adr-062:27-29`; the rule is R3 of `CLAUDE.md`, so the sentence now states the rule, not a verdict on the output |
| W32 | Generated files are marked for GitHub to collapse; `undra schema diff` prints one line per change, breaking or additive | adr-062 lines 66-69 (`linguist-generated=true`); `site/docs/cli.html` `#undra-schema` | A | **verified** `adr-062:58-72` (`linguist-generated=true`, `-diff` not set); one diff line per change: `adr-062:181`, `site/docs/reviewing-generated-code.html:102` |
| W33 | Thirteen changes on the CLI's test fixture (a to-do core): bindings 581 lines, schema 77, the diff 13. *Was: twelve changes, 506, 60, 12* | adr-062 lines 264-265; `crates/undra-cli/tests/schema_diff.rs` lines 353-367 (13 expected lines, "7 breaking, 6 additive"); `site/docs/reviewing-generated-code.html` line 102 | A | **corrected**: the draft used the first measurement (`adr-062:179-181`, `sde:generated-weight:100-101`, `stat:583`), taken before the review added an event port to the fixture; the fixture in the tree has thirteen changes (the test lists them) and the docs page the post sits beside says 581 / 77 / 13. The status line `stat:583` still says twelve: it is the integrator's file |
| W34 | Xcode 27: the Swift test target was reported not to build; our machines and CI run Xcode 26; it needs the crash log | `stat` line 591 | A | **verified** `stat:591`; roadmap line 132 |
| W35 | Undra has no GraphQL layer; an Apollo app keeps Apollo on the platform side | `stat` line 591 | A | **verified** `stat:591` |
| W36 | Whether Undra should have more is a question, not a plan | `site/data/roadmap.json`, Exploring ("A GraphQL story") | A | **verified** `roadmap.json:142` (Exploring: "A GraphQL story") |
| W37 | Three of the five findings taken on were places where our tests were green and a real app was not (a ceiling nobody had measured, a build system we did not use, a size we did not gate). *Was: "Each finding"* | judgement | J | **corrected** (judgement narrowed): the reviews and records show this for U1, U3 and U4; U5 and U7 were gaps in a feature and in review, not test gaps; the post says three of five |
| W38 | Left open by the reviews: OkHttp pinning end to end, Android under Bazel, a byte-reproducible build | `stat` line 593 | A | **verified** `stat:593`; roadmap lines 113, 114, 117 ("What the five reviews left open", not "their reviews") |
| W39 | OkHttp itself leaves a client's network interceptors and event listener out of a WebSocket upgrade, so tracing written that way will not see the core's WebSockets; the docs say so | `rev:okhttp-adapters` line 36 (M4); `adr-060` line 114; `runtimes/kotlin/undra-runtime/okhttp-adapters/README.md` lines 55 and 76 | A | **added**, verified: "an application interceptor's header arrives, a network interceptor's does not, the listener hears nothing". It is a fact about OkHttp's `RealCall`, measured, not a judgement of OkHttp |
| W40 | The report's 2.4x falls inside the measured gains (debug 4.2x and 4.3x; release 2.1x to 7.8x) | `sde:sse-chunks` line 73 ("The user's 2.4x is inside the range"); `adr-047` lines 196-200 | A | **added**, verified |
| W41 | 64-byte events did not move, because the limit there is URLSession's own receive path | `adr-047` lines 198, 202-203; `sde:sse-chunks` lines 41-45 | A | **added**, verified (a delegate that does nothing read 100,000 events of 49 bytes at 11,000 a second) |
