Skip to content

Implement the five interfaces openkal 0.8 adds - #15

Merged
Sunrisepeak merged 5 commits into
mainfrom
feat/openkal-0.8
Aug 27, 2026
Merged

Implement the five interfaces openkal 0.8 adds#15
Sunrisepeak merged 5 commits into
mainfrom
feat/openkal-0.8

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

openkal.terminal upon the kernel's terminal ioctls, openkal.net and openkal.datagram upon its socket calls, openkal.space upon clone, and openkal.timeout upon ppoll and a bounded wait for a child.

Paired with mcpplibs/openkal#13, which this branch follows by name.

Each is written on the kernel's own calling convention and names no facility a program above might also define. That property has held since version 0.5 and CI asserts it by examining the undefined symbols of the produced objects.

What writing the implementation established about the interface

Two changes went into the specification rather than into this repository, and both were found on the first run rather than by reading.

openkal.space is one operation because the kernel's primitive is one act. clone copies the address space and begins execution in the copy; there is no form that does the first without the second. An implementation asked to separate them would have to park a started context and build a channel by which to tell it what to run, which clause 7.1 excludes.

openkal.net yields an owned connection and a borrowed stream, for the same reason kal_file and kal_fs_stream are separate here: the owned handle carries a generation so that a released one stops being valid, and a stream handle is the bare descriptor because that is what the transfer operations take. The first form packed the connection into the handle scheme and kal_stream_read took the packed word as a descriptor, so the transfer read nothing.

Details worth stating

The terminal mode is read, modified and written rather than written alone. The kernel's structure carries input flags, output flags, a baud rate and the control characters, none of which the interface names; composing one from the mode word would discard all of them silently.

kal_terminal_props asks TIOCGWINSZ rather than deriving the answer from TCGETS. A pseudo terminal answers both and a serial line answers only the first, so a word derived from one would claim a facility the next call refuses — the disagreement clause 6.2 exists to prevent.

The endpoint conversion is shared between net and datagram because the type is, and either interface may be provided without the other. A length the implementation does not know is refused rather than read as one it does.

An interrupted bounded wait is reported as an expiry rather than retried with the whole bound again. Retrying would make the bound restart at every signal, leaving a program waiting without end while appearing to be bounded.

kal_timeout_wait_process polls with WNOHANG because the kernel has no bounded wait for a child. The alternative is a SIGCHLD handler, which is process-wide state an implementation would be taking away from the program above it.

kal_datagram_props does not claim broadcast. The kernel provides it only after SO_BROADCAST has been set and this interface has no operation that would set it.

Verification

mcpp test 7 passed, 0 failed
conformance suite 100 observations held, 0 did not hold
terminal interactive path observed under a real pty, not only the refusal path

tests/conformance_v08.cpp is the C++ half of clause 4.3 for the new interfaces: the specification repository checks the C form against SURFACE.txt, and the module form is checked here, where a build of the modules already exists. It found a real omission on its first run — kal_endpoint was declared by the C form and not exported by the module form.

The observations are of behaviour and not only of existence. A test that named each entity and did nothing with it would compile against an implementation whose every operation returned an error, and would report that as conformance.

openkal.terminal upon the kernel's terminal ioctls, openkal.net and
openkal.datagram upon its socket calls, openkal.space upon clone, and
openkal.timeout upon ppoll and a bounded wait for a child.

Each is written on the kernel's own calling convention and names no facility a
program above might also define, which is the property this implementation has
had since version 0.5 and which CI asserts by examining undefined symbols.

## What the implementation established about the interface

openkal.space is one operation because the kernel's primitive is one act. clone
copies the address space and begins execution in the copy; there is no form that
does the first without the second. The specification was changed rather than this
file, because an implementation asked to separate them would have to park a
started context and build a channel to tell it what to run.

openkal.net yields an owned connection and a borrowed stream, for the same
reason kal_file and kal_fs_stream are separate: the owned handle carries a
generation so that a released one stops being valid, and a stream handle is the
bare descriptor because that is what the transfer operations take.

## Details worth stating

The terminal mode is read, modified and written rather than written alone. The
kernel's structure carries input flags, output flags, a baud rate and the control
characters, none of which this interface names; composing one from the mode word
would discard all of them silently.

