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
332 changes: 332 additions & 0 deletions .agents/docs/2026-08-20-openkal-completeness-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
# openkal: plan for industrial completeness

This document plans the extension of openkal from its core set to a
specification sufficient to host a C library, and through it the ordinary
software of a hosted system. It does not restate the decisions already taken;
those are recorded in [`2026-08-20-openkal-design.md`](2026-08-20-openkal-design.md)
and in the specification itself, and this plan treats them as given.

## 1. Method

The interfaces are derived from the kinds of resource an execution environment
provides. They are not derived from what a particular program calls.

The distinction is not stylistic, and the specification already records why: a
boundary shaped by one client acquires that client's assumptions and obliges
every other environment to reproduce them. POSIX was shaped by the C library of
its time; the first preview of the WebAssembly System Interface was shaped by
POSIX; an openkal shaped by a compiler would be the third instance of the same
error.

Programs are therefore used to **validate** the derivation and never to produce
it. A program that cannot be hosted indicates that a resource kind has been
omitted or decomposed incorrectly. A program that can be hosted only by
reproducing its own assumptions indicates nothing about openkal, and something
about the program.

### 1.1 The two validation subjects, and why they form a stack

```
an ordinary POSIX program, such as the GCC toolchain
│ the C standard library and POSIX
musl, ported to openkal
│ the openkal C application binary interface
an openkal implementation: Linux, Windows, a microkernel, bare metal
```

The two subjects are not parallel. Porting a C library is the enabling work,
and porting it once causes the software above it to run on every openkal
implementation without further porting. The compiler is then an end-to-end
check of that claim rather than a separate exercise.

The arrangement also settles a question the derivation raises. A compiler
assumes a global namespace of paths; that assumption is not universally
satisfiable, since capability-based kernels and the second preview of the
WebAssembly System Interface have no such namespace. The resolution of a path
against a root the environment supplies is therefore work that belongs in the C
library, performed once, rather than in every program or in the specification.

### 1.2 Evidence gathered

Measurements taken while preparing this plan. They inform coverage, which is a
question about sufficiency; they do not inform shape.

| Subject | Measurement |
| --- | --- |
| GCC driver and `cc1plus` | 189 undefined symbols, of which 149 face the environment |
| Process creation in GCC | `posix_spawn` and `posix_spawnp`; neither `fork` nor `execve` appears |
| Path operations in GCC | `open`, `stat`, `lstat`, `mkdir`, `unlink` alongside `fstatat`: a global namespace is assumed |
| musl, static archive | file and directory operations 48, process and signal 21, memory 7, threads 81, time 8, network 23 |
| musl thread construction | the pthread layer is built upon a wait-and-wake primitive rather than upon kernel mutexes |

Two of these are load-bearing.

The first is that GCC creates processes by spawning rather than by duplicating
the calling image. A specification that copied the POSIX decomposition would
have obliged an implementation on Windows to reproduce `fork`, which cannot be
done faithfully, and would therefore have failed the specification's own
admission criterion. The resource-derived form and the observed usage agree,
which is corroboration rather than derivation.

The second is that musl constructs mutexes and condition variables from a
primitive that suspends an execution context until another context wakes it.
The kernel boundary is therefore the primitive, and the synchronisation objects
are library constructions above it. An interface offering mutexes would be
placing a library at the kernel boundary.

## 2. The four decisions this plan settles

The core set is specified and published. Four questions were deferred, and each
must be answered before the specification can host a C library.

### 2.1 Optional capabilities

An earlier draft deferred the mechanism. The rule adopted here follows from a
distinction the specification already draws for another purpose.

> An **operation** that an implementation may lack becomes an interface of its
> own. A **property** that varies between implementations is reported by a
> capability word.

The justification is that the two are not alike. An operation that is present
and always fails is the defect the specification rejects in clause 6.4; the
remedy is that its absence is expressed by its absence, which at the granularity
of an interface means the linker reports it. A property, by contrast, cannot be
called. Whether paths are compared case-sensitively, whether a clock advances
while the machine is suspended, what the granularity of an allocation is: these
are facts, and a program adapts to them rather than invoking them.

