Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .agents/docs/2026-11-06-declared-unit-continuation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Declared units and live continuation identities

This record describes the source group under review. The public dependency
versions have not yet been advanced to this ABI.

`owned_unit::retire` seals admission and takes an explicit `program` from
`self` or `describe`. Both id and birth identify a still-live continuation.
A helper may outlive the publisher that started it, so its previous numeric
unit is not a sufficient return location. The build owner is an appropriate
continuation when it remains alive through retirement. An expired identity,
an inaccessible destination or a different native session yields unknown.
Migration does not authorize termination or admission into the continuation.

`unit_observer` retains the original native observation reference. Its public
surface contains observation and lifetime operations only; native controls
and the native job are private. A compile-failure consumer verifies that it
cannot be supplied as a declared spawn unit or used to terminate a unit.
The C ABI observe/close restriction is a caller precondition on systems whose
unit is a numeric group, rather than a kernel-enforced read-only handle.

The source tests use the existing directory-local blocking IPC transport.
The native consumers measure paired startup and listener start/stop samples,
retain outliers, and test sealed admission after both unknown and successful
retirement. Three native source builds are required; cross-compilation alone
does not qualify native lifetime behavior or cost.

## 2026-11-06 dependency-group preparation

The final source group names OpenKal 0.24, Linux 0.25, macOS 0.22, Windows
0.20 and Emscripten 0.13. OS 0.3.0 uses platform 0.24 for its hosted and
image consumers. Its public manifest is advanced only after the lower source
archives and index entries are available. Native source controls establish
API behaviour; the final complete package CI and ordinary public resolution
remain separate qualification gates.
22 changes: 10 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ name: CI
# run under node (checks/emscripten); a program that starts one
# fails at LINK naming the openkal symbol (checks/absent)
#
# The library is built with the released mcpp, 2026.11.2.1. The ok images
# (MCPP_PLATFORM) and checks/hosted (its manifest) select openkal-platform
# 0.23.0 and take their C library and C++ run-time from it (openkal 0.23's
# implementations among them; openkal-musl 0.27.0 embeds its allocator, which
# needs mcpp 2026.11 or later). The library
# The library is built with the released mcpp, 2026.11.5.1. The ok images
# (MCPP_PLATFORM) and checks/hosted select openkal-platform 0.24 and its
# OpenKal 0.24 implementation, musl 0.28 and C++ runtime 0.19 group. The library
# itself needs 2026.10.27.1 or later, the first that selects a platform
# (SPEC-013). A dispatch may name a branch of mcpp instead (`mcpp_ref'), which
# the latest released mcpp builds and which is then used.
Expand All @@ -37,7 +35,7 @@ env:
MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF || '' }}
# 2026.11.3.1: the std module compiled with the platform's words (openkal-platform 0.23.0's
# `-fcf-protection=full` refused every `import std` of an earlier mcpp's).
MCPP_INSTALL: ${{ (github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF) && 'mcpp' || 'mcpp@2026.11.3.1' }}
MCPP_INSTALL: ${{ (github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF) && 'mcpp' || 'mcpp@2026.11.5.1' }}
XLINGS_VERSION: v2026.10.26.1
XLINGS_NON_INTERACTIVE: '1'

Expand Down Expand Up @@ -113,7 +111,7 @@ jobs:
run: |
profile='${{ matrix.profile }}'
mcpp test --profile "$profile" 2>&1 | tee tests.log
for name in platform_test fs_test process_test ipc_test dirs_terminal_test linux_test; do
for name in platform_test fs_test process_test ipc_test unit_test dirs_terminal_test linux_test; do
grep -q "^$name \.\.\. ok" tests.log || { echo "::error::$name did not run or did not pass"; exit 1; }
done

Expand Down Expand Up @@ -211,7 +209,7 @@ jobs:
run: |
profile='${{ matrix.profile }}'
mcpp test --profile "$profile" ${{ matrix.target }} 2>&1 | tee tests.log
for name in platform_test fs_test process_test ipc_test dirs_terminal_test linux_test library_test; do
for name in platform_test fs_test process_test ipc_test unit_test dirs_terminal_test linux_test library_test; do
grep -q "^$name \.\.\. ok" tests.log || { echo "::error::$name did not run or did not pass"; exit 1; }
done

Expand All @@ -229,7 +227,7 @@ jobs:
# openkal 0.23.0, which this release is written against, is the one
# openkal-platform 0.23.3's version group names; mcpp 2026.11.3.1 and
# later pin it for their openkal rows.
MCPP_PLATFORM: openkal-platform@0.23.3
MCPP_PLATFORM: openkal-platform@0.24.0
defaults:
run:
shell: bash
Expand Down Expand Up @@ -260,7 +258,7 @@ jobs:
run: |
set -euo pipefail
mcpp test --target x86_64-openkal 2>&1 | tee tests.log
for name in platform_test fs_test process_test ipc_test dirs_terminal_test linux_test; do
for name in platform_test fs_test process_test ipc_test unit_test dirs_terminal_test linux_test; do
grep -q "^$name \.\.\. ok" tests.log || { echo "::error::$name did not pass as an image"; exit 1; }
done
mcpp test --target aarch64-openkal --no-run
Expand All @@ -281,7 +279,7 @@ jobs:
# openkal 0.23.0, which this release is written against, is the one
# openkal-platform 0.23.3's version group names; mcpp 2026.11.3.1 and
# later pin it for their openkal rows.
MCPP_PLATFORM: openkal-platform@0.23.3
MCPP_PLATFORM: openkal-platform@0.24.0
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -399,7 +397,7 @@ jobs:
run: |
for t in tests/*_test.cpp; do
n=$(basename "$t")
case "$n" in process_test.cpp|ipc_test.cpp) continue ;; esac
case "$n" in process_test.cpp|ipc_test.cpp|unit_test.cpp) continue ;; esac
[ -f "checks/emscripten/tests/$n" ] || { echo "::error::checks/emscripten/tests has no $n"; exit 1; }
done
- name: Tests, under node
Expand Down
103 changes: 103 additions & 0 deletions .github/workflows/unit-lifecycle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: declared unit lifecycle
on:
pull_request:
workflow_dispatch:
concurrency:
group: unit-lifecycle-${{ github.ref }}
cancel-in-progress: true
env:
XLINGS_NON_INTERACTIVE: '1'

jobs:
native:
name: declared unit and blocking ACK (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-24.04, backend: openkal-linux, toolchain: gcc@16.1.0, revision: 225c545f8353570c277ebe79d0bb47b882826bca }
- { os: macos-15, backend: openkal-macos, toolchain: llvm@23.1.3, revision: 3b9686e9260f4a14169602c865751eea2af5f9e0 }
- { os: windows-2025, backend: openkal-windows, toolchain: msvc@system, revision: 9e8f5ab1c1202bf6a46929e82bec77e2667db730 }
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
# Immutable published OpenKal0.24/backend release commits; no prototype refs.
- uses: actions/checkout@v4
with:
repository: mcpplibs/openkal
ref: c17bc255b9672325a726f0afa69f15f20c766b73
path: deps/openkal
- uses: actions/checkout@v4
with:
repository: mcpplibs/${{ matrix.backend }}
ref: ${{ matrix.revision }}
path: deps/backend
- uses: ilammy/msvc-dev-cmd@v1
if: runner.os == 'Windows'
- name: Install the published build tool (Unix)
if: runner.os != 'Windows'
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.10.26.1
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
- name: Install the published build tool (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
irm https://d2learn.org/xlings-install.ps1.txt | iex
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Install the actual native compiler
run: |
xlings update
xlings install mcpp@2026.11.5.1 -y -g
spec='${{ matrix.toolchain }}'
case "$spec" in
msvc@system) mcpp toolchain default msvc ;;
*) mcpp toolchain install "${spec%@*}" "${spec#*@}"
mcpp toolchain default "$spec" ;;
esac
- name: Select the coherent source group under review
env:
IMPLEMENTATION: ${{ matrix.backend }}
run: |
python - <<'PY'
from pathlib import Path
import os,re
root=Path('mcpp.toml')
text=root.read_text()
text=re.sub(r'^openkal = .*$', 'openkal = { path = "deps/openkal" }', text, flags=re.M)
text=re.sub(r'^'+re.escape(os.environ['IMPLEMENTATION'])+r' = .*$', os.environ['IMPLEMENTATION']+' = { path = "deps/backend" }', text, flags=re.M)
root.write_text(text)
backend=Path('deps/backend/mcpp.toml')
backend.write_text(re.sub(r'^openkal = .*$', 'openkal = { path = "../openkal" }', backend.read_text(), flags=re.M))
PY
- name: Compile and run the real lifecycle/ACK consumer
run: |
set -o pipefail
mcpp test unit_test --verbose 2>&1 | tee unit-lifecycle.log
binary=$(find target -type f \( -name unit_test -o -name unit_test.exe \) | head -1)
test -n "$binary"
"$binary" | tee unit-cost.jsonl
- name: An observer cannot admit or terminate a program
run: |
set +e
(cd checks/observer-control && mcpp build) > observer-control.log 2>&1
result=$?
set -e
test "$result" -ne 0
python - <<'PYCONTROL'
from pathlib import Path
log=Path('observer-control.log').read_text()
assert 'unit_observer' in log and ('declared_unit' in log or 'owned_unit' in log) and 'terminate' in log, log
PYCONTROL
- uses: actions/upload-artifact@v4
if: always()
with:
name: unit-lifecycle-${{ matrix.os }}
path: |
unit-lifecycle.log
unit-cost.jsonl
observer-control.log
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ with compile-time platform constants and a few platform modules.

```toml
[dependencies]
os = { namespace = "mcpplibs", version = "0.2.9" }
os = { namespace = "mcpplibs", version = "0.3.0" }
```

```cpp
Expand Down Expand Up @@ -34,6 +34,29 @@ what is not a kernel question: the machine beneath the system (`machine`), the d
loader (`library`: the C library's on Linux and macOS, kernel32's on Windows), and the
platform modules.

## Declared process units

Version 0.3.0 provides `process::owned_unit` and the observation-only
`process::unit_observer`. Create an owned unit before starting its external
members, retain `reference()` in the supervising invocation, and pass the unit
through `spawn_options::declared_unit`. The existing `process::spawn` performs
the start. The creating program is a member; `terminate()` can end it too.
`own_unit` and `declared_unit` are mutually exclusive.

An observer opens that opaque reference and reports `unit_state::occupied`,
`empty` or `unknown`. Its type exposes neither admission nor termination.
Closing it terminates nothing. Missing native identity, including loss of all
native reference holders on a system that requires them, remains `unknown`.

`owned_unit::retire(continuation)` seals admission before observing the end of
external members. The continuation identifies the surviving supervising
program outside the helper's unit, obtained from `self` or `describe` with
both identifier and birth retained. A dead, reused or
unreachable continuation does not authorize membership migration. The caller
can finish its bookkeeping after successful retirement. Retirement does not
grant control of the continuation's unit. Output recovery requires a positive
`empty` observation; leader termination alone is insufficient.

## The program's C library

A program over this package chooses its C library. It can be the system's own (glibc,
Expand Down Expand Up @@ -87,7 +110,7 @@ dropped (0.2.8).
| `mcpplibs.os.error` | `errc` (openkal's closed set of conditions), `error`, `result<T>` |
| `mcpplibs.os.env` | variables (names without case on Windows), `NAME=value` entries as overrides (`assignments`), arguments, the program's own image, lists of directories in the host's separator (`split_list`, `join_list`), PATH search with PATHEXT |
| `mcpplibs.os.fs` | `write_atomic`, `publish` (no replace), `append`, `sync`, `caps`, `lock`, links, `set_executable`, `stamp_of`, `identity_of`, `owner_of`, `writable`, `locate`, `absolute`, `names_a_place`, `canonical`, a bounded `read`; what a volume does, measured (`measure`, `volume_type`), and the operations that follow it (`place`, `exchange`); names in the host's rules without the file system: `generic`, `native`, `extended`, `is_absolute`, `same`, `within` |
| `mcpplibs.os.process` | `spawn`, `run` (and with bounds: a hard bound, a grace, a drain, output limits), `exec`, a command line joined as it is for the command interpreter (`one_string`), pipes, both outputs on one pipe, output onto this program's error output, units and `end_unit`, bound lifetime, bounded waits and reads; `self`, `describe`, `alive`, `threads` |
| `mcpplibs.os.process` | `spawn`, `run` (and with bounds: a hard bound, a grace, a drain, output limits), `exec`, a command line joined as it is for the command interpreter (`one_string`), pipes, both outputs on one pipe, output onto this program's error output, units and `end_unit`, declared units, observation and retirement, bound lifetime, bounded waits and reads; `self`, `describe`, `alive`, `threads` |
| `mcpplibs.os.interrupt` | `requested`, `wait`, `end_as_requested`, `end_as_output_closed` |
| `mcpplibs.os.stdio` | the three streams as bytes; a bounded read of input; the output sent to the error stream for a while (`output_to_error`) |
| `mcpplibs.os.terminal` | `interactive`, `size`, `sequences`, `color`, `raw_mode` (with or without the interrupting keystroke), `foreground` |
Expand Down
2 changes: 1 addition & 1 deletion checks/hosted/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ version = "0.1.0"
os = { path = "../.." }

[target.'cfg(any(linux, macos, windows))']
platform = "openkal-platform@0.23.3"
platform = "openkal-platform@0.24.0"

# The tests are the package's own, each reached by an include from
# tests/<name>.cpp here (a test program is a file of the package that runs it).
Expand Down
7 changes: 7 additions & 0 deletions checks/hosted/tests/unit_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Restate imports for the dependency scanner, as the other hosted wrappers do.
#include "../../../tests/check.hpp"
import std;
import mcpplibs.os.process;
import mcpplibs.os.ipc;
import mcpplibs.os.env;
#include "../../../tests/unit_test.cpp"
7 changes: 7 additions & 0 deletions checks/observer-control/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# A negative compilation check: an observer supplies no unit control authority.
[package]
name = "observer-control-misuse"
version = "0.1.0"

[dependencies]
os = { path = "../.." }
9 changes: 9 additions & 0 deletions checks/observer-control/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import std;
import mcpplibs.os.process;

int main() {
auto observer = mcpplibs::os::process::unit_observer::open("opaque reference");
mcpplibs::os::process::spawn_options options;
options.declared_unit = &*observer; // An observer cannot admit a program.
observer->terminate(); // It has no unit control operation.
}
12 changes: 6 additions & 6 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "os"
version = "0.2.9"
version = "0.3.0"
description = "mcpplibs.os: a C++ operating-system library over openkal --- processes, files, environment, terminals, directories and local IPC, with compile-time platform constants and a few platform modules."
license = "Apache-2.0"
authors = ["mcpplibs"]
Expand All @@ -12,23 +12,23 @@ repo = "https://github.com/mcpplibs/os"
# own C and C++ runtime. A program that wants the standalone arrangement, or
# the image arrangement of `<arch>-openkal`, selects it in its own manifest.
[dependencies]
openkal = "0.23.0"
openkal = "0.24.0"

[target.linux.dependencies]
openkal-linux = "0.24.1"
openkal-linux = "0.25.0"

[target.macos.dependencies]
openkal-macos = "0.21.3"
openkal-macos = "0.22.0"

[target.windows.dependencies]
openkal-windows = "0.19.0"
openkal-windows = "0.20.0"

# Emscripten (`wasm32-emscripten`): openkal written above the C library, with
# twelve of openkal's fifteen interfaces. `openkal.process', `openkal.exec' and
# `openkal.space' are absent, so a program that starts one fails at link
# naming the symbol; `openkal.task' is present in a `-pthread' link only.
[target.'cfg(os = "emscripten")'.dependencies]
openkal-emscripten = "0.12.0"
openkal-emscripten = "0.13.0"

[targets.os]
kind = "lib"
Expand Down
Loading
Loading