From dc712395a3d4bb1c13eaee3f7c94a20ac8dc5405 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 20 Sep 2026 18:40:34 +0800 Subject: [PATCH 1/7] 0.17.0 --- what this library does not supply is declared, and the declaration is asserted README has carried a table of absent facilities since this port began, and nothing executed it. It was contradicted once already: before 0.16.0 `SIG_IGN` was accepted for every signal and installed for none, so a program that asked not to be ended by the interrupt keystroke was told it had succeeded and was ended by it. `[c-abi.absent]` states each facility and the SHAPE in which its absence reaches a program: link the definition is not in the archive; the link names it. This is the shape openkal's capability model requires of an implementation (SPEC 0.14 clause 6.1, which calls a run-time report of unsupportedness a defect). enosys the definition exists and reports that it cannot act. accepted-no-effect the call succeeds and part of what it asked for is not done --- the shape of the defect 0.16.0 repaired, named so that "how many of these are there" has an answer. The set of names this library DOES supply is not enumerable here: POSIX has about twelve hundred, and enumerating it is the mistake clause 3.3 records withdrawing. The exceptions are enumerable, and twenty-one of them are listed. `tools/check-absent.sh` asserts every row against the objects this package builds, in the direction that row states: a `link` name that is defined, or an `enosys` name that is not, fails. Both directions are checked, because a script that verified only the `link` rows would pass against a table that had quietly moved every row to `enosys` --- the change that would matter most. CI's hand-written withheld list is replaced by that script. The list and the manifest were two copies of one fact, and a copy goes stale the first time only one of them is edited. The step also flips one row and requires the script to reject it, because a check that cannot fail asserts nothing. REQUIRES mcpp 2026.9.20.1. An engine that does not know a key refuses the whole manifest rather than ignoring it, so this version cannot be registered before the index floor has moved. Co-authored-by: Claude Code --- .github/workflows/ci.yml | 48 ++++++++++++++-------- README.md | 21 ++++++++++ mcpp.toml | 77 ++++++++++++++++++++++++++++++++++- tools/check-absent.sh | 88 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 215 insertions(+), 19 deletions(-) create mode 100755 tools/check-absent.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe6c0c3..e6f506f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -490,7 +490,7 @@ jobs: # accumulates one per configuration, so a search across all of them reads # definitions from a build made before the change --- which is how this # check first reported every withheld symbol as still present. - - name: What is withheld is not defined, and what is not withheld is + - name: Every [c-abi.absent] row agrees with the archive if: runner.os == 'Linux' && matrix.target == '' run: | set -euo pipefail @@ -503,30 +503,42 @@ jobs: echo " examining $n objects in $(ls -d target/*/*/)" [ "$n" -gt 100 ] || { echo "::error::only $n objects; nothing was examined"; exit 1; } - defines() { # symbol -> the number of definitions in these objects - nm $objs 2>/dev/null | grep -cE "^[0-9a-f]+ [TWi] $1\$" || true - } - + # THE LIST LIVES IN THE MANIFEST AND NOT HERE. It used to be written + # out again in this step, which made the manifest's `[c-abi.absent]` + # table and this workflow two copies of one fact --- and a copy goes + # stale the first time only one of them is edited. The script reads + # the table and checks each row against the objects in the direction + # that row's `form` states: a `link` name must not be defined, an + # `enosys` name must be. + bash tools/check-absent.sh mcpp.toml $objs + + # The other half, and a different property: that this package still + # compiles the things it is supposed to. Without it the check above + # would pass for a package that had stopped compiling anything whose + # names happen not to be in the table. `pipe` is here because openkal + # 0.8 made it expressible; `socket` and `fork` because their closures + # reach the password functions and the thread implementation, so they + # are deliberately NOT withheld and the manifest says why. + defines() { nm $objs 2>/dev/null | grep -cE "^[0-9a-f]+ [TWi] $1\$" || true; } fail=0 - for s in epoll_create1 eventfd timerfd_create inotify_init signalfd; do - d=$(defines "$s") - if [ "$d" = 0 ]; then echo " withheld, not defined: $s" - else echo "::error::$s is withheld and yet defined $d time(s)"; fail=1; fi - done - - # The other half. Without it the check above would pass for a package - # that had stopped compiling anything at all. `pipe` is here because - # openkal 0.8 made it expressible; `socket` and `fork` because their - # closures reach the password functions and the thread implementation, - # so they are deliberately NOT withheld and the manifest says why. for s in printf malloc open pipe faccessat chmod socket fork; do d=$(defines "$s") if [ "$d" -ge 1 ]; then echo " defined here: $s" else echo "::error::$s is not withheld and yet is not defined"; fail=1; fi done - [ "$fail" = 0 ] || exit 1 - echo " ok the withheld set is exactly what the manifest names" + + # THE CHECK MUST BE ABLE TO FAIL. A row moved to the other form has + # to turn this step red; without this leg the step above passes + # identically against a script that reads no objects at all. + sed 's/^mprotect = { form = "enosys"/mprotect = { form = "link"/' \ + mcpp.toml > /tmp/absent-flipped.toml + grep -q 'mprotect = { form = "link"' /tmp/absent-flipped.toml || { + echo "::error::the flip that this leg depends on did not apply"; exit 1; } + if bash tools/check-absent.sh /tmp/absent-flipped.toml $objs; then + echo "::error::a row moved to the wrong form was accepted"; exit 1 + fi + echo " ok the table is asserted, and the assertion can fail" - name: What this package is built from is not what it publishes working-directory: examples/cross-hello diff --git a/README.md b/README.md index 835e8ab..97e9f74 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,27 @@ The following are absent, and each is refused rather than quietly accepted, because a facility that reports success and does nothing is the one kind of answer that leaves a program wrong without telling it. +**Since 0.17.0 this table has an executor.** `[c-abi.absent]` in `mcpp.toml` +states each facility and the SHAPE in which its absence reaches a program --- +`link` (the definition is not in the archive), `enosys` (it is, and reports +that it cannot act), `accepted-no-effect` (the call succeeds and part of what +it asked for is not done) --- and `tools/check-absent.sh` asserts every row +against the objects this package builds, in the direction that row states. A +`link` name that turned out to be defined, or an `enosys` name that turned out +to be missing, fails the build. + +Prose alone had no executor, and it was contradicted once: before 0.16.0 +`SIG_IGN` was accepted for every signal and installed for none, so a program +that asked not to be ended by the interrupt keystroke was told it had +succeeded and was ended by it. `accepted-no-effect` is that shape, and it is +named so that "how many of these are there" is a question with an answer. + +`link` is the shape openkal's own capability model requires of an +implementation (SPEC 0.14 clause 6.1, which calls a run-time report of +unsupportedness a defect). The other two are departures from it, and a reader +counting them is reading the cost of presenting POSIX above an interface that +does not carry all of it. + | Absent | What a program observes | Why | | --- | --- | --- | | signal handlers | `sigaction` reports `ENOSYS` for any handler other than the default or ignore. **Since 0.16.0 a disposition is accepted only where it is the one already in effect**: `SIG_DFL` succeeds for every signal but `SIGPIPE`, `SIG_IGN` succeeds for `SIGPIPE` alone, and the enquiry reports `SIG_IGN` for `SIGPIPE` rather than a zeroed record | openkal has no asynchronous delivery. A handler that was accepted and could never run would be silently wrong; masking, which has nothing to mask, succeeds. Until 0.16.0 `SIG_IGN` was accepted for every signal and installed for none, so a program that asked not to be ended by the interrupt keystroke was told it had succeeded and was ended by it. `SIGPIPE` is the one disposition that is not the default, and not by accident: openkal requires a write to a stream whose far end is gone to report the condition rather than end the program, so an implementation beneath has already arranged that the signal does nothing. | diff --git a/mcpp.toml b/mcpp.toml index 65ca208..c4f3589 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-musl" -version = "0.16.0" +version = "0.17.0" description = "musl 1.2.5 redirected onto openkal: one C library, ported once, above every implementation of the specification rather than above one kernel." license = "Apache-2.0" @@ -38,6 +38,81 @@ data-model = "arch-default" wchar = 32 builtins = "iso" +# WHAT THIS C LIBRARY DOES NOT SUPPLY, AND IN WHAT SHAPE EACH ABSENCE REACHES A +# PROGRAM. +# +# The set of names a C library supplies is not enumerable here --- POSIX has +# about twelve hundred --- and enumerating it is the mistake openkal's own +# specification records withdrawing (SPEC 0.14 clause 3.3, on the name it gave +# to a set of interfaces and then took back). The exceptions are enumerable. +# README's "The following are absent" table has carried them in prose since +# this port began, and nothing executed that prose: it was contradicted once +# already, in the release before this one, where `SIG_IGN` was accepted for +# every signal and installed for none and a program that asked not to be ended +# by the interrupt keystroke was told it had succeeded and was ended by it. +# +# `form` is how the absence reaches the program, and it is the load-bearing +# field: +# +# link the definition is not in the archive; the program +# fails to link, naming the symbol. This is the shape +# openkal's capability model requires of an +# implementation (clause 6.1: a conforming +# implementation shall not provide an interface whose +# operations report a lack of support at run time). +# enosys the definition exists and reports that it cannot act. +# POSIX has a value for this and a caller can read it, +# which is what makes it a bounded answer rather than a +# silent one. +# accepted-no-effect the call succeeds and part of what it asked for is not +# done. This is the shape of the defect the previous +# release repaired, and it is named so that "how many of +# these are there" is a question with an answer. +# +# tools/check-absent.sh asserts every row against the built archive: a `link` +# name that IS defined, or an `enosys` name that is NOT, fails the build. +# +# THIS TABLE REQUIRES mcpp 2026.9.20.1. An engine that does not know a key +# refuses the whole manifest rather than ignoring the key, so a release +# carrying this table cannot be registered before the index floor has moved: +# mcpp releases, `index.toml` raises `min_mcpp`, and only then does this +# version appear in the index. The order is not a convenience --- a descriptor +# registered ahead of the floor makes every client below it fail to load this +# package at all, which is a worse answer than not having the table. +[c-abi.absent] + +# Readiness held by the environment. A set is a facility of one kernel rather +# than a capability; `poll` and `select` ask each descriptor in turn, which is +# what an interface without a set permits. These five are not compiled at all +# (see the withheld sources below), so the link names them. +epoll_create = { form = "link", note = "openkal has no readiness set; poll and select ask each descriptor in turn" } +epoll_create1 = { form = "link", note = "as epoll_create" } +epoll_ctl = { form = "link", note = "as epoll_create" } +epoll_wait = { form = "link", note = "as epoll_create" } +epoll_pwait = { form = "link", note = "as epoll_create" } +timerfd_create = { form = "link", note = "a descriptor that is created and never becomes ready is worse than one that cannot be created" } +timerfd_settime = { form = "link", note = "as timerfd_create" } +timerfd_gettime = { form = "link", note = "as timerfd_create" } +eventfd = { form = "link", note = "as timerfd_create" } +eventfd_read = { form = "link", note = "as timerfd_create" } +eventfd_write = { form = "link", note = "as timerfd_create" } +signalfd = { form = "link", note = "as timerfd_create" } +inotify_init = { form = "link", note = "as timerfd_create" } +inotify_init1 = { form = "link", note = "as timerfd_create" } +inotify_add_watch = { form = "link", note = "as timerfd_create" } +inotify_rm_watch = { form = "link", note = "as timerfd_create" } + +# Reported to the caller, which can read the value and act on it. +mprotect = { form = "enosys", note = "openkal has no operation upon a mapping's protection; musl asks for a guard page and proceeds without one when told this" } +chown = { form = "enosys", note = "a capability-oriented environment has no principal for an owner to name" } +fchown = { form = "enosys", note = "as chown" } +lchown = { form = "enosys", note = "as chown" } + +# The call succeeds and part of what it asked for is not done. Each of these +# is a place where refusing would be worse than the partial answer, and the +# README row beside it says why. +tcsetattr = { form = "accepted-no-effect", note = "openkal names three positions of the terminal mode word; a request that alters output post-processing, the line speed, the control characters or VMIN/VTIME is accepted and that part has no effect" } + [dependencies] openkal = "0.14.0" diff --git a/tools/check-absent.sh b/tools/check-absent.sh new file mode 100755 index 0000000..a9c389a --- /dev/null +++ b/tools/check-absent.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# Asserts `[c-abi.absent]` against the archive this package actually builds. +# +# check-absent.sh ... +# +# The table in mcpp.toml states, for each facility this C library does not +# supply, the SHAPE in which the absence reaches a program. Two of the three +# shapes are checkable from the artefact alone, and they are checked in +# opposite directions: +# +# form = "link" the definition must NOT be in the archive. This is the +# shape openkal's capability model requires of an +# implementation (SPEC 0.14 clause 6.1), and a name that +# turned out to be defined would mean a program links and +# then meets the absence somewhere it cannot read it. +# form = "enosys" the definition MUST be in the archive. A name that +# turned out to be missing would fail the link instead, +# which is a different contract from the one stated --- and +# the one a caller prepared to read `ENOSYS` is not +# prepared for. +# +# `accepted-no-effect` is not checkable here: it is a statement about what a +# call does, not about whether it exists, and the conformance suite is where a +# behaviour is examined. It is listed so that "how many of these are there" has +# an answer; this script checks that such a name is defined, which is the most +# an artefact can say about it. +# +# THE DIRECTIONS ARE BOTH CHECKED ON PURPOSE. A script that only verified the +# `link` rows would pass against a manifest that had quietly moved every row to +# `enosys`, which is the change that would matter most. +set -euo pipefail + +manifest="${1:?usage: check-absent.sh ...}" +shift +[ "$#" -gt 0 ] || { echo "no archive or object given" >&2; exit 2; } + +NM="${NM:-nm}" + +defined="$($NM --defined-only "$@" 2>/dev/null \ + | awk '$2=="T"||$2=="W"||$2=="R"||$2=="D"||$2=="B"||$2=="S"{print $3}' \ + | sed 's/^_//' | sort -u || true)" + +# An empty surface is never a conforming one: nothing found means the objects +# were wrong or the symbols were not recognised, and reporting success would +# conceal both. The same rule tools/check-surface.sh states, for the same +# reason. +if [ -z "$defined" ]; then + echo "no defined symbol was found; the objects or the symbol format are wrong" >&2 + exit 1 +fi + +status=0 +rows=0 +in_table=0 +while IFS= read -r line; do + case "$line" in + '[c-abi.absent]'*) in_table=1; continue ;; + '['*) in_table=0; continue ;; + esac + [ "$in_table" -eq 1 ] || continue + case "$line" in ''|'#'*) continue ;; esac + name="${line%%=*}"; name="${name// /}" + [ -n "$name" ] || continue + case "$line" in + *'form = "link"'*) want=absent ;; + *'form = "enosys"'*) want=present ;; + *'form = "accepted-no-effect"'*) want=present ;; + *) echo "row '$name' names no form this script knows" >&2; status=1; continue ;; + esac + rows=$((rows+1)) + if grep -qxF -- "$name" <<< "$defined"; then found=present; else found=absent; fi + if [ "$found" != "$want" ]; then + if [ "$want" = absent ]; then + echo "$name is declared absent at the link and IS defined in the archive" >&2 + else + echo "$name is declared to report its refusal and is NOT defined in the archive" >&2 + fi + status=1 + fi +done < "$manifest" + +if [ "$rows" -eq 0 ]; then + echo "[c-abi.absent] has no rows; this script asserted nothing" >&2 + exit 1 +fi + +[ "$status" -eq 0 ] && echo "every [c-abi.absent] row agrees with the archive: $rows row(s)" +exit "$status" From c9607199a3e6176b6b47c6cb571ceece1aa86b5c Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 20 Sep 2026 18:55:47 +0800 Subject: [PATCH 2/7] TEMPORARY: build mcpp from its pull-request branch so this can be measured before the release [c-abi.absent] needs an engine that knows the key. MCPP_SOURCE_REF exists for exactly this, and the line is removed before this pull request merges --- the final state is MCPP_VERSION at 2026.9.20.1 and this back to the repository variable. Co-authored-by: Claude Code --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6f506f..2e29700 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,12 @@ on: required: false default: "" env: - MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF }} + # TEMPORARY, AND REMOVED BEFORE THIS PULL REQUEST MERGES. `[c-abi.absent]` + # needs an engine that knows the key, and 2026.9.20.1 is not released yet; + # building mcpp from its own pull-request branch is what MCPP_SOURCE_REF is + # for. The final state is the pin below moved to 2026.9.20.1 and this line + # back to the repository variable. + MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF || 'cenv-probe-target-and-interface-enumeration' }} MCPP_VERSION: 2026.9.18.3 XLINGS_VERSION: v2026.8.17.2 XLINGS_NON_INTERACTIVE: '1' From 9d28d99a89579e2c23814b2589a85e2ffb553e7c Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 20 Sep 2026 19:43:24 +0800 Subject: [PATCH 3/7] three more rows, and the one README row the schema cannot carry sigaction, chmod and fchmodat are listed as `enosys`: each reports its refusal for the inputs the README names, and the check asserts each is defined, which is what `enosys` claims about the artefact. "A mode given at creation" has no entry. It is about an argument of `open` and `mkdir`, not about those calls --- they do what they are for and the mode is what is not applied --- so an entry keyed on `open` would say the call is absent, which is false and worse than no entry. The manifest keys on names, so a facility narrower than a name stays in the README until the schema can say it. Written down in both places rather than rounded off. 24 rows agree with the archive. Co-authored-by: Claude Code --- README.md | 7 +++++++ mcpp.toml | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/README.md b/README.md index 97e9f74..57dc5f1 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,13 @@ unsupportedness a defect). The other two are departures from it, and a reader counting them is reading the cost of presenting POSIX above an interface that does not carry all of it. +**One row of this table has no entry in the manifest, and that is stated rather +than rounded off.** "A mode given at creation" is about an ARGUMENT of `open` +and `mkdir`, not about those calls: they do what they are for and the mode is +what is not applied. An entry keyed on `open` would say the call is absent, +which is false and worse than no entry. The manifest keys on names, so a +facility narrower than a name stays here until the schema can say it. + | Absent | What a program observes | Why | | --- | --- | --- | | signal handlers | `sigaction` reports `ENOSYS` for any handler other than the default or ignore. **Since 0.16.0 a disposition is accepted only where it is the one already in effect**: `SIG_DFL` succeeds for every signal but `SIGPIPE`, `SIG_IGN` succeeds for `SIGPIPE` alone, and the enquiry reports `SIG_IGN` for `SIGPIPE` rather than a zeroed record | openkal has no asynchronous delivery. A handler that was accepted and could never run would be silently wrong; masking, which has nothing to mask, succeeds. Until 0.16.0 `SIG_IGN` was accepted for every signal and installed for none, so a program that asked not to be ended by the interrupt keystroke was told it had succeeded and was ended by it. `SIGPIPE` is the one disposition that is not the default, and not by accident: openkal requires a write to a stream whose far end is gone to report the condition rather than end the program, so an implementation beneath has already arranged that the signal does nothing. | diff --git a/mcpp.toml b/mcpp.toml index c4f3589..87cf270 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -104,6 +104,9 @@ inotify_rm_watch = { form = "link", note = "as timerfd_create" } # Reported to the caller, which can read the value and act on it. mprotect = { form = "enosys", note = "openkal has no operation upon a mapping's protection; musl asks for a guard page and proceeds without one when told this" } +sigaction = { form = "enosys", note = "a disposition other than the one already in effect; openkal has no asynchronous delivery, and a handler that was accepted and could never run would be silently wrong" } +chmod = { form = "enosys", note = "a request whose read or write bits differ from what stat already reports; a capability-oriented environment has no principal for a permission to name" } +fchmodat = { form = "enosys", note = "as chmod" } chown = { form = "enosys", note = "a capability-oriented environment has no principal for an owner to name" } fchown = { form = "enosys", note = "as chown" } lchown = { form = "enosys", note = "as chown" } @@ -111,6 +114,14 @@ lchown = { form = "enosys", note = "as chown" } # The call succeeds and part of what it asked for is not done. Each of these # is a place where refusing would be worse than the partial answer, and the # README row beside it says why. +# +# ONE README ROW IS NOT EXPRESSIBLE HERE, AND THAT IS WORTH STATING RATHER THAN +# ROUNDING OFF. "A mode given at creation" is about an ARGUMENT of `open` and +# `mkdir`, not about those calls: they do what they are for, and the mode is +# what is not applied. A row keyed on `open` would say the call is absent, +# which is false and worse than the absence of a row. The table keys on names, +# so a facility narrower than a name stays in the README until the schema can +# say it. tcsetattr = { form = "accepted-no-effect", note = "openkal names three positions of the terminal mode word; a request that alters output post-processing, the line speed, the control characters or VMIN/VTIME is accepted and that part has no effect" } [dependencies] From bc57ed20fafa26b7d58ccb0b165f7b45ee7e2a7a Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 20 Sep 2026 19:45:28 +0800 Subject: [PATCH 4/7] the implementation pins move with the trees beside them openkal-linux is 0.15.0 and openkal-windows is 0.10.0 since each began stating which interfaces it provides. CI builds this package against the working trees of those repositories and refuses a pin that names a different version, which is the guard that caught this: 'Nothing is wrong with either; they are not in step.' Co-authored-by: Claude Code --- mcpp.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mcpp.toml b/mcpp.toml index 87cf270..bc8513d 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -140,7 +140,7 @@ openkal = "0.14.0" # # The consequence for a program is that it names this package and nothing else. [target.'cfg(os = "linux")'.dependencies] -openkal-linux = { version = "0.14.0", features = ["standalone"] } +openkal-linux = { version = "0.15.0", features = ["standalone"] } [target.'cfg(os = "macos")'.dependencies] openkal-macos = { version = "0.11.0", features = ["standalone"] } @@ -179,7 +179,7 @@ openkal-opensbi = { version = "0.8.0", features = ["standalone"] } defines = ["OKM_HAS_FS=0", "OKM_HAS_PROCESS=0", "OKM_HAS_TASK=0"] [target.'cfg(windows)'.dependencies] -openkal-windows = { version = "0.9.0", features = ["standalone"] } +openkal-windows = { version = "0.10.0", features = ["standalone"] } # The feature macros musl's own build establishes. # From eed93df876b9e4e31baeb187db30685f95cf1760 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 20 Sep 2026 20:03:48 +0800 Subject: [PATCH 5/7] the second thing this table cannot say: an absence that varies by target `fork` is composed here from `openkal.space`, and the reference to it is weak: a backend that provides the interface gets a working `fork`, one that does not gets `ENOSYS`. openkal-linux provides it; openkal-windows declines it, and its README gives the reason --- constructing the copy out of `CreateProcessW` would be present, would look like the operation, and would not produce a copy of the caller, which is the simulation clause 3.1 forbids. Every row of `[c-abi.absent]` is unconditional, so `fork` written as `enosys` would be false on Linux and omitting it is silent on Windows. Silence is the lesser of the two and the row returns when the schema carries `targets`. Written down beside the one already there, because a table that answered either by rounding it off would be a table nobody could assert against --- the state this one exists to leave. Co-authored-by: Claude Code --- README.md | 23 +++++++++++++++++------ mcpp.toml | 28 +++++++++++++++++++++------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 57dc5f1..b7bb4f2 100644 --- a/README.md +++ b/README.md @@ -273,12 +273,23 @@ unsupportedness a defect). The other two are departures from it, and a reader counting them is reading the cost of presenting POSIX above an interface that does not carry all of it. -**One row of this table has no entry in the manifest, and that is stated rather -than rounded off.** "A mode given at creation" is about an ARGUMENT of `open` -and `mkdir`, not about those calls: they do what they are for and the mode is -what is not applied. An entry keyed on `open` would say the call is absent, -which is false and worse than no entry. The manifest keys on names, so a -facility narrower than a name stays here until the schema can say it. +**Two things this table says and the manifest cannot, stated rather than rounded +off.** + +*A facility narrower than a name.* "A mode given at creation" is about an +ARGUMENT of `open` and `mkdir`, not about those calls: they do what they are +for and the mode is what is not applied. An entry keyed on `open` would say the +call is absent, which is false and worse than no entry. + +*An absence that varies by target.* `fork` is composed here from +`openkal.space`, and the reference to it is weak: a backend that provides the +interface gets a working `fork`, and one that does not gets `ENOSYS`. +openkal-linux provides it; openkal-windows declines it, and its README gives +the reason --- constructing the copy out of `CreateProcessW` would be present, +would look like the operation, and would not produce a copy of the caller. +Every row of `[c-abi.absent]` is unconditional, so `fork` written as `enosys` +would be false on Linux and omitting it is silent on Windows; silence is the +lesser of the two, and the row returns when the schema carries `targets`. | Absent | What a program observes | Why | | --- | --- | --- | diff --git a/mcpp.toml b/mcpp.toml index bc8513d..697446a 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -115,13 +115,27 @@ lchown = { form = "enosys", note = "as chown" } # is a place where refusing would be worse than the partial answer, and the # README row beside it says why. # -# ONE README ROW IS NOT EXPRESSIBLE HERE, AND THAT IS WORTH STATING RATHER THAN -# ROUNDING OFF. "A mode given at creation" is about an ARGUMENT of `open` and -# `mkdir`, not about those calls: they do what they are for, and the mode is -# what is not applied. A row keyed on `open` would say the call is absent, -# which is false and worse than the absence of a row. The table keys on names, -# so a facility narrower than a name stays in the README until the schema can -# say it. +# TWO THINGS THIS TABLE CANNOT SAY TODAY, STATED RATHER THAN ROUNDED OFF. +# +# A FACILITY NARROWER THAN A NAME. "A mode given at creation" is about an +# ARGUMENT of `open` and `mkdir`, not about those calls: they do what they are +# for, and the mode is what is not applied. A row keyed on `open` would say the +# call is absent, which is false and worse than the absence of a row. +# +# AN ABSENCE THAT VARIES BY TARGET. `fork` is composed here from +# `openkal.space` (port/src/okm_fork.c), which the reference to it takes +# WEAKLY: a backend that provides the interface gets a working `fork`, and one +# that does not gets `-ENOSYS`. openkal-linux provides it; openkal-windows +# declines it, for the reason its own README gives --- constructing the copy +# out of `CreateProcessW` would be present, would look like the operation, and +# would not produce a copy of the caller. So `fork` is `enosys` on Windows and +# works on Linux, and every row here is unconditional. Writing it as `enosys` +# would be false on Linux; omitting it is silent on Windows, and silence is the +# lesser of the two. The row returns when the schema carries `targets`. +# +# Both stay in the README until then. A table that answered these two by +# rounding them off would be a table nobody could assert against, which is the +# state this one exists to leave. tcsetattr = { form = "accepted-no-effect", note = "openkal names three positions of the terminal mode word; a request that alters output post-processing, the line speed, the control characters or VMIN/VTIME is accepted and that part has no effect" } [dependencies] From e8b0f8585e33baf4ff6700c91d4c87289400f6ae Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sun, 20 Sep 2026 20:48:44 +0800 Subject: [PATCH 6/7] the absence table moves to the top level, and asks nothing of the index floor mcpp ignores a top-level table it does not know and refuses an unknown MEMBER of a table it does know. Written as `[c-abi].absent`, this file made every mcpp below 2026.9.20.1 refuse the WHOLE manifest on every target -- measured against the published 2026.9.18.3 archive on exactly this file, not reasoned about. Everything the table does is diagnostic, so an engine that ignores it reports the same raw link error it reports today. An engine that refused it would have taken this package away entirely and forced `index.toml` to raise `min_mcpp`, costing every client below that floor the whole index for a note they merely would not receive. The consequence for this release: 0.17.0 may be registered as soon as it is tagged, with no floor move ahead of it. The comment above the table records that, in place of the release-ordering it previously described. tools/check-absent.sh reads the new spelling and still finds all 24 rows. --- .github/workflows/ci.yml | 6 +++--- README.md | 4 ++-- mcpp.toml | 23 +++++++++++++++-------- tools/check-absent.sh | 8 ++++---- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e29700..d8eb39e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ on: required: false default: "" env: - # TEMPORARY, AND REMOVED BEFORE THIS PULL REQUEST MERGES. `[c-abi.absent]` + # TEMPORARY, AND REMOVED BEFORE THIS PULL REQUEST MERGES. `[c-abi-absent]` # needs an engine that knows the key, and 2026.9.20.1 is not released yet; # building mcpp from its own pull-request branch is what MCPP_SOURCE_REF is # for. The final state is the pin below moved to 2026.9.20.1 and this line @@ -495,7 +495,7 @@ jobs: # accumulates one per configuration, so a search across all of them reads # definitions from a build made before the change --- which is how this # check first reported every withheld symbol as still present. - - name: Every [c-abi.absent] row agrees with the archive + - name: Every [c-abi-absent] row agrees with the archive if: runner.os == 'Linux' && matrix.target == '' run: | set -euo pipefail @@ -509,7 +509,7 @@ jobs: [ "$n" -gt 100 ] || { echo "::error::only $n objects; nothing was examined"; exit 1; } # THE LIST LIVES IN THE MANIFEST AND NOT HERE. It used to be written - # out again in this step, which made the manifest's `[c-abi.absent]` + # out again in this step, which made the manifest's `[c-abi-absent]` # table and this workflow two copies of one fact --- and a copy goes # stale the first time only one of them is edited. The script reads # the table and checks each row against the objects in the direction diff --git a/README.md b/README.md index b7bb4f2..8087cd2 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ The following are absent, and each is refused rather than quietly accepted, because a facility that reports success and does nothing is the one kind of answer that leaves a program wrong without telling it. -**Since 0.17.0 this table has an executor.** `[c-abi.absent]` in `mcpp.toml` +**Since 0.17.0 this table has an executor.** `[c-abi-absent]` in `mcpp.toml` states each facility and the SHAPE in which its absence reaches a program --- `link` (the definition is not in the archive), `enosys` (it is, and reports that it cannot act), `accepted-no-effect` (the call succeeds and part of what @@ -287,7 +287,7 @@ interface gets a working `fork`, and one that does not gets `ENOSYS`. openkal-linux provides it; openkal-windows declines it, and its README gives the reason --- constructing the copy out of `CreateProcessW` would be present, would look like the operation, and would not produce a copy of the caller. -Every row of `[c-abi.absent]` is unconditional, so `fork` written as `enosys` +Every row of `[c-abi-absent]` is unconditional, so `fork` written as `enosys` would be false on Linux and omitting it is silent on Windows; silence is the lesser of the two, and the row returns when the schema carries `targets`. diff --git a/mcpp.toml b/mcpp.toml index 697446a..363f99e 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -72,14 +72,21 @@ builtins = "iso" # tools/check-absent.sh asserts every row against the built archive: a `link` # name that IS defined, or an `enosys` name that is NOT, fails the build. # -# THIS TABLE REQUIRES mcpp 2026.9.20.1. An engine that does not know a key -# refuses the whole manifest rather than ignoring the key, so a release -# carrying this table cannot be registered before the index floor has moved: -# mcpp releases, `index.toml` raises `min_mcpp`, and only then does this -# version appear in the index. The order is not a convenience --- a descriptor -# registered ahead of the floor makes every client below it fail to load this -# package at all, which is a worse answer than not having the table. -[c-abi.absent] +# THIS TABLE ASKS NOTHING OF AN OLDER ENGINE, AND ITS SPELLING IS WHY. mcpp +# ignores a top-level table it does not know and refuses an unknown MEMBER of +# a table it does know, so `[c-abi].absent` --- which reads better, and is +# where the first draft of this file put it --- made every mcpp below +# 2026.9.20.1 refuse THIS WHOLE MANIFEST on every target. Measured against the +# published 2026.9.18.3 archive on exactly this file. Everything the table +# does is diagnostic, so an engine that ignores it reports the same raw link +# error it reports today; an engine that refused it would have taken this +# package away entirely and forced the index floor up, costing every client +# below that floor the whole index for a note they merely would not receive. +# +# The consequence here: 0.17.0 needs no `min_mcpp` move and may be registered +# as soon as it is tagged. An older client builds it and gets the link error +# without the sentence that explains it, which is what it gets today. +[c-abi-absent] # Readiness held by the environment. A set is a facility of one kernel rather # than a capability; `poll` and `select` ask each descriptor in turn, which is diff --git a/tools/check-absent.sh b/tools/check-absent.sh index a9c389a..66759c6 100755 --- a/tools/check-absent.sh +++ b/tools/check-absent.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Asserts `[c-abi.absent]` against the archive this package actually builds. +# Asserts `[c-abi-absent]` against the archive this package actually builds. # # check-absent.sh ... # @@ -54,7 +54,7 @@ rows=0 in_table=0 while IFS= read -r line; do case "$line" in - '[c-abi.absent]'*) in_table=1; continue ;; + '[c-abi-absent]'*) in_table=1; continue ;; '['*) in_table=0; continue ;; esac [ "$in_table" -eq 1 ] || continue @@ -80,9 +80,9 @@ while IFS= read -r line; do done < "$manifest" if [ "$rows" -eq 0 ]; then - echo "[c-abi.absent] has no rows; this script asserted nothing" >&2 + echo "[c-abi-absent] has no rows; this script asserted nothing" >&2 exit 1 fi -[ "$status" -eq 0 ] && echo "every [c-abi.absent] row agrees with the archive: $rows row(s)" +[ "$status" -eq 0 ] && echo "every [c-abi-absent] row agrees with the archive: $rows row(s)" exit "$status" From de4d902380e034ded4c290a107614e957678284a Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sun, 20 Sep 2026 20:58:30 +0800 Subject: [PATCH 7/7] this release no longer waits on mcpp's, and CI proves the claim it rests on The branch built mcpp from its own pull-request branch, because the first spelling of the absence table was `[c-abi].absent` and that genuinely needed an unreleased engine: nested, every older mcpp refused the whole manifest. Top-level, it does not. The temporary MCPP_SOURCE_REF override is gone and the engine pin stays at 2026.9.18.3 -- the release that predates the table -- because building green against it is the end-to-end evidence for what the table's own comment claims: a client below 2026.9.20.1 keeps this package and loses only the note. tools/check-absent.sh asserts the rows against the OBJECTS and reads the manifest itself, so it needs nothing from the engine. 0.17.0 can therefore be tagged without waiting for mcpp 2026.9.20.1. --- .github/workflows/ci.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8eb39e..06238d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,12 +30,19 @@ on: required: false default: "" env: - # TEMPORARY, AND REMOVED BEFORE THIS PULL REQUEST MERGES. `[c-abi-absent]` - # needs an engine that knows the key, and 2026.9.20.1 is not released yet; - # building mcpp from its own pull-request branch is what MCPP_SOURCE_REF is - # for. The final state is the pin below moved to 2026.9.20.1 and this line - # back to the repository variable. - MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF || 'cenv-probe-target-and-interface-enumeration' }} + MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF }} + # THE PIN STAYS ON THE RELEASE THAT PREDATES `[c-abi-absent]`, AND THAT IS + # THE POINT. The table is a top-level one, so an engine that does not know + # it ignores it (measured against this published archive on this file); the + # rows are asserted against the OBJECTS by tools/check-absent.sh, which + # reads the manifest itself and needs nothing from the engine. Building this + # package green here is therefore the end-to-end evidence for the claim the + # table's own comment makes: a client below 2026.9.20.1 keeps this package. + # + # An earlier draft of this branch built mcpp from its own pull-request + # branch through MCPP_SOURCE_REF, because the first spelling of the table + # WAS nested and did need the unreleased engine. Moving the table to the top + # level removed that need, and with it this release's dependency on mcpp's. MCPP_VERSION: 2026.9.18.3 XLINGS_VERSION: v2026.8.17.2 XLINGS_NON_INTERACTIVE: '1'