Skip to content

Commit 0995ea9

Browse files
committed
docs(design): openkal across the three platforms, and the plan's self-review
The plan gains item E -- openkal's cross-build closing on iOS, Android and Web -- and the self-review the goal asks for. Retitled, because the document is no longer only about SDK toolchains. E is three different answers, and the spec is what makes the third legitimate. E1 Android DONE and merged. `openkal-linux` is written on the kernel's syscall interface and borrows nothing from a C library; Android's kernel IS Linux and sys.h branches on architecture, not OS. Green against the RELEASED 2026.9.11.3: both ABIs build, the objects name no C library symbol -- checked against a DIFFERENT libc, which is the half worth having -- and a program over openkal alone printed `openkal: 1-2-3` on an emulator. E2 iOS a consequence of D rather than new work: once the SDK resolves, `openkal-macos` compiles for the iOS rows unchanged. One asymmetry is recorded rather than papered over: `cfg(os = "linux")` covers Android because Android is an `env` on `linux`, while `cfg(os = "macos")` does NOT cover iOS because iOS is its own `os`. A `cfg(apple)` dimension was considered and rejected -- it would introduce a dimension for one consumer's convenience. E3 Web new software, and the smallest of the three: forwarding to a POSIX-shaped libc is thinner than issuing syscalls. The partial surface is CONFORMANT, by clause 6.2's three times -- provided and forwarding, provided with the capability word reporting what is exercisable, or NOT PROVIDED so the report is an undefined symbol at link. `process`, `exec` and `space` take the third: providing `kal_process_spawn` on wasm to return an error is the shape the spec names as a defect, because the caller cannot tell. E4 records that `examples/portable` is stale on EVERY target -- it fails to compile for the host with `unknown type name 'kal_spawn'`, identically to how it fails for Android -- so the example that exists to demonstrate portability builds nowhere, and that is a criterion rather than an assumption. The task list is now per-repo with its dependency edges, three of which are not reorderable, and it names the one split that is by construction rather than by choice: a new package is a new repository in this ecosystem. The self-review covers the nine angles. Two are worth reading: the one place simplicity was deliberately refused (E2's two cfg lines), and where the risk concentrates (E3, which is new code in a new package and therefore cannot regress anything that exists).
1 parent f7e4584 commit 0995ea9

2 files changed

Lines changed: 229 additions & 21 deletions

File tree

.agents/docs/2026-09-11-sdk-toolchains-and-ios-local-verification.md

Lines changed: 227 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,24 @@ subject: targets
33
status: active
44
---
55

6-
# SDK toolchains, the payload/engine seam, and iOS local verification
6+
# SDK toolchains, the payload/engine seam, and openkal across iOS, Android and Web
77

8-
**Status:** design, for review. Nothing here is implemented.
8+
**Status:** design. Items A and E1 are measured and merged; the rest is for
9+
review before implementation.
910

10-
**Scope.** Four items, in dependency order. The first is measured and only needs
11+
**Scope.** Five items, in dependency order. The first is measured and only needs
1112
writing down; the second removes something rather than adding; the third is the
12-
only structural change; the fourth is the one with a new capability.
13+
only structural change in the engine; the fourth adds a platform; the fifth
14+
closes the same three platforms for openkal, where one is already done, one is
15+
a reuse and one is new software.
1316

1417
| | item | shape |
1518
|---|---|---|
1619
| A | `aarch64-linux-android` becomes `verified`, and the extraction tool changes | evidence exists, two edits |
1720
| B | the `ndk` toolchain alias is withdrawn | deletion |
1821
| C | a payload describes itself, so the engine stops knowing the NDK | new contract at an existing seam |
1922
| D | iOS builds and runs on the simulator, locally and in CI | one parameter, one row column, one runner package |
23+
| E | openkal's cross-build closes on all three platforms, with examples and docs | one shared, one located, one new implementation |
2024

2125
## A. Android's device row has an execution path after all
2226

