Skip to content

The unit a program belongs to is a resource, not a flag --- clause 7.1 said so - #26

Merged
Sunrisepeak merged 7 commits into
mainfrom
spawn-is-one-operation
Aug 30, 2026
Merged

The unit a program belongs to is a resource, not a flag --- clause 7.1 said so#26
Sunrisepeak merged 7 commits into
mainfrom
spawn-is-one-operation

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

0.11 first spelled this as KAL_SPAWN_OWN_JOB': make the started program a unit, and let kal_process_terminate' reach it afterwards. Two implementations
satisfied that with no stored state, because getpgid(pid) == pid' recovers the association from the kernel. The third could not: it can FORM a unit --- a job object is exactly this --- but cannot recover one from a process handle, and kal_process' is one word already holding the process.

⭐ CLAUSE 7.1 STATES MECHANICALLY WHAT THAT MEANS: an implementation that must
maintain a translation table, a registry, or a name resolver in order to satisfy
this specification indicates that THE SPECIFICATION has taken a shape borrowed
from one environment, and the shape is at fault. So the flag was the defect.

⚠️ AND THE OBVIOUS REPAIR WOULD HAVE MOVED THE DEFECT RATHER THAN REMOVED IT. An
operation that opens an empty unit is natural where units are created and then
filled, and impossible where a unit is created BY its first member --- a process
group's identity is a process's, and there is nothing to open. That is the same
clause 7.1 finding spelled the other way round.

⇒ So the identity is established at the first start and reported to the caller:
`kal_spawn.job' is null, or points at a word that is zero (form a unit, and its
identity is written here) or non-zero (join that one). Both kinds of system
perform that remembering nothing.

⭐ Two things improve that were not the point:
· `kal_process_terminate' now means ONE program, always. Under the flag its
meaning turned on a property of the handle no caller could see, which is
the kind of hidden state this interface exists to avoid.
· Several programs can be started into one unit, which a flag cannot say ---
and a shell runner with one timeout over several commands wants exactly that.

⚠️ Recorded rather than glossed, at the declarations: a unit must NOT be ended by
releasing it (this system's job objects can be asked to, and must not be, or one
operation would mean two things); and a process group is named by an identifier
that is reused, so the two kinds of unit are not equally strong.

Conformance against openkal-linux: 141 held, 0 did not hold.

Three declarations --- `kal_process_spawn', `..._with' and `..._bound' --- were
never three operations. They were one operation and three MODIFIERS of it,
spelled apart for exactly one reason: clause 8 forbids adding an argument to a
declaration that exists, so each modifier had to arrive as a new name.

⭐ The 0.10 comment on `..._bound' predicted where that ends, in terms:
"Declaring every combination is how an interface acquires four spawns and then
eight." Two more modifiers then arrived TOGETHER --- the directory a program runs
in, and terminating what a started program itself started --- and the evidence
that they arrive together is a single consumer signature whose child calls
`setpgid(0, 0)' and `chdir(cwd)' on adjacent lines. Four orthogonal modifiers is
sixteen declarations, each written in every implementation.

⇒ The modifiers moved into `struct kal_spawn' and the family collapsed to one
declaration. `..._with' and `..._bound' are REMOVED rather than kept beside it:
openkal has no users outside this ecosystem, so there is nobody to keep a second
spelling for, and an interface offering two ways to say one thing must explain
the difference for ever. A caller written against the old shape fails to
COMPILE, which is the loud failure and not the quiet one.

⚠️ Clause 8 was set aside to do this, deliberately and once. SPEC entry 14 says
so, says what it costs --- `kal_spawn' is frozen by clause 5.3, so a future
modifier carrying a PARAMETER rather than a flag will still need its own
declaration --- and says that this is not a precedent for doing it again once
there is written code to protect.

⚠️ The conformance suite observes the REFUSAL and not the effect, and says why:
openkal has no operation that reads a working directory (deliberately --- it has
no ambient one) and none that enumerates processes, so nothing written against
openkal alone can tell whether an implementation honoured either flag. They are
in the same category as the streams a spawn is given. The effects are observed
where they can be, in openkal-musl's probes.

Surface 101 -> 99. Conformance against openkal-linux: 140 held, 0 did not.
…remember it

A macro does not cross a module boundary, and twice a capability word was added
to a header, used from a module consumer, and failed to compile there:
`KAL_LOCK_*' in 0.10 and `KAL_SPAWN_*' in 0.11. Both were found by a build in
another repository. The interface modules give the well-named subset
(`kal::fs::lock::exclusive'); what was missing was anything that made the
mapping COMPLETE.

⇒ src/macros.cppm is generated from the headers and CI regenerates and diffs it,
so a macro added without a module spelling is a red build here rather than a
consumer's compile error two repositories away. 80 constants; the one
function-like macro is listed as skipped, so "absent" is a stated fact rather
than a gap.

⚠️ THE DECLARED NAME CANNOT BE THE MACRO'S NAME. The macro is still defined when
the constant is declared, so `constexpr kal_uintptr KAL_VERSION_MAJOR = …'
expands on the left as well and becomes `constexpr kal_uintptr 0u = …'.
Lower-casing gives an identifier the preprocessor does not touch, and matches
the spelling the interface modules already use.

⚠️⚠️ AND THE GUARD DID NOT BITE THE FIRST TIME. `git diff` against an untracked
file reports nothing, so the step passed while checking nothing --- which is worse
than having no step at all. Verified in both directions after tracking the file:
adding a macro to a header produces a diff, restoring the header clears it.
…al means

The generated constants keep the C spelling with a `_M' suffix rather than being
lower-cased. The C spelling is what a reader is looking for --- they arrived here
because a header said `KAL_SPAWN_OWN_JOB' --- so it is kept and marked;
`KAL_SPAWN_OWN_JOB_M' is a different token, so the preprocessor leaves it alone.

⚠️⚠️ AND SPEC ENTRY 15 CORRECTS SOMETHING I HAD WRITTEN AS AN IMPLEMENTATION
LIMIT. openkal-windows refuses KAL_SPAWN_OWN_JOB, and I recorded the reason as
"nowhere to keep the job handle". That reads as a shortcoming of that
implementation. It is not.

Clause 7.1 states the test mechanically: an implementation that must maintain a
translation table, a registry, or a name resolver in order to satisfy this
specification indicates that THE SPECIFICATION has taken a shape borrowed from
one environment, and the shape is at fault. Two implementations satisfy the flag
with no stored state because `getpgid(pid) == pid' recovers the association from
the kernel; the third would need a registry. So the flag assumes an environment
where the unit is discoverable from a member, and that assumption is the defect.

⇒ Entry 15 records it as OPEN rather than settled, and says why the obvious
reshape --- a unit the caller holds --- is not simply asserted here: Windows makes
such a unit externally and assigns members, while a process group is made by a
member from inside, and a shape natural to one and not the other is the same
defect spelled the other way round.
…1 said so

0.11 first spelled this as `KAL_SPAWN_OWN_JOB': make the started program a unit,
and let `kal_process_terminate' reach it afterwards. Two implementations
satisfied that with no stored state, because `getpgid(pid) == pid' recovers the
association from the kernel. The third could not: it can FORM a unit --- a job
object is exactly this --- but cannot recover one from a process handle, and
`kal_process' is one word already holding the process.

⭐ CLAUSE 7.1 STATES MECHANICALLY WHAT THAT MEANS: an implementation that must
maintain a translation table, a registry, or a name resolver in order to satisfy
this specification indicates that THE SPECIFICATION has taken a shape borrowed
from one environment, and the shape is at fault. So the flag was the defect.

⚠️ AND THE OBVIOUS REPAIR WOULD HAVE MOVED THE DEFECT RATHER THAN REMOVED IT. An
operation that opens an empty unit is natural where units are created and then
filled, and impossible where a unit is created BY its first member --- a process
group's identity is a process's, and there is nothing to open. That is the same
clause 7.1 finding spelled the other way round.

⇒ So the identity is established at the first start and reported to the caller:
`kal_spawn.job' is null, or points at a word that is zero (form a unit, and its
identity is written here) or non-zero (join that one). Both kinds of system
perform that remembering nothing.

⭐ Two things improve that were not the point:
  · `kal_process_terminate' now means ONE program, always. Under the flag its
    meaning turned on a property of the handle no caller could see, which is
    the kind of hidden state this interface exists to avoid.
  · Several programs can be started into one unit, which a flag cannot say ---
    and a shell runner with one timeout over several commands wants exactly that.

⚠️ Recorded rather than glossed, at the declarations: a unit must NOT be ended by
releasing it (this system's job objects can be asked to, and must not be, or one
operation would mean two things); and a process group is named by an identifier
that is reused, so the two kinds of unit are not equally strong.

Conformance against openkal-linux: 141 held, 0 did not hold.
…self

⭐ THE FLAG BECAME A HANDLE, AND CLAUSE 7.1 IS WHAT DECIDED IT. `KAL_SPAWN_OWN_JOB'
asked that a started program form a unit and that `kal_process_terminate' reach
it afterwards. Two implementations satisfied that free --- `getpgid(pid) == pid'
recovers the association from the kernel --- and the third could not: it can form
a unit but cannot recover one from a process handle. Clause 7.1 says an
implementation that must keep a registry to satisfy this specification indicates
the SPECIFICATION took a shape borrowed from one environment.

⚠️ AND THE OBVIOUS REPAIR WOULD ONLY HAVE MOVED IT. An operation that opens an
empty unit is natural where units are filled after creation and impossible where
a unit is created BY its first member. So the identity is established at the
first start and reported back: `kal_spawn.job' is null, or points at zero (form
one, receive its identity) or at a name (join it). Both kinds of system do that
remembering nothing.

`kal_process_job_enter' puts THE CALLER into a unit, which the spawn field
cannot say and which a C library needs: a copy made by `fork' wants to lead a
unit BEFORE it replaces itself, and that is what every shell and every runner
with a timeout is written as.

⭐ Two things improve that were not the goal: `kal_process_terminate' means one
program again, always --- under the flag its meaning turned on invisible state ---
and several programs can share one unit, which a flag cannot express.

Recorded at the declarations rather than left to be found: releasing a unit must
not end it (this system's job objects can be asked to, and must not be, or one
operation would mean two things), and a unit named by a process identifier is
weaker than one named by a handle, because identifiers are reused.
… signals

⭐⭐ THE DECOMPOSITION IS THE DESIGN. "Signals" is not one capability, it is a
Unix mechanism carrying eight different uses, and this interface already
expressed seven of them: terminating a program, terminating a unit, a stream
whose far end has gone, a started program that ended, a bound upon waiting,
waking another context. The eighth had no spelling at all --- THIS PROGRAM HAS
BEEN ASKED TO END --- and it is the one a shell, a server and a runner with a
timeout are all written around.

⚠️ AND SIGNALS THEMSELVES CANNOT BE THE INTERFACE. They are one environment's
shape: a numbered set, a disposition, a handler interrupting whatever ran. The
other system has console control routines, which arrive ON A CONTEXT OF THEIR
OWN and cover different causes; two targets have no processes to signal at all.
Clause 7.1 names that shape exactly.

⇒ `kal_process_stop_requested' answers a word: zero, then non-zero, never
cleared. Read it, or WAIT on it with `kal_task_wait' --- which this specification
already has and `openkal.timeout' already bounds. No new concept, and none of
the re-entrancy rules a handler forces on every caller.

⚠️ Stated rather than left to be discovered: it is a notice and not a veto; it
does not say who asked; and it says NOTHING about an end that cannot be observed
--- the un-declinable signal, the abrupt termination, `kal_process_job_terminate'
--- because nothing anywhere gets to notice those. ⭐ That symmetry is why this is
implementable on both kinds of system rather than on one.

⚠️⚠️ AND THE CONFORMANCE SUITE CAUGHT ITS OWN AUTHOR AGAIN, for the third time:
"the capability word contains no position the specification has not assigned".
A new property bit and an unextended mask is evidently the mistake I make every
release, and it is evidently the check that finds it.

Surface 102 -> 103. Conformance against openkal-linux: 141 held, 0 did not.
The proposal as written had the unit as a FLAG on the spawn. Clause 7.1 decided
against it — an implementation that must keep a registry to satisfy a
specification indicates the specification borrowed a shape — and the obvious
repair, an operation that opens an empty unit, would have moved the same defect
to the other kind of system rather than removing it.

⇒ §2.4 records both the first shape and why it changed, rather than presenting
the final one as though it had been obvious. §2.5 adds the stop-request word and
the decomposition that produced it: "signals" is eight uses in one mechanism,
this interface already expressed seven, and only the eighth needed a spelling.
@Sunrisepeak
Sunrisepeak merged commit 5084c6d into main Aug 30, 2026
12 checks passed
@Sunrisepeak
Sunrisepeak deleted the spawn-is-one-operation branch August 30, 2026 20:22
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.

1 participant