Capability words are the ordinary mechanism of every system that has faced this
question, including `sysconf`, `pathconf`, the auxiliary vector and the
processor identification instruction. They are also the only mechanism available
once the implementation is chosen at link time, which the specification's
layering requires.

Information therefore becomes available at three times, each being the earliest
at which it exists.

| Time | Mechanism | What it answers |
| --- | --- | --- |
| Dependency resolution | the implementation package declares the interfaces it provides | may this program be built against this implementation |
| Link | an undefined symbol | was an interface used that the implementation does not provide |
| Run | a capability word | how does this implementation behave within an interface it provides |

### 2.2 Interfaces beyond the core

Derived from resource kinds. The core set is unchanged.

| Interface | Resource | Class |
| --- | --- | --- |
| `openkal.abort` | termination | core |
| `openkal.memory` | a region of the address space | core |
| `openkal.stream` | a byte stream | core |
| `openkal.env` | the parameters a program receives at inception | standard |
| `openkal.time` | a time source | standard |
| `openkal.fs` | a directory, and an open file | standard |
| `openkal.process` | a program image that has been started | standard |
| `openkal.task` | an execution context, and a suspension primitive | standard |
| `openkal.net` | an endpoint | optional |
| `openkal.module` | a code image that has been loaded | optional |
| `openkal.entropy` | a source of unpredictable bits | optional |
| `openkal.event` | readiness of a set of resources | reserved |

*Standard* denotes an interface an implementation hosting a C library provides.
*Optional* denotes one it may omit without ceasing to host a C library, at the
cost of the facilities built upon it. *Reserved* denotes a name whose contents
are not yet normative.

`openkal.event` is reserved rather than specified because readiness
notification is the interface at which environments differ most, and because a
C library can be hosted without it: blocking operations suffice, and the
facilities that require readiness are those a program uses when it declines to
block. Specifying it prematurely would produce an interface shaped by whichever
environment was consulted first.

#### 2.2.1 `openkal.fs`

The resource is a directory or an open file, and operations are relative to a
directory the program holds. There is no global namespace of paths.

The reason is the admission criterion. A global namespace is not available in a
capability-based kernel, and an implementation on such a kernel would have to
construct one. Relative operations, by contrast, are natural on every
environment considered: they are the primitive on capability systems, and on
POSIX they are the `…at` family, which musl already uses to implement the
global forms.

The consequence is that the resolution of an absolute path is work performed by
the C library against a root directory the environment supplies at inception.
This is the arrangement the second preview of the WebAssembly System Interface
adopts, and it is what allows a program to be confined without its cooperation.

#### 2.2.2 `openkal.process`

The resource is a program image that has been started. The operations are to
start one, to wait for it, and to request its termination.

Duplication of the calling image is not among them. It is not universally
implementable, and the observed behaviour of the validation subject does not
require it.

#### 2.2.3 `openkal.task`

The resource is an execution context sharing the address space, together with a
primitive that suspends a context until another wakes it.

Mutexes and condition variables are not among the operations. They are
constructions above the primitive, as the measurement of musl demonstrates, and
placing them at the kernel boundary would place a library there.

### 2.3 Concurrency

An implementation shall permit concurrent operations upon distinct handles.

Concurrent operations upon one handle shall not damage the implementation's own
state. The order in which they take effect, and whether the bytes of one
transfer may be separated by those of another, are unspecified.

Atomicity below a threshold, which POSIX guarantees for pipes, is not required.
It is not universally implementable, and a specification that required it would
oblige an implementation to introduce buffering it does not otherwise need.

### 2.4 Ownership

Handles obtained from the core interfaces are borrowed and are not released.

Handles obtained from `openkal.fs`, `openkal.process`, `openkal.net` and
`openkal.module` are owned, and each of those interfaces provides the operation
that releases one. An implementation shall not treat a released handle as valid.

The recommended construction divides the handle word into an index and a
generation, incrementing the generation on release. The specification does not
require it: it requires only the property, which this construction achieves
without a lookup table and therefore without the compatibility layer that clause
7.1 excludes.

## 3. Work breakdown

### 3.1 Groups and their dependencies

