Skip to content

0.9.0 — one idea has one spelling, and every declaration can cross a boundary - #20

Merged
Sunrisepeak merged 7 commits into
mainfrom
abi/one-idea-one-spelling
Aug 28, 2026
Merged

0.9.0 — one idea has one spelling, and every declaration can cross a boundary#20
Sunrisepeak merged 7 commits into
mainfrom
abi/one-idea-one-spelling

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

Compile-time portability was the floor and was being mistaken for the goal. Every
interface, operation and structure is now shaped so that it can also be crossed
at run time, and clause 3's table states for each which boundaries it crosses.

Thirteen of the fifteen interfaces could not be crossed, and five causes stood
in the way rather than fifteen:

cause count answer
an exported data object 10 every report becomes an operation; one that varies between the resources of an interface takes the resource
an operation returns a pointer into the implementation 5 the value is copied into the caller's buffer and the length reported is the value's own
a result wider than one machine word 7 a transfer returns one signed word: the count, or the negated condition when none moved
an entry worded as a call 2 an entry is an address at which a context begins; no return address exists
clause 6.5 1 availability decided at production cannot serve an artifact produced once for many environments, so openkal.exec reports it as well

The transfer result was going to gain an out-parameter. It did not. The only
consumer of the two-word form collapsed the pair by hand at every site and by one
rule — report what moved, or the condition when nothing did. The pair was never
the shape a caller wanted. kal_io_result is deleted.

Also

  • ⚠️ standard and hosted are withdrawn. A class named after a kind of
    environment is falsified by an environment nobody had in mind, and this one was
    falsified inside its own ecosystem: a C library is hosted above an
    implementation providing none of the three interfaces the class promised.
  • kal_memory_granularity — a C library above openkal had no way to learn the
    quantum and fixed it at 4 KiB when it was built. One real system has two
    quanta (4 KiB pages, 64 KiB allocations), which is why this is one number
    defined as the coarsest that is always safe rather than a page size.
  • kal_node_info carries its own size, reports which fields it filled, and
    carries an opaque identity. Those three questions were conflated, and the
    consequence was two different files reported as the same file with no error.
  • Links enter openkal.fs, and resolution is stated in fs.h beside every
    operation it governs. It was stated only in clause 11, and two call sites of
    one implementation consequently chose opposite directions.
  • kal_fs_open_file deleted; typed handles at kal_fs_stream and
    kal_spawn_streams; kal_endpoint.addr_len no longer freezes a per-word-size
    difference; clause 7.2 requires a forged handle to be refused; a name-length
    bound is reportable; kal_version and kal_interfaces are exported by every
    implementation and belong to no interface.

One binary, two implementations

tools/run-abi-test.sh is new and is the reason this could land at all. The
property a distributed binary rests upon was not merely untested — there was no
artifact of the shape that could test it
, because every target had exactly one
implementation and it was linked in.

It builds openkal-linux as a shared object, builds a second implementation from
the same objects with six names renamed and answered by an interposer, builds one
probe against neither by name, and runs that one binary against each. Every answer
in the second run is a branch nothing here had ever taken: an implementation older
than the consumer's declarations, a coarser quantum, an optional interface
declined at run time, executable memory withheld, and an implementation that
cannot distinguish one node from another.

Two false greens in the suite's own machinery, both found while running it

  • run-conformance.sh chose its binary with find | head -1. target
    accumulates a directory per fingerprint, so a run reported 143 held from a
    binary built two days earlier containing none of the observations just added.
  • full was defined as ["standard", …]. Withdrawing standard left it naming
    four kinds of examination and no interface: 168 held became 119 held, with an
    exit status of zero both times.

168 observations hold against openkal-linux 0.7.0, up from 143.

speak-agent and others added 7 commits August 29, 2026 00:45
…a boundary

Compile-time portability was the floor and is no longer mistaken for the goal.
Every interface, operation and structure is now shaped so that it can also be
crossed at run time, and clause 3's table states for each which boundaries it
crosses --- a statement about the declarations, which an implementation cannot
violate and only a declaration can.