@@ -254,26 +258,226 @@ recorded rule is that a host surface must be minimal, named and written down,
254258
and two named items on one host is the whole of it. Neither is a fallthrough:
255259
each is reached deliberately, and its absence is an error that names it.
256260

261+
## E. openkal across the three platforms
262+
263+
openkal is the other half of "a platform is supported": the target rows say a
264+
COMPILER can emit for a platform, and openkal says a program written against
265+
one interface can RUN there. The three platforms are three different answers,
266+
and the spec's own capability model is what makes the third one legitimate
267+
rather than a compromise.
268+
269+
### E1. Android shares the Linux implementation — done
270+
271+
`openkal-linux` is written on the Linux kernel's system-call interface and
272+
borrows nothing from any C library, which is what lets it sit beneath one.
273+
Android's kernel IS Linux, the per-architecture syscall ABI is identical, and
274+
`src/sys.h` branches on `__x86_64__` / `__aarch64__` — the architecture, not
275+
the operating system.
276+
277+
So a portable program needs no new line: `cfg(os = "linux")` is true for an
278+
Android triple, because Android is an `env` value on a `linux` OS.
279+
280+
Measured and merged (`openkal-linux#26`), against the RELEASED
281+
`mcpp 2026.9.11.3` rather than a working tree:
282+
283+
```
284+
== aarch64-linux-android == 17 objects, ARM aarch64
285+
== x86_64-linux-android == 17 objects, x86-64
286+
both Android ABIs build from this implementation unchanged, and name no C library symbol
287+
```
288+
289+
and a program written against openkal alone — no C library, no `import std`
290+
printed `openkal: 1-2-3` with exit 0 on an API 24 emulator image.
291+
292+
The symbol check is the half worth having. Compiling is the weaker statement;
293+
the property this package exists for is that its objects name no C library
294+
symbol, and on Android the C library is a DIFFERENT one, so a reference that
295+
resolved to glibc by habit would appear as a bionic name. Two compiler-runtime
296+
names are permitted with their reason recorded, both measured to be defined in
297+
the NDK's `libclang_rt.builtins-<arch>-android.a` and in neither bionic
298+
`libc.so`.
299+
300+
### E2. iOS reuses the macOS implementation
301+
302+
The same argument as Android, on Apple's side: iOS and macOS share the Darwin
303+
kernel, and `openkal-macos` is arch-dispatched the same way. What blocked it
304+
was never the implementation — it was that there was nothing to build against,
305+
and item D is what supplies that.
306+
307+
So E2 is a consequence of D rather than new work: with `sdk_path("iphoneos")`
308+
and `sdk_path("iphonesimulator")` resolving, `openkal-macos` compiles for the
309+
three iOS rows, and a `cfg` selection picks it up. The selection needs one
310+
decision: `cfg(os = "ios")` is its own OS value, so a portable program's
311+
`cfg(os = "macos")` line does NOT cover iOS the way `cfg(os = "linux")` covers
312+
Android. The two are not symmetric, and the reason is the modelling decision
313+
itself — Android is an `env` on `linux`, iOS is its own `os`.
314+
315+
That asymmetry is correct and should not be papered over. A program targeting
316+
both Apple platforms writes:
317+
318+
```toml
319+
[target.'cfg(os = "macos")'.dependencies]
320+
openkal-macos = "0.7.0"
321+
322+
[target.'cfg(os = "ios")'.dependencies]
323+
openkal-macos = "0.7.0"
324+
```
325+
326+
An alternative was considered and rejected: making `cfg(os = "macos")` true for
327+
iOS, or adding a `cfg(apple)` predicate. `Triple::is_apple()` exists in the
328+
engine for exactly this question, so a `cfg(apple)` dimension is expressible —
329+
but it would be a NEW cfg dimension introduced for one consumer's convenience,
330+
and the honest reading is that two OS values are two dependency lines. If a
331+
third Apple platform arrives the case can be revisited with three consumers
332+
rather than one.
333+
334+
### E3. Web needs a new implementation, above a C library
335+
336+
Emscripten is the one platform that changes the model. There is no kernel and
337+
there are no system calls to issue: Emscripten supplies its own C library over
338+
a JavaScript host. So an implementation cannot be written the way
339+
`openkal-linux` is — BENEATH a C library — and must sit ABOVE one, which the
340+
specification explicitly permits: "an implementation may be built upon a C
341+
library, beneath one, or without one."
342+
343+
`openkal-emscripten` is therefore new software rather than a sharing decision.
344+
It is also the SMALLEST of the three implementations, because forwarding to a
345+
POSIX-shaped libc is thinner than issuing syscalls: the Linux implementation is
346+
3918 lines and the macOS one 3512, most of which is calling-convention and
347+
kernel-structure detail that an above-libc implementation does not have.
348+
349+
**The partial surface is legitimate, and the spec says how.** Clause 6.2 gives
350+
three times, each the earliest at which the information exists:
351+
352+
| time | mechanism | question |
353+
|---|---|---|
354+
| dependency resolution | the package declares what it provides | may this program be built against this implementation |
355+
| link | an undefined symbol | was an interface used that the implementation does not provide |
356+
| run | a capability word | how does it behave within an interface it provides |
357+
358+
So the three groups of the 137-symbol surface get three different treatments,
359+
and none of them is "present and always fails" — which the spec names as a
360+
defect *because the caller cannot tell*:
361+
362+
| group | treatment | why |
363+
|---|---|---|
364+
| `stream`, `fs`, `time`, `env`, `memory`, `random`, `abort`, `terminal` | provided, forwarding to Emscripten's libc | MEMFS and the JS host serve all of these |
365+
| `net`, `datagram`, `task`, `timeout` | provided, with the capability word reporting what is exercisable | sockets exist but are WebSocket-shaped; threads exist but need a link flag. `KAL_EXEC_PROP_AVAILABLE` is the precedent: the interface is provided either way and whether it can be exercised is read |
366+
| `process`, `exec`, `space` | NOT PROVIDED | there is no fork, no exec and no second address space. A program that uses one fails at LINK naming the symbol, which is clause 6.2's second time and is the mechanism rather than a defect |
367+
368+
The third row is the design decision worth stating plainly: **an absent symbol
369+
is the report.** Providing `kal_process_spawn` on wasm so that it returns an
370+
error would be the shape the spec forbids, and it would also be undetectable
371+
until run time on a platform where the answer is known at link time.
372+
373+
### E4. The examples and the documentation
374+
375+
**The `portable` example is stale on every target, not just the new ones.**
376+
Measured: `openkal/examples/portable` fails to compile for the host with
377+
`unknown type name 'kal_spawn'` — it pins `openkal = "0.10.0"` while using an
378+
API from a different version, and it fails IDENTICALLY on `x86_64-linux-android`.
379+
So the example that exists to demonstrate portability does not build anywhere,
380+
and that has to be fixed before it can demonstrate three more platforms.
381+
382+
Once it builds, it gains the three platform legs as `cfg` dependency lines and
383+
a README recording what was measured on each — which is the same discipline
384+
`examples/13-platform-targets` follows in mcpp: every claim in the README is
385+
measured against the artefact the README describes, not carried over from
386+
another one.
387+
388+
`docs/24-openkal-cross.md` (and its Chinese copy) already carries an
389+
"Android, Web and iOS under this model" section written when Web had no
390+
implementation. It needs the one change that makes it current: the Web row
391+
stops reading "needs an implementation written above a C library" and starts
392+
naming `openkal-emscripten`, with the three-group table above as the reason a
393+
partial surface is a conformant one.
394+
395+
## Self-review across the angles
396+
397+
Recorded because the plan is large enough that an angle left unexamined is an
398+
angle that fails late.
399+
400+
**Architecture.** Item C is the only structural change, and it moves knowledge
401+
in the direction the rest of the ecosystem already flows: the recipe that
402+
installs a payload is the thing that knows its layout. Items A, B, D and E2
403+
remove or reuse rather than add. E3 adds a package, not a mechanism.
404+
405+
**Stability.** C's descriptor is additive and absence is today's behaviour, so
406+
no released payload changes meaning. D touches a function with three existing
407+
callers behind a default argument. The risk concentrates in E3, which is new
408+
code — and new code in a new package cannot regress anything that exists.
409+
410+
**Elegance and simplicity.** B is a deletion. D turns out to be a parameter
411+
rather than a concept, because the SDK locator already exists. The one place
412+
simplicity was deliberately refused is E2's two `cfg` lines: a `cfg(apple)`
413+
dimension would be shorter to write and would introduce a dimension for one
414+
consumer.
415+
416+
**User experience.** Nothing has to be declared for any of the five platforms:
417+
the row's pin is the default and installs on demand. What a user writes is the
418+
deployment floor and, for a program over openkal, one `cfg` line per platform.
419+
The refusals carry their own row's reason, which is what B and the capability
420+
gate are for.
421+
422+
**Compatibility and seamless upgrade.** No floor moves. `index.toml`'s
423+
`min_mcpp` is untouched, so a client stopped at an older engine keeps reading
424+
the whole index. The engine's CI pin moves in the repositories whose CI needs
425+
the new rows, which is the distinction between a pin and a floor.
426+
427+
**Cross-platform.** The host surface this plan adds is two named items on one
428+
host (the iOS SDK via `xcrun`, `simctl` via the runner), both category (b) of
429+
the recorded policy. Everything else resolves through the ecosystem. Three of
430+
the five items are verified on hosts other than the one I am working from,
431+
which is where the last several defects in this area came from.
432+
433+
**Consistency.** One name per payload after B. One slot in the fingerprint for
434+
the deployment floor, because a target is Apple or Android and never both. One
435+
tier vocabulary across four documents, now enforced by
436+
`check_target_tiers.py`.
437+
438+
**Test coverage.** Every claim has a criterion that fails when its subject is
439+
removed, listed below. The two that cannot be measured from a Linux machine are
440+
named as such, with the runner that measures them instead.
441+
257442
## Task list and dependencies
258443