```
S. specification: clauses for the four decisions ─┬─► I. interface modules ─┬─► L. Linux implementation ─┬─► M. musl port ──► G. compiler validation
│ │ │
└─► C. conformance ──────┴────────────────────────────┘
```

| Group | Tasks | Depends upon |
| --- | --- | --- |
| S | capability rule, interface inventory, concurrency, ownership | this plan |
| I | `env`, `time`, `fs`, `process`, `task` declaration modules | S |
| L | the same five for Linux | I |
| C | behavioural suites, surface comparison, capability-word agreement | I |
| M | musl retargeted onto openkal | L, C |
| G | the compiler built and run above the ported library | M |

Groups I and C are independent of each other and both depend upon S. Group L
may begin as soon as the declarations of a given interface exist, so the five
interfaces proceed in parallel rather than in sequence.

### 3.2 Sequence within an interface

Each interface is taken through the same five steps, and the order is not
interchangeable.

1. State the resource and the operations that follow from it.
2. Determine which operations may be absent, and separate those into their own
interface.
3. Determine which properties vary, and assign them positions in the capability
word.
4. Write the Linux implementation, which is where a decomposition error becomes
visible.
5. Write the conformance suite, which is where a claim becomes checkable.

Step 4 precedes step 5 deliberately. The implementation of the core set
demonstrated that a decomposition error is visible to an implementer and not to
a reader of the specification: positioning was excluded from the stream
interface by argument, and the exclusion was confirmed when the Linux
implementation showed that positioning is a property of the individual
descriptor.

## 4. Assessment

### 4.1 Architecture

The interface remains the unit of provision and of versioning, and the
specification retains ownership of every module. The extension adds interfaces;
it does not add mechanisms, with the single exception of the capability word,
which is confined to properties.

### 4.2 Stability

Structure layouts remain frozen. The capability word introduces a new obligation:
a position once assigned retains its meaning, and a property that ceases to vary
is not reclaimed. Unassigned positions are reserved and read as zero, so that a
program compiled against a later specification behaves correctly against an
earlier implementation.

### 4.3 Simplicity

The rule distinguishing operations from properties replaces the three mechanisms
that were drafted and withdrawn: a record of capability flags, a configuration
file that accompanied it, and a set of fallback overloads detected through
argument-dependent lookup. Each was more elaborate than the rule that replaces
them, and each solved a problem that a correct decomposition does not have.

### 4.4 Consumer experience

A program declares the interfaces it requires. A program built against an
implementation that lacks one fails during dependency resolution, with the
interface named. A program that reaches an interface it did not declare fails
at link, with the function named. Neither failure is deferred to run time.

### 4.5 Compatibility

The core set does not change. An implementation of the core set that predates
this extension remains conforming, and a program that uses only the core set is
unaffected.

### 4.6 Portability

The interfaces are derived rather than borrowed, and each is checked against
four environments before it is specified: a hosted system with a global
namespace, a hosted system without one, a capability-based kernel, and a
freestanding target. An interface that any of the four could satisfy only by
constructing a compatibility layer is decomposed again rather than admitted.

### 4.7 Upgrade

An implementation adds interfaces without altering those it already provides. A
program observes the addition through dependency resolution. No existing
manifest requires modification.

## 5. Criteria for completeness

The extension is complete when the following hold. Each is an observation rather
than a judgement.

1. musl, retargeted onto openkal, builds and passes its own test suite against
the Linux implementation.
2. The compiler toolchain, built against that library, compiles and links a
program, and the program runs.
3. A second implementation exists for an environment without a global path
namespace, and the same library binary interface is satisfied by it.
4. The conformance suite verifies, for every interface, the behaviour of the
operations provided, the absence of those not provided, and the agreement
between each capability word and the behaviour it describes.
5. No implementation requires a table, a registry or a name resolver in order to
satisfy the specification.

Criterion 3 is the one that distinguishes this work from a portable C library.
A specification satisfied only by environments resembling the one it was written
against has not been validated, however many programs it hosts.

## 6. Matters this plan does not settle

| Matter | Status |
| --- | --- |
| `openkal.event` | reserved; see 2.2 |
| Symbol versioning | the evolution rule prohibits change rather than permitting coexistence, and an ecosystem that outgrows the prohibition will require a mechanism this specification does not define |
| Locale and character encoding | properties of a C library rather than of a kernel boundary; the specification does not address them |
| Signals as a general mechanism | the process interface provides termination; asynchronous delivery to a running program is deferred with `openkal.event` |
53 changes: 51 additions & 2 deletions .agents/docs/2026-08-20-openkal-design.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# openkal 设计方案:通用内核 ABI 规范

**状态**:**0.1 已实施并发布**。规范与声明模块在
**状态**:**0.2 已实施并发布**(0.1 的分层已被 review 推翻,见 §21)。规范与接口模块在
[`mcpplibs/openkal`](https://github.com/mcpplibs/openkal),Linux 参考实现在
[`mcpplibs/openkal-linux`](https://github.com/mcpplibs/openkal-linux),两者均为 `0.1.0`。
[`mcpplibs/openkal-linux`](https://github.com/mcpplibs/openkal-linux),两者均为 `0.2.0`。
实施计划与结果见
[`2026-08-20-openkal-implementation-plan.md`](2026-08-20-openkal-implementation-plan.md)。

Expand Down Expand Up @@ -1196,3 +1196,52 @@ extern "C" {
| ⚠️ 不做的事 | 它**不算**第三方后端,也不算第三个后端(linux/bare 是官方的两个) |

⇒ 判据仍然是**别人来不来**,而这份实现让「来」这件事从一个季度变成一个周末。

---

## 21. ⚠️ 0.1 的分层被推翻:实现不该拥有应用 import 的名字

0.1 让**实现**提供 `openkal.stream`,规范包提供 `openkal.decl.stream` 由实现再导出。
review 指出这与「openkal 是接口」矛盾 —— **应用 import 的那个名字落在了规范管不到的一方手里**。

### 21.1 我的理由为什么站不住

那套安排的**唯一**用途是让可选能力可经 ADL 探测,而 ADL 要求实现的声明对消费者可见。
⚠️ **而 0.1 一个可选能力都没有** —— `write_vectored` 是我为了演示机制放进去的。

⇒ **机制服务的是一个当时不存在的需求**,却付出了「实现拥有接口名」这个真实代价。

### 21.2 0.2 的分层(实测成立)

```
应用 ──import──► openkal ◄──import── 实现
│ │
└──────────── 链接 ─────────────────┘
```

- 规范包提供 `openkal.types` / `.abort` / `.stream` / `.memory`
- **实现零模块**,只贡献定义;它 import 与消费者**同一个**接口,因为它要定义自己声明的东西
- ⇒ **实现无法扩展接口** —— 这不是需要执行的规则,是这个安排的推论

ⓘ 实测:后端零模块的工程编译运行通过;缺实现时报
`undefined reference to kal_stream_write`(链接期,可读)。

### 21.3 代价与去向

| | 0.1 | 0.2 |
|---|---|---|
| 缺实现 | 编译期,点名模块 | **链接期**,点名函数 |
| 可选能力探测 | ADL,编译期 `if constexpr` | **无**(0.2 无可选能力) |
| 实现能否扩展接口 | 能加重载,靠 conformance 封 | **不能**,由安排本身排除 |
| 碎片化风险 | 有,需 surface diff 兜底 | **无** |

⇒ 可选能力的机制**推迟到真的出现时再定**,SPEC 6.3 记录了两个候选与各自的约束
(以及那三条实测:限定名 vs 非限定名、ADL 到不了未 import 的模块、点号延伸导致模块自环)。

### 21.4 元结论

这是本轮第三次同一形状:**我为一个尚不存在的需求设计了机制,并为它付出了真实代价。**

前两次是 `openkal.namespace`(为统一命名合并了两种资源)与 `cfg(mmu)`(把 openarch 的
结论搬进 openkal)。⇒ **判据应当是「今天有没有这个需求」,而不是「将来会不会有」** ——
将来的需求可以用「推迟决定并记录约束」来接,代价远低于提前实现一个错的机制。
Loading
Loading