Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,39 @@ The other corrections worth carrying:
document warned about.** It recorded `_LIBCPP_VERSION 200100` and clang
22.0.0git; 6.0.9 reports `220108` and clang 24.0.0git. The lesson it drew --
that the surface must match the LIBRARY and not the compiler -- is right, and
the numbers it drew it from are two releases stale.
the numbers it drew it from are two releases stale. Re-measured from an
installed payload on 2026-09-11 by preprocessing `_LIBCPP_VERSION` out of
`<__config>` with the payload's own `em++`, so the number is the library's
and not a release note's.

- **The file count was 133, not 134**, and the two are the same surface size
the NDK ships: 2 `.cppm` plus 131 `.inc` partitions under
`<payload>/emscripten/cache/sysroot/share/libc++/v1/`. Counted rather than
recalled, because this document uses these counts as the evidence that a
vendor ships the surface.

- **"No additional flags" needed one qualification, and the qualification is
the good news.** `em++` ships the surface's SOURCE, not a BMI, so
`import std;` on its own fails with `module 'std' not found`. What is not
needed is the *generation* machinery this section describes at length --
there is nothing to generate. Building the BMI is the engine's ordinary job
for every toolchain it supports, and measured with exactly the two steps it
already performs:

em++ -std=c++23 --precompile <sysroot>/share/libc++/v1/std.cppm -o std.pcm
em++ -std=c++23 -fmodule-file=std=std.pcm -o hello.js hello.cpp std.pcm
node hello.js -> 1-2-3

`std.pcm` is 34 MB and the link produced `hello.js` (65370 bytes) plus
`hello.wasm` (447175 bytes). `-Wno-reserved-module-identifier` is NOT
required -- the precompile succeeds without it, with two warnings, and mcpp
passes it only to silence them (`src/toolchain/clang.cppm:243`).

So `wasm32-emscripten` needs no new standard-library mechanism at all:
`stdModuleSource` points at that `std.cppm` and the existing path takes it
from there. **And the two-file output is confirmed** -- which is the one
genuinely new engine item, answered by the implicit-output channel the link
edge already has for import libraries and PDBs.
- **`emsdk` cannot be a payload at all.** `emscripten-core/emsdk` publishes
**zero** GitHub releases, and its `emsdk.py` fetches the real toolchain over
the network at install time. The recipe therefore names what `emsdk.py`
Expand Down Expand Up @@ -1116,6 +1148,22 @@ unpersisted declaration would be absent exactly when a user names a format.
carries no such line and the program that wrote it could not emit one, so
replaying it yields what that program said.

Measured across two real binaries rather than only in a unit test, because an
absent-tolerance claim is about what a *previous version* wrote:

| step | binary | the graph's header line | result |
|---|---|---|---|
| 1 | released 2026.9.10.2 | `graph=normal;schedule=none;accel=default` | builds |
| 2 | 2026.9.11.2 | `;dist=none` appended | fingerprint change, full rebuild, no error |
| 3 | 2026.9.11.2 | unchanged | `Finished dev in 0.00s` -- the fast path replays |
| 4 | 2026.9.10.2 again | its own older directory | `0.00s` -- the downgrade does not choke |

Step 4 also says what the absent-tolerance is worth. The version is part of the
fingerprint, so two binaries never share a graph directory and an older mcpp
never actually reads a `dist=` field. That makes the field's read side defence
in depth rather than a live path -- which is the same conclusion §11.2 reaches
from the other direction, and is why the invariant is held in a unit test.

### 11.9 Test coverage

The count is not the measure; what each test excludes is. Two are worth naming.
Expand Down
1,076 changes: 1,076 additions & 0 deletions .agents/docs/2026-09-11-platform-targets-design-review.md

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions .agents/docs/2026-09-11-six-open-issues-analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,3 +531,119 @@ refuted for every build the vendor manifest currently serves, measured across
all four Linux host entries rather than the pinned one. So both remaining
platform rows are executable on an x86_64 Linux runner with no device and no
virtualization, which is what a CI lane needs.

## 9. Ecosystem sign-off

Written after the work landed, across four repositories, against what shipped
rather than against the plan. The question this section answers is not "is each
change correct" -- section 8 and the tests answer that -- but "does the
ecosystem hold together with these changes in it".

### 9.1 The one thing that went wrong twice, in two repositories

A check that asks a **proxy** question refuses correct output, and it did so
twice in one day in two repositories:

