Skip to content

Commit 5fdb4e0

Browse files
openkal 0.8: five optional interfaces, admitted against a stated criterion (#13)
* spec: version 0.8 adds five optional interfaces The five are terminal control, connections, datagrams, address spaces, and a bound upon operations that would otherwise wait without end. Each is optional in the sense clause 3 defines: an environment with no network is not deficient for providing neither openkal.net nor openkal.datagram, and one with no memory management unit cannot provide openkal.space and is not deficient either. Clause 6.1 expresses each absence as the absence of a definition at the link, so a program requiring one is refused when it is built rather than when it runs. The admission criterion applied to each was the same, and three candidates did not meet it. An interface enters the specification only when it is a minimal capability that every kernel has, cannot be composed from the atoms already specified, and is usable in both directions --- composable from above and implementable from below. What can be composed belongs in a library above the line. ## Decomposition openkal.terminal is a separate interface rather than operations upon openkal.stream, for the reason clause 6.4 gives when it places positioning in openkal.fs: the behaviour varies between the RESOURCES of the stream interface rather than between implementations. The pair is get/set and not two setters, because a program that turns line editing off must be able to restore what was there rather than a default. openkal.net and openkal.datagram are separate from each other on the same grounds. A message boundary is not a property a byte stream has; ordering is not a property a datagram has. Datagrams are also the easier half to provide, so an implementation supplying only that one is ordinary rather than deficient. openkal.space is not fork. Clause 7.1 refuses to require the duplication of an address space AND its execution state; what is specified is the first half alone. A library above the interface reaches fork by saving its own execution state before the clone and restoring it in the new context, which is composition performed with the compiler's own facilities. openkal.timeout states a duration rather than an instant, because kal_task_wait already does and a second spelling of one idea is what this specification most consistently refuses. An expired bound is kal_err_again; the closed error set of clause 5.2 required no addition. ## Types struct kal_endpoint carries bytes and a length rather than a tagged union of families. The length distinguishes one kind of address from another and is a VALUE rather than a layout, so the set of lengths defined may grow while clause 5.3 continues to hold the structure fixed. An implementation refuses a length it does not know rather than reading it as one it does. It is declared in types.h because both openkal.net and openkal.datagram use it and either may be provided without the other. ## Additions to openkal.process kal_process_spawn_with starts a program receiving exactly the directories named, and is the inverse of kal_fs_preopen under clause 7.11. kal_process_channel yields a pair of streams of which one end crosses a spawn boundary; both ends are owned, and the release is declared beside them because a parent that does not release the far end never observes the end of input on its own. Both are second declarations rather than arguments added to existing ones, because clause 8 forbids altering one that exists. ## Verification declarations 91 names, compiled without the environment's headers types 150 declarations, no backend type named modules the five new interfaces compile The two entries clause 6.3 gains record readiness notification and an instant rather than a duration, with the property of implementations that excluded each. Clause 11 gains six entries. The entry on duplication of the calling image states that fork is refused as an OPERATION and that the atoms from which a library may compose it are now specified, so that a later reader does not conclude the capability was excluded along with the operation. * spec: openkal.space is one operation, not two Withdrawn while the first implementation was being written, which is the point at which the defect became visible. The interface separated the copying of an address space from the starting of a context in it, so that a caller held a space as a handle and started a context in it afterwards. No environment this specification targets has that pair as a primitive. On Linux the copy and the start are one act: clone(2) copies the address space and begins execution in the copy, and there is no form that does the first without the second. An implementation asked to provide the separated form would therefore have to start a context anyway, park it upon a waiting primitive, and build a channel by which to tell it what to run afterwards. Clause 7.1 states the test and the verdict: an implementation that must maintain a registry in order to satisfy the specification indicates that the specification has taken a shape borrowed from one environment, and the shape is at fault rather than the implementation. The separated form also had an unstated question it could not answer. If a caller copies a space, modifies its own memory, and then starts a context, the copy the context sees is the one taken at the first call under one reading and at the second under the other. The single operation does not have the question. removed struct kal_space, kal_space_clone, kal_space_destroy retained kal_space_start, now taking the entry directly, and kal_space_props Clause 6.3 records the withdrawn form among the mechanisms considered and not adopted, with the property of implementations that excluded it, so that a later reader proposing it again finds the measurement rather than repeating it. The conformance section was rewritten around the exit status. A started context has an address space of its own, so a store it performs is not visible to the caller; how it ended is the one thing it can report, and the section therefore observes that the entry received its argument by having the entry choose its exit code from it. declarations 89 names, compiled without the environment's headers types 146 declarations, no backend type named * spec: a connection is an owned handle whose stream is borrowed Found by writing the first implementation, which is where a defect of this kind becomes visible: reading the interface did not reveal it, and building it did on the first run. openkal.net yielded a kal_stream and declared it owned. That cannot be implemented under clause 7.2, which requires that a released handle not be treated as valid. A stream handle is whatever the environment's transfer operations take --- on a system of descriptors, a number the environment reuses as soon as it is closed --- so there is nowhere to carry the generation that makes a released handle detectable. An owned handle can carry one; a borrowed stream cannot. openkal.fs had already answered the same question. A file is owned, its stream is borrowed from it through kal_fs_stream, and the stream is released with the file rather than separately. openkal.net now has that shape exactly, with the same wording, because it is the same arrangement. added struct kal_net_conn, kal_uintptr kal_net_stream(struct kal_net_conn) changed connect, accept, peer, local, shutdown and close take a connection changed kal_timeout_accept yields a connection The first form also failed a plainer test. The Linux implementation packed the connection into the handle scheme, kal_stream_read took the packed word as a descriptor, and the transfer read nothing: FAIL: the bytes read are the bytes written FAIL: the peer observes end of input after a half-closure Both observations hold under the corrected shape. The conformance sections name the properties through the modules rather than through the macros. A macro does not cross a module boundary, and a section is a module; the C form's KAL_ names are for a consumer written in C. declarations 90 names, compiled without the environment's headers types 149 declarations, no backend type named conformance 100 observations held, 0 did not hold, against openkal-linux * Add openkal-kit: what is composed, kept out of what is specified The specification admits an interface only when it is a minimal capability every kernel has and cannot be composed from the interfaces already present. That rule keeps openkal implementable on a machine with firmware and nothing else, and it leaves a gap: a program wanting to carry bytes between two of its own contexts, or to turn "127.0.0.1:8080" into an endpoint, has an answer in POSIX and none here, because both are composed rather than primitive. The gap was being filled by the port layer. There is one port layer today and what it composes is POSIX, so a native openkal program either wrote the composition again or took a whole C library. This is where it is written once. ## The contract form decides what is normative Clause 10 states that openkal's contract is a C application binary interface. The kit deliberately is not one: C++ modules in `namespace kal::kit`, exporting no name beginning with `kal_`. A C++ name is mangled to `_ZN3kal3kit…`, so `check-surface.sh --complete` does not read a program that links the kit as an implementation which has added names. "Is this normative" is therefore answered by the shape of what is exported rather than by a sentence saying it is not. A sentence can be overlooked; a mangled name cannot become a C symbol. The consequence is that the kit MAY evolve, which clause 8 forbids the specification from doing. ## What is in it openkal.kit.endpoint writes an endpoint down and reads one back. Clause 3.4 excludes name resolution from the specification, and that exclusion leaves every caller of openkal.net with the same small problem: a configuration file says "127.0.0.1:8080" and kal_net_connect takes bytes and a number. The rejections are what makes it worth having --- a parser accepting "1.2.3" as 1.2.0.3, or "300.1.1.1" as 44.1.1.1, turns a mistyped configuration into a connection somewhere else and reports nothing at the time. openkal.kit.channel carries bytes between two contexts of one address space, from a region of openkal.memory and openkal.task's wait and wake. It is deliberately not kal_process_channel: that interface crosses a spawn boundary and is a kernel facility because a child cannot be handed a pointer into its parent's memory. This one needs nothing from the kernel but a place to sleep. Two details are load-bearing and are recorded where they are made. The close is tested AFTER the indices, because a writer that filled the ring and closed in the same breath leaves bytes behind it and a reader seeing the close first would report an end of input that lost data. Both indices are woken on close, because a writer waiting for room and a reader waiting for bytes must both learn that no more is coming. ## Verification The tests are behavioural. A test that named each entity and did nothing with it would compile against a package whose every operation did nothing. The channel is exercised with a hundred thousand bytes through a four-kilobyte ring, across two contexts, in blocks that are not a divisor of the ring. A payload that fitted would never make the writer wait, and the sleeping and waking the module exists for would go unobserved. openkal-kit: every observation held tools/run-kit-tests.sh substitutes working trees for both the specification and the implementation, as run-conformance.sh does and for the same reason: a change spanning two repositories must be tested against both halves as written. The substitution is asserted rather than assumed --- one that matched nothing would leave the manifest naming a published version, and the run would report on that one while appearing to report on this branch --- and a trap restores the manifests, because leaving a local absolute path in a public repository is a mistake this repository has made once already. * conformance: examine the three operations added to openkal.process They are examined in that interface's section rather than in one of their own, because they are part of it. Clause 6.1 makes an interface provided IN PART a deviation, so an implementation reaching this section has undertaken to provide them and the suite is entitled to observe them. The observations are of behaviour: a channel carries bytes, and closing the far end is observed as end of input on the near one. That last is what the release exists for --- a parent which does not release the far end after a spawn never observes the end of input, which is the deadlock this pair invites and the reason the release is declared beside the operation rather than left to openkal.stream. ⚠️ THE ABI CHECK WOULD HAVE FAILED, AND ONLY WITH `abi` SELECTED. It compares the capability word against the set of positions the specification has assigned, and that set was written out by name: DID NOT HOLD [abi] the capability word contains no position the specification has not assigned An implementation claiming the two positions version 0.8 adds was therefore reported as claiming something unassigned. Measured by narrowing the set again and watching the observation fail, so that the widening is known to be what carries it rather than assumed. That is the second time in this release that adding to an existing interface reached further than adding a new one. The first was the surface checker, which found three backends providing openkal.process in part; this one is in the suite those backends run against themselves. 143 observations held, 0 did not hold, with full,optional and the five interfaces version 0.8 adds * kit: state the surface claim as it was measured, not as it was reasoned The claim is that the kit cannot be mistaken for an implementation because it exports no name beginning with kal_, and the claim holds: measured on its objects, zero of the defined names do. ⚠️ The REASON given was wrong in its particulars. It said a C++ name is mangled to `_ZN3kal3kit…`, and these objects contain no such symbol: the operations are inline and are emitted into consumers rather than exported, so what the objects define are module initialisers like `_ZGIW7openkalW3kitW7channel`. An argument that rests on how a compiler mangles names is worth checking against what the compiler emitted. The structural conclusion is unchanged --- C++ mangling cannot produce a bare kal_ symbol --- but a reader following the comment to verify it would have looked for something that is not there. * Build the portable program, and pin the released engine Three changes, and the second is why the first two were needed. THE ENGINE. MCPP_VERSION moves to 2026.8.27.1, which is now published, and the repository variable MCPP_SOURCE_REF is cleared, so this repository tests the released engine rather than a branch of it. THE EXAMPLE HAD DRIFTED THREE SPECIFICATION VERSIONS. examples/portable named `openkal = "0.5.0"` while declaring itself the program that demonstrates the current contract to a reader who would copy it. The backends it named were equally old. NOTHING BUILT IT, WHICH IS WHY. Its README stated that "an implementation's continuous integration fetches this source ... builds it against itself, and asserts the lines below". Measured across all six implementation repositories, the word `portable` appears in none of their workflows. The sentence described an arrangement that existed nowhere, and a claim with no criterion behind it cannot notice its own subject going stale. The conformance job now builds and runs it against each implementation it tests, substituting the working trees as the conformance runner already does. The criterion carries a denominator: a program that failed to start would print none of its lines, and a check for the absence of failures would hold over an empty log, so the line count is asserted before the count of failures. Measured before the step was written --- the source needed no change, only its manifest: over openkal 0.8 and openkal-linux as written here it prints all eight interface lines and `observations that did not hold: 0`. Two details the measurement settled. The substitution uses relative paths, because this job runs on Windows where MSYS reports `/d/a/...`, which the build tool does not resolve, and because a relative path cannot put one machine's directory into a file that might be committed. It passes no `standalone` feature: that feature makes the implementation supply `_start`, which collides with the C library's crt1.o on a hosted toolchain --- `multiple definition of _start`, measured --- and the conformance runner defaults to no features for the same reason. * Substitute the specification on both routes to it The portable step rewrote the example's manifest and not the implementation's, so one package was reachable two ways and the build refused: error: dependency 'mcpplibs.openkal' is requested as both a path dep (by 'portable') and a git dep (by 'mcpplibs.openkal-linux@path'). The refusal is correct --- two routes to one package are two versions of it --- and the omission was mine rather than the example's. The local measurement that preceded this step did substitute both; only the step did not. Both manifests now name the same directory by relative path, as tools/run-conformance.sh already does for the suite, and each substitution is asserted rather than assumed. * The portable program names no compiler either Building it for the first time found that it did not compile under MSVC: error C3861: '__atomic_load_n': identifier not found error C2065: '__ATOMIC_RELAXED': undeclared identifier error C2065: '__UINT64_TYPE__': undeclared identifier ⚠️ NAMING NO SYSTEM CALL IS NOT THE SAME AS NAMING NO COMPILER. The program's opening sentence claims it names no implementation, no operating system, no descriptor number and no system call, and all of that was true. It was written in GCC and Clang spellings throughout, so the one program whose premise is portability failed on one of the three compilers its own specification is tested against. Two of the five conformance rows would have caught it on any day since it was written; neither ever built it. The integer types become the specification's own. openkal/types.h already carries the MSVC branch, so `kal_u32` and `kal_u64` are the portable spellings, and using them keeps this program inside the vocabulary it exists to demonstrate. `kal_task_wait` takes `const kal_u32*`, which the lock word now is by declaration rather than by coincidence. The atomics become the language's. `std::atomic_ref` refers to the existing word rather than replacing it, which is what allows `kal_task_wait` to take its address, and it is freestanding in C++23 --- so depending on it does not contradict what this program shows. Suspension is a kernel facility and openkal offers it; an atomic operation is a language facility and openkal is right not to. Measured over openkal-linux before pushing: all eight interface lines and `observations that did not hold: 0`, including the contended-counter observation, which is the one that exercises the rewritten lock rather than merely compiling it. --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com>
1 parent 37b4bdd commit 5fdb4e0

43 files changed

Lines changed: 2582 additions & 28 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ env:
3333
# A version verified to build these packages, not a measured minimum. The pin
3434
# exists for reproducibility rather than because an older mcpp is known to
3535
# fail.
36-
MCPP_VERSION: 2026.8.26.2
36+
MCPP_VERSION: 2026.8.27.1
3737
XLINGS_VERSION: v2026.8.17.2
3838
XLINGS_NON_INTERACTIVE: '1'
3939

@@ -717,6 +717,88 @@ jobs:
717717
# unconditionally would fail to link against a conforming implementation
718718
# of three. Selecting three is therefore asserted to produce a report
719719
# rather than a link failure.
720+
# openkal-kit, which is in this repository and is not this specification.
721+
#
722+
# The kit composes facilities from openkal's interfaces --- awaiting a
723+
# channel, writing an endpoint down and reading one back --- that the
724+
# specification declines to admit because they can be composed. Running its
725+
# tests here is what keeps the composition working as the interfaces it
726+
# rests upon change.
727+
#
728+
# THE SURFACE CHECK ABOVE IS WHAT KEEPS THE TWO APART, and it does so by
729+
# the shape of what is exported rather than by a declaration. The kit is
730+
# C++ modules in `namespace kal::kit` and exports no name beginning with
731+
# `kal_`; a C++ name is mangled to `_ZN3kal3kit…`, so an implementation
732+
# that linked the kit still passes `check-surface.sh --complete`.
733+
- name: The kit, composed from the interfaces above
734+
if: matrix.implementation == 'openkal-linux'
735+
run: |
736+
bash tools/run-kit-tests.sh '${{ matrix.implementation }}' .impl
737+
738+
# ⭐⭐ THE EXAMPLE A CONSUMER READS, BUILT AND RUN.
739+
#
740+
# examples/portable is the program the specification points at as the one
741+
# every implementation must satisfy, and NOTHING BUILT IT. Its README
742+
# stated that "an implementation's continuous integration fetches this
743+
# source ... builds it against itself, and asserts the lines below";
744+
# measured across all six implementation repositories, the word `portable`
745+
# appeared in none of their workflows. A promise with nothing executing it.
746+
#
747+
# ⚠️ AND THE DRIFT THAT FOLLOWS FROM THAT IS VISIBLE IN ITS OWN MANIFEST:
748+
# it named `openkal = "0.5.0"` --- three specification versions behind ---
749+
# while claiming to demonstrate the current contract to a reader who would
750+
# copy it. Nothing could have noticed.
751+
#
752+
# The manifest names published versions, because that is what a consumer
753+
# writes. Those do not exist while a change spanning these repositories is
754+
# under review, so the working trees stand in, exactly as
755+
# tools/run-conformance.sh does for the suite.
756+
#
757+
# ⚠️ RELATIVE PATHS, NOT `pwd`. This job runs on Windows too, where MSYS
758+
# reports `/d/a/...`, which the build tool does not resolve. Relative
759+
# paths also cannot put a local directory into a file that might be
760+
# committed.
761+
#
762+
# ⚠️ NO `standalone` FEATURE HERE. That feature makes the implementation
763+
# supply `_start`, which collides with the C library's crt1.o on a hosted
764+
# toolchain --- measured: "multiple definition of `_start`". The
765+
# conformance runner defaults to no features for the same reason.
766+
- name: The portable program builds and runs over this implementation
767+
working-directory: examples/portable
768+
run: |
769+
set -euo pipefail
770+
subst() { sed -E "$2" "$1" > "$1.next" && mv "$1.next" "$1"; }
771+
subst mcpp.toml 's|^openkal = .*$|openkal = { path = "../.." }|'
772+
subst mcpp.toml 's|^${{ matrix.implementation }} = .*$|${{ matrix.implementation }} = { path = "../../.impl" }|'
773+
grep -q 'path = "../../.impl"' mcpp.toml \
774+
|| { echo "::error::the implementation was not substituted"; exit 1; }
775+
776+
# ⚠️ THE IMPLEMENTATION REACHES THE SPECIFICATION TOO, AND BY ITS OWN
777+
# FORM. Substituting only this manifest leaves two routes to one
778+
# package and the build refuses, correctly:
779+
#
780+
# error: dependency 'mcpplibs.openkal' is requested as both a path
781+
# dep (by 'portable') and a git dep (by 'openkal-linux@path')
782+
#
783+
# Both must name the same directory, which is what run-conformance.sh
784+
# does for the suite. From `.impl/` the specification is `..`; the two
785+
# spellings resolve to one canonical path and therefore to one node.
786+
subst ../../.impl/mcpp.toml 's|^openkal = .*$|openkal = { path = ".." }|'
787+
grep -q 'openkal = { path = ".." }' ../../.impl/mcpp.toml \
788+
|| { echo "::error::the implementation still reaches openkal its own way"; exit 1; }
789+
790+
mcpp run 2>&1 | tee portable.log
791+
792+
# ⭐ THE COUNT IS THE CRITERION, AND SO IS THE DENOMINATOR. A program
793+
# that failed to start would print none of these lines, and a check
794+
# for the absence of failures would hold over an empty log.
795+
lines=$(grep -c '^openkal: ' portable.log)
796+
[ "$lines" -ge 10 ] \
797+
|| { echo "::error::the program printed $lines lines; it reports one per interface"; exit 1; }
798+
grep -q 'openkal: observations that did not hold: 0' portable.log \
799+
|| { echo "::error::the portable program reported observations that did not hold"; exit 1; }
800+
echo " the portable program held every observation over ${{ matrix.implementation }}"
801+
720802
- name: A selection of three interfaces is examined, not refused
721803
run: |
722804
rm -rf conformance/target

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,40 @@ under the compiler its author used is a description of that compiler.
145145
## License
146146

147147
Apache-2.0.
148+
149+
## openkal-kit
150+
151+
`kit/` holds facilities composed from the interfaces this specification defines.
152+
It is a separate package, `mcpplibs/openkal-kit`, and it is **not** part of the
153+
specification.
154+
155+
The specification admits an interface only when it is a minimal capability every
156+
kernel has and cannot be composed from the interfaces already present. That rule
157+
is what keeps openkal implementable on a machine with firmware and nothing else,
158+
and it leaves a gap: a program that wants to carry bytes between two of its own
159+
contexts, or to turn `"127.0.0.1:8080"` into an endpoint, has an answer in POSIX
160+
and no answer here — because both are composed rather than primitive.
161+
162+
That gap was being filled by the port layer. There is one port layer today and
163+
what it composes is POSIX, so a native openkal program either wrote the
164+
composition again or took a whole C library. The kit is where the composition is
165+
written once.
166+
167+
**The contract form is what makes the two unmistakable.** Clause 10 states that
168+
openkal's contract is a C application binary interface. The kit deliberately is
169+
not one: it is C++ modules in `namespace kal::kit`, and it exports no name
170+
beginning with `kal_`. Measured on its objects: the defined names are C++ mangled
171+
module initialisers such as `_ZGIW7openkalW3kitW7channel`, and the operations are
172+
inline and emitted into consumers rather than exported at all. So
173+
`tools/check-surface.sh --complete` does not read a program that links the kit as
174+
an implementation which has added names — the rule that checker enforces is about
175+
the C surface, and the kit has none.
176+
177+
So "is this normative" is answered by the shape of what is exported rather than
178+
by a sentence saying it is not. A sentence can be overlooked; a mangled name
179+
cannot become a C symbol.
180+
181+
The consequence is the one that matters. Clause 8 forbids the specification from
182+
altering a declaration it has published, which is what makes openkal safe to
183+
depend upon and what makes it the wrong place for a facility still finding its
184+
shape. **The kit may evolve.**

SPEC.md

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# openkal Specification, version 0.7
1+
# openkal Specification, version 0.8
22

33
## 1. Scope
44

@@ -43,11 +43,24 @@ provides an interface in whole or not at all.
4343
| `openkal.process` | a program image that has been started | standard |
4444
| `openkal.task` | an execution context, and a suspension primitive | standard |
4545
| `openkal.exec` | a region of the address space a program may execute | optional |
46+
| `openkal.terminal` | an interactive stream's treatment of what is typed | optional |
47+
| `openkal.net` | a connection, and a listener for connections | optional |
48+
| `openkal.datagram` | a message with a boundary, sent without a connection | optional |
49+
| `openkal.space` | an address space, and a context executing in one | optional |
50+
| `openkal.timeout` | a bound upon operations that would otherwise wait | optional |
4651
| `openkal.event` | readiness of a set of resources | reserved |
4752

48-
Version 0.7 specifies the core, standard and optional interfaces. The reserved
53+
Version 0.8 specifies the core, standard and optional interfaces. The reserved
4954
row is not specified, and its name shall not be used for other purposes.
5055

56+
The five interfaces added in version 0.8 are optional in the sense clause 3
57+
defines, and their optionality is not a concession. An environment with no
58+
network is not deficient for providing neither `openkal.net` nor
59+
`openkal.datagram`; an environment with no memory management unit cannot
60+
provide `openkal.space` and is not deficient either. Clause 6.1 expresses each
61+
absence as the absence of a definition at the link, so a program that requires
62+
one of them is refused when it is built rather than when it runs.
63+
5164
*Core* denotes an interface every implementation provides. *Standard* denotes
5265
one an implementation hosting a C library provides. *Optional* denotes one it
5366
may omit without ceasing to host a C library, at the cost of the facilities
@@ -339,7 +352,9 @@ This clause adds no declaration and alters none, so clause 8's rule is not
339352
engaged and the version does not advance on its account. Every declaration of
340353
version 0.6 already satisfies it — one hundred and one of them, examined by the
341354
procedure below — and what is new is that the property is now stated and
342-
checked rather than held by the care of whoever wrote each header.
355+
checked rather than held by the care of whoever wrote each header. The five
356+
interfaces added in version 0.8 were examined by the same procedure and satisfy
357+
it also; the count is now one hundred and forty-six.
343358

344359
Clause 9's procedure examines this.
345360

@@ -413,6 +428,44 @@ consumer imports, which contradicts clause 4.
413428
implementation declares them. Not adopted: a record can disagree with the code
414429
it describes, and the file is a second place in which a package is configured.
415430

431+
Two further arrangements were weighed while specifying version 0.8 and are
432+
recorded on the same basis.
433+
434+
**Readiness notification.** An interface reporting that a stream may be read, by
435+
waking a word as `kal_task_wake` does, was considered as the remedy for a context
436+
that would otherwise wait without end. It composes better than the bound this
437+
specification adopted: one operation covers every waitable resource, a single
438+
context may await many sources, and a library above it needs no read-ahead buffer
439+
because a notification consumes nothing.
440+
441+
It was not adopted because of what it asks of an implementation. On an
442+
environment whose readiness is discovered by polling a set of descriptors, an
443+
implementation would have to maintain that set and a context of its own to watch
444+
it. That is a mechanism reconstructed rather than a facility conveyed, which
445+
clause 7.1 excludes. `openkal.timeout` asks the same environment only for what it
446+
already does at the point of the call.
447+
448+
**A space as a handle.** An earlier form of `openkal.space` separated the copying
449+
of an address space from the starting of a context in it, so that a caller held a
450+
space and could start a context in it afterwards. It was withdrawn while the
451+
first implementation was being written.
452+
453+
No environment this specification targets has that pair as a primitive. The copy
454+
and the start are one act, and an implementation asked to separate them would
455+
have to start a context anyway, park it upon a waiting primitive, and build a
456+
channel by which to tell it what to run. Clause 7.1 identifies that as a fault in
457+
the shape of the specification rather than in the implementation, and the
458+
separated form was the shape at fault. The single operation that replaced it is
459+
what every such environment already performs.
460+
461+
**An instant rather than a duration.** `openkal.timeout` states a duration
462+
because `kal_task_wait` does. An instant would not accumulate drift when a caller
463+
retries in a loop, and was considered for that reason. It was not adopted because
464+
it would give one specification two spellings of one idea. A caller that requires
465+
an instant computes the remaining duration from `kal_time_monotonic`, so the cost
466+
falls upon the caller that has the requirement rather than upon every
467+
implementation.
468+
416469
The measurements that constrain any future proposal:
417470

418471
1. A requires-expression naming a qualified entity that does not exist is
@@ -787,3 +840,33 @@ The following are recorded so that they are not mistaken for oversights.
787840
in its symbol names. Clause 8 protects the interface by prohibiting change
788841
rather than by permitting coexistence, and an ecosystem that outgrows that
789842
prohibition will require a mechanism this version does not define.
843+
4. **Readiness.** Awaiting one of several sources is not an operation of this
844+
specification. It is reached above the interface, from `openkal.task` and a
845+
bound upon each wait; clause 6.3 records the alternative that was weighed and
846+
the property of implementations that excluded it.
847+
5. **Name resolution.** `openkal.net` and `openkal.datagram` carry an address and
848+
a port. Turning a name into one is excluded by clause 3.4 and remains so: an
849+
implementation shall not be required to parse an unbounded set of name
850+
schemes.
851+
6. **Permission and ownership of files.** Not defined, and not a deferral. A
852+
permission presupposes an identity, and the environments this specification
853+
targets do not agree that one exists. A C library above openkal reports the
854+
absence as the error its own surface defines.
855+
7. **Creation and reading of links.** Not defined, and not a deferral, for the
856+
reason clause 6.4 gives: whether a filesystem has links is a property of the
857+
format rather than of the environment. `KAL_FS_PROP_LINKS` reports it, and
858+
resolution follows one where the property is claimed.
859+
8. **Duplication of the calling image.** `fork` is refused by clause 7.1 and that
860+
refusal stands. It is refused as an OPERATION. The atomic capabilities from
861+
which a library may compose it are specified: `openkal.space` clones an
862+
address space and starts a context in one, and `KAL_SPACE_PROP_CLONE_HANDLES`
863+
states whether the handles accompany the memory. What this specification
864+
declines to do is duplicate execution state, which a library above the
865+
interface performs with the compiler's own facilities. A sentence reading
866+
"openkal will not have fork" would have buried that distinction, and this
867+
entry exists so that it is not written.
868+
9. **Transfer of a handle between address spaces.** `kal_space_start` conveys no
869+
handle, and `kal_process_channel` conveys a stream only across a spawn. A
870+
general mechanism for passing a handle to a context in another space is not
871+
defined by this version. It is the question `openkal.space` reaches first and
872+
is not peculiar to it.

SURFACE.txt

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# The C surface of openkal 0.6, one name per line.
1+
# The C surface of openkal 0.8, one name per line.
22
#
33
# This file is normative and is the single source consulted by clause 9. A
44
# conforming implementation exports the names of the interfaces it provides
@@ -57,9 +57,12 @@ kal_fs_set_modified
5757
kal_fs_stream
5858
kal_fs_truncate
5959
# openkal.process
60+
kal_process_channel
61+
kal_process_channel_close
6062
kal_process_close
6163
kal_process_props
6264
kal_process_spawn
65+
kal_process_spawn_with
6366
kal_process_terminate
6467
kal_process_wait
6568
# openkal.task
@@ -75,3 +78,37 @@ kal_exec_alloc
7578
kal_exec_free
7679
kal_exec_props
7780
kal_exec_publish
81+
# openkal.terminal
82+
kal_terminal_get_mode
83+
kal_terminal_props
84+
kal_terminal_set_mode
85+
kal_terminal_size
86+
# openkal.net
87+
kal_net_accept
88+
kal_net_close
89+
kal_net_close_listener
90+
kal_net_connect
91+
kal_net_listen
92+
kal_net_listener_local
93+
kal_net_local
94+
kal_net_peer
95+
kal_net_props
96+
kal_net_shutdown
97+
kal_net_stream
98+
# openkal.datagram
99+
kal_datagram_close
100+
kal_datagram_local
101+
kal_datagram_open
102+
kal_datagram_props
103+
kal_datagram_recv_from
104+
kal_datagram_send_to
105+
# openkal.space
106+
kal_space_props
107+
kal_space_start
108+
# openkal.timeout
109+
kal_timeout_accept
110+
kal_timeout_granularity_ns
111+
kal_timeout_read
112+
kal_timeout_recv_from
113+
kal_timeout_wait_process
114+
kal_timeout_write

conformance/mcpp.toml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-conformance"
4-
version = "0.5.1"
4+
version = "0.6.0"
55
description = "The behavioural half of clause 9: a suite an implementation of openkal runs against itself, selectable to the interfaces it provides."
66
license = "Apache-2.0"
77
authors = ["mcpplibs"]
@@ -18,7 +18,7 @@ repo = "https://github.com/mcpplibs/openkal"
1818
# What it depends upon is therefore openkal and the language. The formatting in
1919
# okc.report is the price of that, and it is sixty lines.
2020
[dependencies]
21-
openkal = "0.7.0"
21+
openkal = "0.8.0"
2222

2323
# The implementation under examination is not named here.
2424
#
@@ -62,6 +62,16 @@ process = ["fs"] # a program is started relative to a directory
6262
task = []
6363
exec = []
6464

65+
# The five interfaces version 0.8 adds. Each is optional in the sense clause 3
66+
# defines, so each is a feature and none is in `hosted': a set demanding an
67+
# optional interface would turn a permitted choice into a link error, which is
68+
# the mistake the note above `optional' records having been made once already.
69+
terminal = []
70+
net = []
71+
datagram = []
72+
space = ["process"] # a started context is waited for as a process
73+
timeout = []
74+
6575
# The specification names this set `hosted' (clause 3.3). Both spellings are
6676
# here because the workflows and the older invocations use `standard', and a
6777
# rename that broke them would be a rename of the wrong thing.
@@ -91,7 +101,7 @@ standard = ["hosted"]
91101
# bare-metal one provides it only if its board has a source, and neither is a
92102
# deviation. The caller names this set for an implementation that provides
93103
# these interfaces and omits it for one that does not.
94-
optional = ["exec", "random"]
104+
optional = ["exec", "random", "terminal", "net", "datagram", "space", "timeout"]
95105

96106
# The kinds of examination. Behaviour is always performed; the other three are
97107
# selected, because each costs time that a reader running the suite to answer

0 commit comments

Comments
 (0)