259444
```
260-
A1 android-system-image: debugfs -> xim:7zip xim-pkgindex
261-
A2 aarch64-linux-android -> verified + docs mcpp (independent)
262-
B1 withdraw the `ndk` alias mcpp
263-
C1 sdk_path(sdk) parameter mcpp (D depends)
264-
C2 .mcpp-toolchain.json reader + malformed refusal mcpp
265-
C3 android-ndk.lua writes the descriptor xim-pkgindex (after C2)
266-
D1 three iOS rows: pin llvm, located sysroot mcpp (after C1)
267-
D2 -m*-version-min per row mcpp (after D1)
268-
D3 xim:apple-simulator-tools with simctl-run xim-pkgindex
269-
D4 runner wired for the two sim rows mcpp (after D3)
270-
D5 docs: 20-toolchains SDK section + zh, examples/13 mcpp
445+
repo id task depends on
446+
-------------- -- ------------------------------------------ ----------
447+
xim-pkgindex A1 android-system-image: debugfs -> xim:7zip -
448+
mcpp A2 aarch64-linux-android -> verified + 4 docs -
449+
mcpp B1 withdraw the `ndk` alias -
450+
mcpp C1 sdk_path(sdk) parameter -
451+
mcpp C2 .mcpp-toolchain.json reader + refusal -
452+
xim-pkgindex C3 android-ndk.lua writes the descriptor C2 shipped
453+
mcpp D1 three iOS rows: llvm pin, located sysroot C1
454+
mcpp D2 -m*-version-min per row D1
455+
xim-pkgindex D3 xim:apple-simulator-tools (simctl-run) -
456+
mcpp D4 runner wired for the two simulator rows D3
457+
mcpp D5 docs: 20-toolchains + zh, examples/13 A2 D1
458+
openkal E3a openkal-emscripten: the new implementation -
459+
openkal E3b its conformance + independence CI E3a
460+
openkal E4a fix examples/portable (stale on every host) -
461+
openkal E4b examples/portable: three platform legs E4a E3a D1
462+
mcpp E4c docs/24-openkal-cross + zh: name the Web impl E3a
271463
```
272464

273-
`A2`, `B1`, `C1`, `C2`, `D1`, `D2`, `D4`, `D5` are one mcpp PR with a version
274-
number, per the single-PR rule. `A1`, `C3`, `D3` are one xim-pkgindex PR. `C3`
275-
must land after `C2` ships, because a descriptor no engine reads is a file
276-
nothing checks.
465+
**Per-repo single PRs.** `A2 B1 C1 C2 D1 D2 D4 D5 E4c` are one mcpp PR with a
466+
version number -- #612, retitled. `A1 C3 D3` are one xim-pkgindex PR.
467+
`E3a E3b E4a E4b` are one openkal-side PR, except that `openkal-emscripten` is
468+
a NEW PACKAGE and therefore its own repository by this ecosystem's convention
469+
(one package, one repository) -- which is a split by construction rather than
470+
by choice.
471+
472+
**Three edges are not reorderable.** `C3` after `C2` ships, because a
473+
descriptor no engine reads is a file nothing checks. `D4` after `D3`, because a
474+
`runner` naming a program no package provides is a manifest that cannot
475+
resolve. `E4b` after `D1`, because the example cannot have an iOS leg before
476+
the rows can build.
477+
478+
**E2 has no task.** It is a consequence of `D1`: once the SDK resolves,
479+
`openkal-macos` compiles for the iOS rows unchanged, and the only artefact is
480+
two `cfg` lines in `E4b`'s example.
277481

278482
## Criteria
279483

@@ -289,6 +493,10 @@ write it down.
289493
| D: iOS builds | macOS runner, `xim:llvm` plus the located SDK, artefact is Mach-O arm64 with the iOS platform in `LC_BUILD_VERSION` |
290494
| D: the simulator runs | macOS runner, `mcpp run --target aarch64-ios-sim` prints `1-2-3` |
291495
| D: the host surface is bounded | on a macOS runner with `xcode-select` pointing nowhere, both iOS rows fail with a message naming the SDK -- and no other row changes |
496+
| E1: Android shares the implementation | merged and green against the RELEASED engine: both ABIs build, objects name no C library symbol, and a program over openkal alone ran on an emulator |
497+
| E2: iOS reuses it | `openkal-macos` compiles for the three iOS rows on a macOS runner, and its objects name no C library symbol -- the same check the Android leg applies, against a third libc |
498+
| E3: the Web implementation conforms | the conformance suite passes for the groups it provides; and a program using `kal_process_spawn` fails at LINK naming the symbol, which is the criterion that the absent groups are absent rather than present-and-failing |
499+
| E4: the examples build | `examples/portable` builds for the host and for all five platforms it names -- it currently builds for NONE, which is why this is a criterion and not an assumption |
292500

293501
**One premise needs measuring before D is scheduled**: that GitHub's macOS
294502
runners ship both an iOS SDK and a bootable simulator. If they ship the SDK but

.agents/docs/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Records that declare one. Everything else is listed by date below.
4646

4747
### targets
4848

49-
- [SDK toolchains, the payload/engine seam, and iOS local verification](2026-09-11-sdk-toolchains-and-ios-local-verification.md) — active
49+
- [SDK toolchains, the payload/engine seam, and openkal across iOS, Android and Web](2026-09-11-sdk-toolchains-and-ios-local-verification.md) — active
5050
- [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
5151

5252
### triage
@@ -58,7 +58,7 @@ Records that declare one. Everything else is listed by date below.
5858
### 2026-09
5959

6060
- [Six open issues: what each one actually is, and what would answer it](2026-09-11-six-open-issues-analysis.md) — active
61-
- [SDK toolchains, the payload/engine seam, and iOS local verification](2026-09-11-sdk-toolchains-and-ios-local-verification.md) — active
61+
- [SDK toolchains, the payload/engine seam, and openkal across iOS, Android and Web](2026-09-11-sdk-toolchains-and-ios-local-verification.md) — active
6262
- [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
6363
- [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
6464
- [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

0 commit comments

Comments
 (0)