kal_terminal_props asks TIOCGWINSZ rather than deriving the answer from TCGETS. A
pseudo terminal answers both and a serial line answers only the first, so a word
derived from one would claim a facility the next call refuses.

The endpoint conversion is shared between net and datagram because the type is,
and either interface may be provided without the other.

An interrupted bounded wait is reported as an expiry rather than retried with the
whole bound again, which would make the bound restart at every signal and leave a
program waiting without end while appearing to be bounded.

kal_timeout_wait_process polls with WNOHANG because the kernel has no bounded
wait for a child. The alternative is a SIGCHLD handler, which is process-wide
state an implementation would be taking away from the program above it.

  tests/conformance_v08.cpp  the C++ half of clause 4.3 for the new interfaces
  mcpp test                  7 passed, 0 failed
  conformance suite          100 observations held, 0 did not hold
                             terminal's interactive path observed under a pty
ADDING TO AN EXISTING INTERFACE OBLIGES EVERY IMPLEMENTATION OF IT, and adding a
new interface obliges none. Clause 6.1 makes an interface an implementation does
not provide absent at the link and not a deviation; it makes one provided IN PART
a deviation. The five interfaces version 0.8 adds are therefore free for a
backend to decline, and the three names added to openkal.process are not.

The specification's own surface checker is what said so, before anything else
noticed:

    openkal.process is provided in part: 3 of 8 names are not exported --
        kal_process_channel
        kal_process_channel_close
        kal_process_spawn_with

kal_process_channel is pipe2 with O_CLOEXEC on both ends. The far end is placed
deliberately by the spawn that receives it; an end that leaked into every other
started program would keep the channel open after the intended reader had closed
it, and the writer would never see the end of input.

The streams are bare descriptors rather than packed handles, because
openkal.stream's transfer operations take what the environment takes.
kal_fs_stream reports a file's stream the same way and for the same reason.

kal_process_channel_close refuses descriptors below three. They are the standard
streams, which are borrowed; closing one through this operation would take a
stream away from the whole program.

kal_process_spawn_with places the grants as descriptors three and upward, which
is where kal_fs_preopen reads them back from. The inverse relationship clause
7.11 describes is between those two operations, which is why they must agree
about the numbering rather than each choosing one.

⚠️ dup3 refuses a duplication onto itself, and the ordinary case reaches that
whenever a granted directory already occupies the number it is destined for.
Refusing is correct of dup3 --- the flags could not be applied --- and here it
means the descriptor is already in place, so it is left alone rather than treated
as a failure.

The grants are resolved before the fork. A failure after it would leave a child
to be reaped and a caller holding an error it cannot act upon.

  surface      exported surface is complete and conforms: 90 names
  mcpp test    7 passed, 0 failed
  observed     a channel carries bytes, and closing the far end is observed as
               end of input on the near one
Two scripts in the specification's repository rewrite this manifest to name a
working tree --- run-conformance.sh and run-kit-tests.sh --- and both restore it
through a trap. A trap does not fire when the process is killed, and a run by
hand followed by `git add -A` then publishes a path that exists on one machine:
a consumer resolving from the index is handed a manifest pointing at a directory
that exists nowhere.

⚠️ That has happened in this ecosystem, in openkal-musl, where it was published
and had to be reverted. The working tree here has carried the same rewrite more
than once since, and only an audit before committing kept it out.

The step runs first, so what it examines is what the commit contains rather than
what the job has since done to it. openkal-musl carries the same check for the
same reason.
openkal 0.8.0 is published, so the manifest names it by version rather than by
branch. A branch is how a change spanning these repositories is developed and is
not a form a published package may carry: a consumer resolving from the index
would be handed a reference that moves.

The engine pin moves to mcpp 2026.8.27.1 and the repository variable
MCPP_SOURCE_REF is cleared, so what this repository tests is the engine a user
installs.
main moved MCPP_VERSION from 2026.8.26.1 to 2026.8.26.2 while this branch moved
it to 2026.8.27.1, which is now released. The branch's value is the later of the
two and is what this repository should test against.

⚠️ While the pull request was in a conflicting state it built no `pull_request`
runs at all --- the checks were not failing, they were never created, which
reads as "nothing has run yet" and is indistinguishable from a queue.
@Sunrisepeak
Sunrisepeak merged commit c096389 into main Aug 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants