You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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).
# SDK toolchains, the payload/engine seam, and iOS local verification
6
+
# SDK toolchains, the payload/engine seam, and openkal across iOS, Android and Web
7
7
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.
9
10
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
11
12
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.
13
16
14
17
|| item | shape |
15
18
|---|---|---|
16
19
| A |`aarch64-linux-android` becomes `verified`, and the extraction tool changes | evidence exists, two edits |
17
20
| B | the `ndk` toolchain alias is withdrawn | deletion |
18
21
| C | a payload describes itself, so the engine stops knowing the NDK | new contract at an existing seam |
19
22
| 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 |
20
24
21
25
## A. Android's device row has an execution path after all
22
26
@@ -254,26 +258,226 @@ recorded rule is that a host surface must be minimal, named and written down,
254
258
and two named items on one host is the whole of it. Neither is a fallthrough:
255
259
each is reached deliberately, and its absence is an error that names it.
256
260
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.
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
271
463
```
272
464
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.
277
481
278
482
## Criteria
279
483
@@ -289,6 +493,10 @@ write it down.
289
493
| D: iOS builds | macOS runner, `xim:llvm` plus the located SDK, artefact is Mach-O arm64 with the iOS platform in `LC_BUILD_VERSION`|
290
494
| D: the simulator runs | macOS runner, `mcpp run --target aarch64-ios-sim` prints `1-2-3`|
291
495
| 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 |
292
500
293
501
**One premise needs measuring before D is scheduled**: that GitHub's macOS
294
502
runners ship both an iOS SDK and a bootable simulator. If they ship the SDK but
Copy file name to clipboardExpand all lines: .agents/docs/README.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -46,7 +46,7 @@ Records that declare one. Everything else is listed by date below.
46
46
47
47
### targets
48
48
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
50
50
-[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
51
51
52
52
### triage
@@ -58,7 +58,7 @@ Records that declare one. Everything else is listed by date below.
58
58
### 2026-09
59
59
60
60
-[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
62
62
-[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
63
63
-[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
64
64
-[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