Thirteen of the fifteen interfaces could not be crossed, and five causes stood
in the way rather than fifteen:

  ten exported data objects      every report becomes an operation, and one
                                 that varies between the RESOURCES of an
                                 interface takes the resource
  five internal pointers         a value is copied into the caller's buffer and
                                 the length reported is the value's own
  seven two-word results         a transfer returns one signed word: the count,
                                 or the negated condition when none moved
  two entries worded as a call   an entry is an address at which a context
                                 begins; no return address exists
  one clause                     6.5 settles availability at production, which
                                 an artifact produced once for many
                                 environments cannot use, so `openkal.exec'
                                 reports it as well

The transfer result was going to gain an out-parameter. It did not: the only
consumer of the two-word form collapsed the pair by hand at every site and by
one rule --- report what moved, or the condition when nothing did --- so the
pair was never the shape a caller wanted. `kal_io_result` is gone.

Also, and not incidentally:

  * `standard` and `hosted` are withdrawn. A class named after a kind of
    environment is falsified by an environment nobody had in mind, and this one
    was falsified inside its own ecosystem: a C library is hosted above an
    implementation providing none of the three interfaces the class promised.
  * `kal_memory_granularity` exists because a C library above openkal had no
    way to learn the quantum and fixed it at four kilobytes when it was built,
    which is wrong on every machine whose quantum differs from the one it was
    built for.
  * `kal_node_info` carries its own size, reports which fields it filled, and
    carries an opaque identity --- three questions that were conflated and that
    left two different files reported as the same file, with no error.
  * Links enter `openkal.fs`, and resolution is stated in `fs.h` beside every
    operation it governs. It was stated only in clause 11, and two call sites
    of one implementation consequently chose opposite directions.
  * `kal_fs_open_file` is deleted; `kal_fs_stream` and `kal_spawn_streams`
    carry their type; `kal_endpoint.addr_len` no longer freezes a per-word-size
    difference; clause 7.2 requires a forged handle to be refused; a name
    length bound is reportable.
  * `kal_version` and `kal_interfaces` are exported by every implementation and
    belong to no interface, so a consumer with no linker to ask can ask.

Two defects in the suite's own machinery were found while running it, and both
were producing false greens:

  * `run-conformance.sh` chose the binary with `find | head -1`. `target`
    accumulates a directory per fingerprint, so a run reported `143 held` from
    a binary built two days earlier that contained none of the observations
    just added. Suites are now removed before building, and finding more than
    one afterwards is a condition rather than a choice.
  * `full` was defined as `["standard", …]`. Withdrawing `standard` left it
    naming four kinds of examination and no interface: 168 held became 119
    held, with an exit status of zero both times.

168 observations hold against openkal-linux 0.7.0, up from 143.
⚠️⚠️ THE PROPERTY A DISTRIBUTED BINARY RESTS UPON WAS NOT MERELY UNTESTED. THERE
WAS NO ARTIFACT OF THE SHAPE THAT COULD TEST IT.

The conformance suite asserts that one artifact, built and run in one place,
behaves as this specification says. What a distributed binary needs is stronger:
that one binary, BUILT ONCE, behaves as specified against an implementation IT
WAS NOT COMPILED AGAINST. Every target had exactly one implementation, the choice
was made at dependency resolution, and the implementation was linked in --- so
there was nothing to run twice.

`tools/run-abi-test.sh` produces one. It builds openkal-linux as a shared object;
builds a second implementation FROM THE SAME OBJECTS, with six names renamed out
of the way and answered by `conformance/abi/interposer.cpp` instead; builds one
probe against neither by name; and runs that one binary against each, asserting
that it is not rebuilt in between.

⭐ EVERY ANSWER IN THE SECOND RUN IS A BRANCH NOTHING HERE HAD EVER TAKEN:

    satisfies-floor  yes -> no      an implementation older than the consumer's
                                    declarations, which is what the version
                                    operation exists to let a consumer refuse
    granularity     4096 -> 65536   a consumer that fixed the quantum when it was
                                    built is wrong here and nowhere else
    has-space        yes -> no      an optional interface declined AT RUN TIME,
                                    where there is no linker to report it
    exec-available   yes -> no      clause 6.5's objection was that a path no
                                    artifact takes is a path nothing has
                                    verified. This is the artifact that takes it
    knows-identity   yes -> no      an implementation that cannot distinguish one
                                    node from another leaves the position clear,
                                    and a caller is TOLD rather than told wrongly

The interposer is not a port: it delegates every operation to the implementation
beneath and changes six answers. That is a second implementation in the only
sense a consumer can observe --- a different shared object, exporting the same
surface, answering differently --- and it is a hundred lines rather than three
thousand.
⚠️⚠️ THIS README TOLD A READER TO ASK FOR `openkal = "0.5.1"` WHILE THE PACKAGE
WAS AT 0.9.0 --- four minor versions, each of which had changed the surface. A
reader following it got declarations that do not match the documentation around
them, and nothing said so.

⭐ THE STALENESS IS NOT THE FINDING. That it was invisible is. The surface is
checked against SURFACE.txt, the declarations are checked in both forms, the
behaviour is checked by the conformance suite --- and the one line a reader
actually types was checked by nobody.

`tools/check-readme-versions.sh` checks that every `<package> = "<version>"`
in a README agrees with that package's own manifest. It reports `?` rather than
passing where the package's tree is not beside it, so its "no" and its "did not
run" do not read the same --- which is the failure mode this ecosystem keeps
meeting and is worth two lines to avoid.
The self-review of this change found four things it had missed, all of the
same shape: a place that names the surface and is not compiled by anything
that would notice it had moved.

examples/ was one. Three programs there still returned kal_io_result, called
kal_fs_open_file, and borrowed environment strings by pointer. They build in
continuous integration, so they would have failed there; they had not been
looked at because nothing about them says which version they are written for.
The portable program's manifest also asked for openkal 0.8.0 while claiming to
demonstrate the current contract to a reader who copies it.

The frozen-layout probe in this workflow asserted kal_node_info's 0.8 layout
(size at 0, 24 bytes). It now asserts the 0.9 one, and asserts the property
that layout exists to have: self_size is at offset zero, because an
implementation built later reads it before it touches anything else. Both
widths were compiled locally before this was pushed.

SURFACE.txt's self-description group was spelled `# openkal ---', which does
not match the `# openkal.<name>' pattern openkal-linux derives its module
imports from. The two names in it were listed without an import and its
generated test failed naming the names rather than the heading.

check-readme-versions.sh now checks manifests as well as READMEs, which is
where the drift above was. Writing that found a defect in the tool itself: a
manifest with no version pin makes grep exit 1, and under pipefail that ended
the loop -- so the survey stopped at the first such file and reported "ok"
having examined eight of eleven pins. It now carries a denominator, and a
floor that denominator cannot supply: the package's own root manifest must
have been reached.
Five things that could not have been written before the change was implemented:
two aarch64 defects the leg had carried since it was written, an interposer
whose value is that nothing of its shape existed, eight jobs that could only be
green when the change was already published, a test that asserted a limitation
that was then lifted, and a defect three layers up whose every ingredient was
correct.
The last defect this change produced, and the one introduced BY the correction
the design recommends: openkal's granularity is not musl's page size, an
implementation with no memory management unit answers one, and the assertion
that should have caught it passed because one is a positive power of two.
…ot about aarch64

The leg that surfaces a defect is not the axis the defect is on. musl does not
pass the vectors to .init_array and glibc does, which x86_64-linux-musl settles
and aarch64 alone would have mis-attributed; and openkal-windows's .def check
was right with the wrong scope, reporting a number that was of the names it knew
about.
@Sunrisepeak
Sunrisepeak merged commit 3da84ba into main Aug 28, 2026
12 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