From 509a0d9829b1d275304831c2df7bf78865f0aa6c Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sat, 29 Aug 2026 00:51:01 +0800 Subject: [PATCH 1/4] 0.3.0 --- adopt openkal 0.9 Transfers return one signed word. The parameters a program receives are copied into the caller's buffer and the length reported is the value's own --- here there are none, so each reports the condition, which is what distinguishes "no such thing" from "one, and it is empty". `kal_memory_granularity' is answered with a constant, which is what the specification says the cheap answer should be, and `kal_version' and `kal_interfaces' likewise. The second is worth having on this row in particular: a machine with no storage, no second image and no scheduler has those interfaces absent as definitions, and the word now says so rather than leaving a consumer to discover it by failing to link. --- .gitignore | 4 ++++ mcpp.toml | 4 ++-- src/kal.cpp | 28 ++++++++++++++++++---------- src/version.cpp | 17 +++++++++++++++++ 4 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 src/version.cpp diff --git a/.gitignore b/.gitignore index 7435482..73453ed 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ target/ .mcpp/ compile_commands.json mcpp.lock + +# A working tree of the specification placed beside the sources. No trailing +# slash: the pattern must match a symbolic link to one as well as a directory. +.spec diff --git a/mcpp.toml b/mcpp.toml index f575049..3face60 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -15,7 +15,7 @@ [package] namespace = "mcpplibs" name = "openkal-uefi" -version = "0.2.0" +version = "0.3.0" description = "An implementation of openkal on UEFI Boot Services, for applications the firmware loads before an operating system exists" license = "Apache-2.0" @@ -32,7 +32,7 @@ authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-uefi" [dependencies] -openkal = "0.8.0" +openkal = "0.9.0" [build] flags = [ diff --git a/src/kal.cpp b/src/kal.cpp index 85b5700..3735f9d 100644 --- a/src/kal.cpp +++ b/src/kal.cpp @@ -45,9 +45,9 @@ efi_system_table* g_st = nullptr; // window rather than a cache. constexpr unsigned kChunk = 128; -kal_io_result write_to(efi_simple_text_output_protocol* out, +kal_intptr write_to(efi_simple_text_output_protocol* out, const unsigned char* p, kal_uintptr n) { - if (!out) return kal_io_result{0, kal_err_io}; + if (!out) return -kal_err_io; efi_char16 buf[kChunk * 2 + 1]; // worst case: every byte becomes CR LF kal_uintptr done = 0; while (done < n) { @@ -63,9 +63,9 @@ kal_io_result write_to(efi_simple_text_output_protocol* out, } buf[w] = 0; if (out->output_string(out, buf) != EFI_SUCCESS) - return kal_io_result{done, kal_err_io}; + return done ? static_cast(done) : -kal_err_io; } - return kal_io_result{done, kal_ok}; + return static_cast(done); } } // namespace @@ -112,13 +112,13 @@ kal_stream kal_stdin (void) { return kal_stream{kStdin}; } kal_stream kal_stdout(void) { return kal_stream{kStdout}; } kal_stream kal_stderr(void) { return kal_stream{kStderr}; } -kal_io_result kal_stream_write(kal_stream s, const void* buf, kal_uintptr n) { - if (!g_st) return kal_io_result{0, kal_err_io}; +kal_intptr kal_stream_write(kal_stream s, const void* buf, kal_uintptr n) { + if (!g_st) return -kal_err_io; efi_simple_text_output_protocol* out = s.h == kStdout ? g_st->con_out : s.h == kStderr ? (g_st->std_err ? g_st->std_err : g_st->con_out) : nullptr; - if (!out) return kal_io_result{0, kal_err_invalid}; + if (!out) return -kal_err_invalid; return write_to(out, static_cast(buf), n); } @@ -128,9 +128,9 @@ kal_io_result kal_stream_write(kal_stream s, const void* buf, kal_uintptr n) { // loses every key that is not a plain character. Returning "no bytes, no error" // is end of input, which is the truthful reading of a console this // implementation does not attempt to interpret. -kal_io_result kal_stream_read(kal_stream s, void*, kal_uintptr) { - if (s.h != kStdin) return kal_io_result{0, kal_err_invalid}; - return kal_io_result{0, kal_ok}; +kal_intptr kal_stream_read(kal_stream s, void*, kal_uintptr) { + if (s.h != kStdin) return -kal_err_invalid; + return 0; } // The console is unbuffered by this implementation; the bytes were handed to @@ -153,6 +153,14 @@ kal_uintptr kal_stream_props(kal_stream s) { // a stricter alignment is satisfied by over-allocating and storing the original // pointer immediately before the aligned address — the same technique a C // library uses for `aligned_alloc` where the platform lacks one. +// The quantum this environment allocates and protects memory in. +// +// Firmware hands out memory in pages of four kilobytes --- that is the unit of +// `AllocatePages', which is what this allocator draws on --- and it applies no +// protection of its own. The coarser of the two is therefore the page, and a +// caller that rounds to it is never wrong. +kal_uintptr kal_memory_granularity(void) { return 4096; } + void* kal_alloc(kal_uintptr size, kal_uintptr align) { if (!g_st) return nullptr; if (size == 0) size = 1; diff --git a/src/version.cpp b/src/version.cpp new file mode 100644 index 0000000..92f70c4 --- /dev/null +++ b/src/version.cpp @@ -0,0 +1,17 @@ +#include + +// What this implementation says about itself before it is used. Both answers +// are constants; see openkal/version.h for why they belong to no interface. +extern "C" { + +kal_u64 kal_version(void) { return KAL_VERSION; } + +// Firmware supplies a console and a page allocator and nothing else this +// specification has an interface for. `openkal.env' is absent because a program +// started by firmware receives no arguments this implementation can report, and +// reporting none would be a claim rather than an absence. +kal_u64 kal_interfaces(void) { + return KAL_IFACE_ABORT | KAL_IFACE_STREAM | KAL_IFACE_MEMORY; +} + +} From 3cc3bbef6ae38a30fa8a405487f53f98ad4bf55c Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sat, 29 Aug 2026 01:39:18 +0800 Subject: [PATCH 2/4] README: the versions it names are the versions that exist Every README here opens by showing what a program writes in its manifest, which is the first thing a reader copies and the last thing anyone edits. These lines had drifted --- the specification's own README asked for a version four minor releases old --- and nothing checked them. `openkal/tools/check-readme-versions.sh` now does. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2412732..550c787 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ldflags = ["-nostdlib", "-Wl,--subsystem,10", "-Wl,-e,efi_main"] [dependencies] openkal = "0.5.1" -openkal-uefi = "0.1.0" +openkal-uefi = "0.3.0" ``` ## ⚠️ The target is `x86_64-windows-gnu`, and that is not a workaround From 2c9564a0315ccbb5a1734b36bc2fd3d6fc06983c Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 29 Aug 2026 02:13:31 +0800 Subject: [PATCH 3/4] ci: build against the specification under review, not the published one A job that resolves the published specification cannot review a change to one. Another job in this file already cloned the specification and substituted it, and that made the arrangement look complete. It was not: the steps below reached openkal by version, so a version under review -- which by definition is not published -- failed them with E_NOT_FOUND: package 'compat.openkal@0.9.0' not found in the synced index ... the index is current, so this name is either wrong or not published yet The unit is the job, not the repository. Measured across the eight repositories of this ecosystem while one change spanned all of them: seven jobs in three of them had this shape, and each of those repositories also had a job doing it correctly -- which is what made the gap invisible to a check done a repository at a time. These steps are green on main and can only be green there, because there the published version is the one under test. It is not a check that fails, it is a check that cannot run at the only time it would have something to say. The README also asked a reader for openkal 0.5.1 against a specification now at 0.9.0, and examples/hello asked for 0.8.0. Both are lines a reader copies. --- .github/workflows/ci.yml | 87 ++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- examples/hello/mcpp.toml | 2 +- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a039f29..3da466b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,9 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 40 env: + # The specification is taken from the branch of the same name where one + # exists, so a change spanning both repositories is reviewed as a whole. + OPENKAL_BRANCH: ${{ github.head_ref || github.ref_name }} MCPP_VERSION: 2026.8.27.1 XLINGS_VERSION: v2026.8.17.2 XLINGS_NON_INTERACTIVE: '1' @@ -151,6 +154,47 @@ jobs: echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)" fi + + # ⚠️⚠️ A JOB THAT RESOLVES THE PUBLISHED SPECIFICATION CANNOT REVIEW A + # CHANGE TO ONE. + # + # Another step in this file already clones the specification and + # substitutes it, and that made the arrangement look complete. It was not: + # the steps below reached `openkal' BY VERSION, so a version under review + # --- which by definition is not published --- failed them with + # + # E_NOT_FOUND: package 'compat.openkal@0.9.0' not found in the + # synced index ... the index is current, so this name is either + # wrong or not published yet + # + # ⭐⭐ THE UNIT IS THE JOB, NOT THE REPOSITORY. Measured 2026-08-28 across + # the eight repositories of this ecosystem while one change spanned all of + # them: seven jobs in three repositories had this shape, and every one of + # those repositories ALSO had a job that substituted correctly --- which is + # what made the gap invisible to a check done a repository at a time. + # + # These jobs are green on `main` and can only be green there, because + # there the published version is the one under test. That is the property + # that makes the defect silent: it is not a check that fails, it is a + # check that cannot run at the only time it would have something to say. + - name: Point at the specification's working tree + run: | + set -euo pipefail + git clone --quiet https://github.com/mcpplibs/openkal .spec + if git -C .spec rev-parse --verify --quiet "origin/$OPENKAL_BRANCH" > /dev/null; then + git -C .spec checkout --quiet "origin/$OPENKAL_BRANCH" + echo "the specification is at $OPENKAL_BRANCH" + else + echo "the specification has no $OPENKAL_BRANCH; its default branch is used" + fi + # ⚠️ EVERY MANIFEST THAT NAMES IT, AND BY THE SAME FORM. mcpp refuses + # a graph in which one package reaches a dependency by version and + # another by path, so substituting only the root leaves the build + # refusing for a second reason instead of the first. + sed -i 's|^openkal = .*$|openkal = { path = ".spec" }|' mcpp.toml + sed -i 's|^openkal = .*$|openkal = { path = "../../.spec" }|' examples/hello/mcpp.toml + grep -q 'path = ".spec"' mcpp.toml && grep -q 'path = "../../.spec"' examples/hello/mcpp.toml \ + || { echo "::error::the specification was not substituted"; exit 1; } - name: Install OVMF and qemu run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ovmf qemu-system-x86 @@ -234,6 +278,9 @@ jobs: run: shell: bash env: + # The specification is taken from the branch of the same name where one + # exists, so a change spanning both repositories is reviewed as a whole. + OPENKAL_BRANCH: ${{ github.head_ref || github.ref_name }} MCPP_VERSION: 2026.8.27.1 XLINGS_VERSION: v2026.8.17.2 XLINGS_NON_INTERACTIVE: '1' @@ -376,6 +423,46 @@ jobs: echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)" fi + + # ⚠️⚠️ A JOB THAT RESOLVES THE PUBLISHED SPECIFICATION CANNOT REVIEW A + # CHANGE TO ONE. + # + # Another step in this file already clones the specification and + # substitutes it, and that made the arrangement look complete. It was not: + # the steps below reached `openkal' BY VERSION, so a version under review + # --- which by definition is not published --- failed them with + # + # E_NOT_FOUND: package 'compat.openkal@0.9.0' not found in the + # synced index ... the index is current, so this name is either + # wrong or not published yet + # + # ⭐⭐ THE UNIT IS THE JOB, NOT THE REPOSITORY. Measured 2026-08-28 across + # the eight repositories of this ecosystem while one change spanned all of + # them: seven jobs in three repositories had this shape, and every one of + # those repositories ALSO had a job that substituted correctly --- which is + # what made the gap invisible to a check done a repository at a time. + # + # These jobs are green on `main` and can only be green there, because + # there the published version is the one under test. That is the property + # that makes the defect silent: it is not a check that fails, it is a + # check that cannot run at the only time it would have something to say. + - name: Point at the specification's working tree + run: | + set -euo pipefail + git clone --quiet https://github.com/mcpplibs/openkal .spec + if git -C .spec rev-parse --verify --quiet "origin/$OPENKAL_BRANCH" > /dev/null; then + git -C .spec checkout --quiet "origin/$OPENKAL_BRANCH" + echo "the specification is at $OPENKAL_BRANCH" + else + echo "the specification has no $OPENKAL_BRANCH; its default branch is used" + fi + # ⚠️ EVERY MANIFEST THAT NAMES IT, AND BY THE SAME FORM. mcpp refuses + # a graph in which one package reaches a dependency by version and + # another by path, so substituting only the root leaves the build + # refusing for a second reason instead of the first. + sed -i 's|^openkal = .*$|openkal = { path = ".spec" }|' mcpp.toml + grep -q 'path = ".spec"' mcpp.toml \ + || { echo "::error::the specification was not substituted"; exit 1; } - name: The EFI application cross-builds run: | # ⚠️ TWICE, AND THE FIRST IS ALLOWED TO FAIL — every row of this diff --git a/README.md b/README.md index 550c787..4dac704 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ target = "x86_64-windows-gnu" ldflags = ["-nostdlib", "-Wl,--subsystem,10", "-Wl,-e,efi_main"] [dependencies] -openkal = "0.5.1" +openkal = "0.9.0" openkal-uefi = "0.3.0" ``` diff --git a/examples/hello/mcpp.toml b/examples/hello/mcpp.toml index a77ca82..d8116de 100644 --- a/examples/hello/mcpp.toml +++ b/examples/hello/mcpp.toml @@ -21,5 +21,5 @@ ldflags = ["-nostdlib", "-Wl,--subsystem,10", "-Wl,-e,efi_main"] # That is the right refusal --- two forms can name two different trees --- and # it means an example inside a repository follows that repository's own # declaration while a change is in flight. -openkal = "0.8.0" +openkal = "0.9.0" openkal-uefi = { path = "../.." } From 35a4b303edb17dc88b08269e9981a50e7ff03ec1 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 29 Aug 2026 02:17:01 +0800 Subject: [PATCH 4/4] ci: substitute the specification per step, not per job Measured 2026-08-28 across the eight repositories of this ecosystem while one change spanned all of them: eight jobs in four of them called `mcpp build' at a point where the manifest still named openkal BY VERSION, so a version under review -- which by definition is not published -- failed them with E_NOT_FOUND. The mechanism is not a missing substitution. run-conformance.sh substitutes the manifest and RESTORES IT ON EXIT, correctly; every step after it is back to naming a version. So an audit asking "does this job substitute?" passes the job and misses the steps, which is how the first pass at this found three repositories and not four. These steps are green on main and can only be green there, because there the published version is the one under test. It is not a check that fails, it is a check that cannot run at the only time it would have something to say. The substitution is also portable now: the opensbi and uefi portability jobs run on macOS and Windows, where BSD sed requires an argument to -i that GNU sed refuses. --- .github/workflows/ci.yml | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3da466b..8c3168f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -191,10 +191,17 @@ jobs: # a graph in which one package reaches a dependency by version and # another by path, so substituting only the root leaves the build # refusing for a second reason instead of the first. - sed -i 's|^openkal = .*$|openkal = { path = ".spec" }|' mcpp.toml - sed -i 's|^openkal = .*$|openkal = { path = "../../.spec" }|' examples/hello/mcpp.toml - grep -q 'path = ".spec"' mcpp.toml && grep -q 'path = "../../.spec"' examples/hello/mcpp.toml \ - || { echo "::error::the specification was not substituted"; exit 1; } + # ⚠️ NOT `sed -i'. This step runs on macOS and on Windows too, and + # BSD sed requires an argument to -i that GNU sed refuses. A temporary + # file is the spelling that holds on all three. + subst() { # subst + sed "s|^openkal = .*$|openkal = { path = \"$2\" }|" "$1" > "$1.next" + mv "$1.next" "$1" + grep -q "path = \"$2\"" "$1" \ + || { echo "::error::$1 was not substituted"; exit 1; } + } + subst mcpp.toml .spec + subst examples/hello/mcpp.toml ../../.spec - name: Install OVMF and qemu run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ovmf qemu-system-x86 @@ -460,9 +467,16 @@ jobs: # a graph in which one package reaches a dependency by version and # another by path, so substituting only the root leaves the build # refusing for a second reason instead of the first. - sed -i 's|^openkal = .*$|openkal = { path = ".spec" }|' mcpp.toml - grep -q 'path = ".spec"' mcpp.toml \ - || { echo "::error::the specification was not substituted"; exit 1; } + # ⚠️ NOT `sed -i'. This step runs on macOS and on Windows too, and + # BSD sed requires an argument to -i that GNU sed refuses. A temporary + # file is the spelling that holds on all three. + subst() { # subst + sed "s|^openkal = .*$|openkal = { path = \"$2\" }|" "$1" > "$1.next" + mv "$1.next" "$1" + grep -q "path = \"$2\"" "$1" \ + || { echo "::error::$1 was not substituted"; exit 1; } + } + subst mcpp.toml .spec - name: The EFI application cross-builds run: | # ⚠️ TWICE, AND THE FIRST IS ALLOWED TO FAIL — every row of this