* `mcpp-plugins` CI refused a correct MSI. WiX 6 ran with no warnings and
produced 32768 bytes for a 114688-byte program; the floor was
`size > exesize / 2`, and its own comment admitted the ratio was invented.
32768 is what a stripped hello-world looks like after a cabinet has had it.
* The same repository had already done this with a 16 KB AppImage bound
refusing a correct 14999-byte bundle.

Both are now direct questions. The AppImage is **run** and its output asserted;
the MSI is **installed** (`msiexec /a`) and the extracted program compared byte
for byte. The pattern to carry forward: when a check reasons "X should be
roughly as large as Y", the artifact can almost always be opened instead.

### 9.2 The one thing that went wrong twice in the same change

`#599` was fixed by reasoning about which shard runs `233`, and the answer was
"whichever one the round-robin puts it in" -- so both Linux shards got the
submodules. `233` runs in every job that invokes the whole suite, of which
there are **three**. The macOS lane caught it, which is the same shape as the
defect being fixed: a rule reasoned about against one object and applied to
all of them.

The correction was to **enumerate**: the three unfiltered
`bash tests/e2e/run_all.sh` jobs are named, and the two jobs that invoke the
suite with a filter or name tests directly are named as not needing it. An
enumeration can be re-checked; a piece of reasoning about sharding cannot.

### 9.3 What the ecosystem rule turned out to cost, and what it did not

The rule is the user's: every tool and every library comes from the ecosystem,
and anything missing is added until the loop closes. Applied to the Android
and Web payloads it cost **one new payload** and otherwise only declarations:

| escape | closed by | new package |
|---|---|---|
| host `debugfs` | `xim:e2fsprogs` | no -- already in the index |
| host `libX11` chain (6 libraries) | declared `deps` | no -- all six already there |
| host `python3` for `em++` | `xim:python@>=3.12` | no, but **aarch64 payload added** |
| `/dev/kvm` | nothing | it is a kernel device |

The interesting entry is the third. `xim:python` was x86_64-only, and that was
the *stated reason* `xim:emsdk` could not declare an interpreter -- an argument
that was true when written and was an argument for adding the missing payload
rather than for depending on the host. Adding it closed the loop for both
arches. The general form: a dependency declined because the ecosystem cannot
serve it is a request for a package, not a licence to use the host.

`/dev/kvm` is the boundary the rule has, and stating where a rule stops is part
of stating the rule. A test now asserts it is the **only** remaining warning in
that recipe, so a second one cannot appear quietly.

### 9.4 Where the ecosystem rule is overruled, and by what

By the licence, and this is the second time the same framework decided a
packaging question. `xim:iphoneos-sdk` carries no CN mirror because a `CN`
entry would mean xlings-res holds a copy of Apple's SDK. The four Android
packages reach the same conclusion from the same field -- all four declare
`licenses = {"Android Software Development Kit License Agreement"}` -- and keep
one upstream URL each. `xim:emsdk` (MIT / NCSA) and `xim:python` (PSF) are
mirrored because their licences permit it.

So the rule composes as: **the ecosystem supplies what it may, and the licence
says what it may.** A recipe that declines a mirror should say which of the two
reasons applies, because a reader who cannot tell "not permitted" from "not
done yet" will eventually do the wrong one.

The cost of getting this order wrong is asymmetric and worth recording: a
mirror that should not exist cannot be withdrawn. Three objects were uploaded
to GitCode before the licence was checked, GitCode assets cannot be deleted,
and the only available remedy is that no recipe references them. The check is
cheap and comes first.

### 9.5 The cross-repository order, re-derived from what happened

mcpp engine ──► 2026.9.11.2 the only thing on the critical path
├──► xim payloads independent; merged first
└──► mcpp-plugins pins a RELEASE, so it cannot precede one
└──► mcpp-index needs the plugins tag's sha256

This was already written in the distribution record's section 11.10, and the
release cycle confirmed it in the sharpest possible way: `mcpp-plugins` #16 has
Linux and Windows green and macOS red on `error: cannot package the Mach-O
program`, which is precisely the defect 2026.9.11.2 fixes. The dependency is
not a convention -- the red lane *is* the dependency.

### 9.6 What is still open, stated rather than implied

* **The four target rows.** `wasm32-emscripten`, `aarch64-ios`,
`aarch64-linux-android` and `x86_64-linux-android` remain `planned`. Both
execution routes are now measured -- `em++` compiles and links `import std`
and `node` runs the result; `qemu-aarch64 -L <extracted system image root>`
executes the default dynamic Android configuration -- and both payloads are
published. What remains is engine work of the same size as the distribution
batch: toolchain resolution for two drivers whose target is fixed by their
payload, an implicit `.wasm` output on the link edge, `runner` defaults per
row, 48 matrix cells, and a CI lane per row. That is the next PR, not a
loose end in this one.
* **`xim:wix`** is a legitimate gap with a known shape: MS-RL, a NuGet flat
container, needing `xim:dotnet`. Version 6 and not 7, because 7 refuses to
run without an out-of-band licence acceptance -- a package pinning it would
install a tool that cannot work.
7 changes: 6 additions & 1 deletion .agents/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded
---
```

276 records.
277 records.

## By subject

Expand All @@ -44,6 +44,10 @@ Records that declare one. Everything else is listed by date below.

- [The category the plugin taxonomy does not name, and what a platform actually decomposes into](2026-09-11-distribution-plugins-and-platform-decomposition.md) — active

### targets

- [Where a platform's knowledge belongs: iOS, Android and Web across the engine, the index and the plugins](2026-09-11-platform-targets-design-review.md) — active

### triage

- [Six open issues: what each one actually is, and what would answer it](2026-09-11-six-open-issues-analysis.md) — active
Expand All @@ -53,6 +57,7 @@ Records that declare one. Everything else is listed by date below.
### 2026-09

- [Six open issues: what each one actually is, and what would answer it](2026-09-11-six-open-issues-analysis.md) — active
- [Where a platform's knowledge belongs: iOS, Android and Web across the engine, the index and the plugins](2026-09-11-platform-targets-design-review.md) — active
- [The category the plugin taxonomy does not name, and what a platform actually decomposes into](2026-09-11-distribution-plugins-and-platform-decomposition.md) — active
- [Two answers and two silences: the scanner's second grammar, and the manifest keys nothing reads](2026-09-09-two-answers-and-two-silences.md) — active
- [A dlopen surface no closure walks, and a process with two unwinders](2026-09-09-dlopen-surface-and-two-unwinders.md) — landed
Expand Down
13 changes: 13 additions & 0 deletions .github/tools/build_examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ BUILD=(
# three-file package.
examples/12-a-new-device-language/toyc
examples/12-a-new-device-language/app
# One source, three platforms. BUILT here rather than skipped, because its
# HOST build needs no payload at all -- the `[target.*-linux-android]`
# sections are inert unless that target is selected, which is itself worth
# one build: a manifest that names a target the runner has no payload for
# must still parse and build for the host.
#
# The cross legs are not built here. `wasm32-emscripten` and the two
# Android rows would pull `xim:emsdk` and `xim:android-ndk` -- about 1.5 GB
# between them -- and the signal already exists elsewhere:
# ci-target-matrix scans every row on four hosts, tests/e2e/641 asserts the
# vocabulary, and the example's README records the measured artifacts and
# the emulator run for the binary it describes.
examples/13-platform-targets
)

# `key|reason`.
Expand Down
23 changes: 21 additions & 2 deletions .github/workflows/ci-fresh-install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,27 @@ jobs:
- distro: ubuntu-2004
image: ubuntu:20.04
setup: apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install curl bash tar gzip xz-utils git ca-certificates binutils findutils file
- distro: debian-11
image: debian:11
# debian-12 AND NOT debian-11, AND THE REASON IS NOT THE FAILURE.
#
# The debian-11 leg started failing on 2026-09-11 with
#
# E: Release file for .../bullseye-security/InRelease is expired
# (invalid since 3d 5h 32min 52s)
#
# and `apt-get update` exits 100. Bullseye is end-of-life and its
# security suite's metadata has expired, which is a property of the
# distribution and not of this workflow -- `-o
# Acquire::Check-Valid-Until=false` would silence it and keep a leg
# that tests against metadata nobody maintains.
#
# What was measured while replacing it: debian 11 and ubuntu 20.04
# both carry glibc 2.31, so the "older glibc" coverage this leg was
# here for was ALREADY DUPLICATED by the ubuntu-2004 leg above, and
# dropping bullseye loses nothing. Bookworm's 2.36 sits between that
# 2.31 and debian-testing's rolling version, so this leg now covers a
# point the matrix did not have.
- distro: debian-12
image: debian:12
setup: apt-get update && apt-get -y install curl bash tar gzip xz-utils git ca-certificates binutils findutils file
env:
# The one derived value (see the header comment): every install job names
Expand Down
Loading
Loading