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
5 changes: 3 additions & 2 deletions .github/ci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ Build the documentation with the same image:
'
```

The generated HTML landing page, changelog, work queue and version 1 protocol
and lifecycle contracts are written below `doc/html/`.
The generated HTML landing page, changelog, work queue, historical protocol 1,
current protocol 2 and unchanged lifecycle contract are written below
`doc/html/`.

Set `SPAWN_CI_IMAGE` to override the local image name,
`DOCKER_PLATFORM` to override the default `linux/amd64` platform, and
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
test -s doc/html/CHANGELOG.html
test -s doc/html/WORK_QUEUE.html
test -s doc/html/protocol-v1.html
test -s doc/html/protocol-v2.html
test -s doc/html/lifecycle-v1.html
test -s doc/html/styles.css
grep -Fq '<title>Ada Spawn Manager</title>' doc/html/index.html
Expand All @@ -71,6 +72,10 @@ jobs:
doc/html/WORK_QUEUE.html
grep -Fq '<h1 id="spawn-manager-protocol-version-1">' \
doc/html/protocol-v1.html
grep -Fq '<h1 id="spawn-manager-protocol-version-2">' \
doc/html/protocol-v2.html
grep -Fq "href=\"$source_base_url/src/spawn-protocol.ads\"" \
doc/html/protocol-v2.html
grep -Fq '<h1 id="spawn-manager-lifecycle-version-1">' \
doc/html/lifecycle-v1.html
unexpected_links=$(
Expand All @@ -84,6 +89,7 @@ jobs:
-e '^CHANGELOG\.html($|#)' \
-e '^WORK_QUEUE\.html($|#)' \
-e '^protocol-v1\.html($|#)' \
-e '^protocol-v2\.html($|#)' \
-e '^lifecycle-v1\.html($|#)' \
-e '^styles\.css$' ||
true
Expand Down
11 changes: 7 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ caller and the dedicated process manager.

- Use `README.md` for the component overview, supported platform and normal
build, test and installation commands.
- Read `doc/protocol-v1.md` when changing request or result fields, validation,
transport bounds or stream handling.
- Read `doc/protocol-v2.md` when changing request or result fields, validation,
transport bounds or stream handling. It defines the current fixed version.
- Use `doc/protocol-v1.md` only for comparison with the historical 0.2.0 wire
contract, not as a current compatibility mode.
- Read `doc/lifecycle-v1.md` when changing processes, descriptors, signals,
timeouts, cancellation, cgroup callbacks or pool cleanup.
- Update `CHANGELOG.md` for user- or integrator-visible changes. Keep planned
Expand Down Expand Up @@ -112,8 +114,9 @@ caller and the dedicated process manager.
- The command-string API retains `/bin/bash -o pipefail -c` behavior and the
manager environment. Structured requests retain exact executable, argument,
replacement-environment, working-directory, stream and result semantics.
- Protocol version 1 is fixed and fail-closed. Wire changes require an explicit
new version, updated format documentation and independent golden-byte tests.
- Each protocol version is fixed and fail-closed. Wire changes require an
explicit new version, updated format documentation and independent
golden-byte tests.
- The post-`fork` child enters no Ada runtime code before `execve` or `_exit`.
Keep the C boundary small, readable and limited to async-signal-safe child
operations; explain non-obvious ownership and cleanup steps in comments.
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Gitlink even when the two Spawn Manager commits are not direct ancestors.

### Added

- Added fixed protocol 2 and an explicit stderr-to-stdout stream policy. Stdout
is opened once and its descriptor duplicated for stderr. Independent streams,
shell behavior, privilege restrictions and supervision remain unchanged;
old protocol versions are rejected rather than negotiated.
- Added public contribution guidance and a private vulnerability-reporting
security policy for the canonical upstream.
- Added lightweight issue and pull-request templates for public contributions.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The shell API retains `/bin/bash -o pipefail -c` behavior. The structured API
retains exact executable, argument, environment, directory, stream and result
semantics.

Protocol version 1 is fixed and fail-closed. An incompatible wire change
Each protocol version is fixed and fail-closed. An incompatible wire change
requires a new protocol version, updated format documentation and independent
golden-byte tests.

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ tests: $(POSIX_TEST) $(PROTOCOL_FAILURE_MANAGER) spawn_tests spawn_manager
@$(OBJDIR)/spawn_manager 47 $(OBJDIR)/spawn_manager_min_spawn &
@$(OBJDIR)/test_runner
@tests/check_signal_shutdown.sh "$(CURDIR)"
@python3 tests/check_signal_reaping.py "$(abspath $(OBJDIR))/spawn_manager" 1
@python3 tests/check_signal_reaping.py "$(abspath $(OBJDIR))/spawn_manager" 2
@tests/check_adaflags.sh "$(CURDIR)"
@tests/check_install.sh "$(CURDIR)"

Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,23 @@ The project provides two request styles:
- a compatible command-string API that executes
`/bin/bash -o pipefail -c COMMAND`; and
- a structured API with an explicit executable, ordered argument vector,
complete replacement environment, working directory, independent standard
output and error destinations, and timeout.
complete replacement environment, working directory, independent or
explicitly shared output destinations, and timeout.

Both styles use the same bounded, versioned protocol and the same supervised
execution core. There is no intermediate `spawn_wrapper` process.

## Status and platform

This revision defines release **0.2.0**. It introduces structured execution
while retaining the existing shell API. Its contents are collected in the
The latest stable release is **0.2.0**. This development revision adds protocol
2 with shared output and requires its matching library and manager. It retains
the existing shell API. Changes are collected in the
[changelog](CHANGELOG.md); accepted later work is tracked in the
[work queue](WORK_QUEUE.md).

The current execution core is Linux-specific. It relies on Unix-domain
sockets, process groups, `prctl(2)`, `/proc` and POSIX process and signal
operations. Version 1 sets `no_new_privs` for every child, so shell and
operations. Both versions set `no_new_privs` for every child, so shell and
structured requests cannot gain privileges through set-user-ID,
set-group-ID or file-capability metadata during `execve`.

Expand All @@ -59,7 +60,7 @@ instead of making that manager available to another caller.

For the exact contracts, see:

- [Protocol version 1](doc/protocol-v1.md) for the shell and structured wire
- [Protocol version 2](doc/protocol-v2.md) for the shell and structured wire
formats, bounds and result encoding.
- [Lifecycle version 1](doc/lifecycle-v1.md) for process groups, signals,
timeouts, reaping, `no_new_privs`, cgroup callbacks and failure handling.
Expand All @@ -73,7 +74,7 @@ connections, switches descriptors to nonblocking mode and closes them. Spawn
Manager also uses Anet's operating-system constants and filesystem utilities.

Frame serialization and exact frame transfer deliberately belong to Spawn
Manager. Version 1 does not use `Anet.Streams`, Ada record streaming or the
Manager. Neither version uses `Anet.Streams`, Ada record streaming or the
generic `Anet.Sockets.Send` and `Receive` operations for protocol data. Those
socket operations each represent one system call, whereas this protocol must
resume partial reads and writes, distinguish first-byte and completion
Expand All @@ -84,7 +85,7 @@ rules with `poll(2)`, `recv(2)` and `send(2)`.

This boundary is intentional rather than a replacement for Anet. A future
Anet primitive offering the same exact, deadline-bounded nonblocking transfer
semantics could replace the generic I/O part without changing the version 1
semantics could replace the generic I/O part without changing the current
wire format. Frame layout, validation and protocol failure classification
would remain Spawn Manager responsibilities.

Expand Down
11 changes: 6 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ through, so Spawn Manager must preserve their documented boundaries exactly.
- Pool sockets remain private, correctly owned and safely cleaned up.
- Structured requests preserve exact arguments and replacement environments
without shell parsing.
- Version 1 applies `no_new_privs` to every request child.
- Protocol versions 1 and 2 apply `no_new_privs` to every request child.
- Unexpected descriptors are not inherited by request children.
- Signal state is normalized without losing manager or child observability.
- Each request has a supervised process group with bounded termination and
Expand All @@ -74,7 +74,8 @@ Examples include:
- unintended descriptor or sensitive environment inheritance;
- failure to terminate or reap processes which remain within the documented
process-group or caller-supplied cgroup containment boundary;
- privilege gain despite the version 1 `no_new_privs` contract;
- privilege gain despite the `no_new_privs` contract in protocol versions
1 and 2;
- unsafe post-fork behavior in the multithreaded caller boundary;
- reuse of a manager whose supervision or lifecycle state is uncertain;
- argument, environment, stream or working-directory confusion that crosses a
Expand Down Expand Up @@ -106,9 +107,9 @@ realistic security impact should be reported through a public issue.
The local Unix socket is not an authorization boundary against other code
already running with the same operating-system identity.

Version 1 has no privilege-gaining execution mode and no individual-request
cancellation operation. Cgroup placement is supplied by trusted caller
callbacks and is not part of the wire protocol.
Protocol versions 1 and 2 have no privilege-gaining execution mode and no
individual-request cancellation operation. Cgroup placement is supplied by
trusted caller callbacks and is not part of the wire protocol.

Anet owns socket lifecycle and addressing. Spawn Manager owns frame encoding,
exact transfer, validation, deadlines and protocol-failure classification.
42 changes: 21 additions & 21 deletions WORK_QUEUE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This is the maintained list of accepted follow-up work after the initial
structured-execution delivery. It records concrete consumers and known
boundaries without silently extending protocol version 1. Assign a ticket and
boundaries without silently extending released protocols. Assign a ticket and
define acceptance evidence before starting an item.

## Post-0.2.0 validation
Expand All @@ -17,16 +17,15 @@ define acceptance evidence before starting an item.

## Accepted follow-ups

### Neutral GitHub organization
### GitHub organization maintenance

The canonical repository initially remains under its current personal GitHub
namespace so the Codelabs redirect and upstream transition do not depend on a
new governance decision. Reconsider a transfer to a neutral project
organization after its permanent name, repository scope and at least two
trusted organization owners are confirmed.
The canonical repository now lives under `abuild-project`. Keep repository and
Pages URLs, review applications and access permissions aligned with that
organization rather than treating the completed transfer as pending work.

Before a transfer, inventory repository and Pages URLs, Actions permissions,
installed review applications, Dependabot, secrets and branch and tag rules.
Before any later transfer, inventory repository and Pages URLs,
Actions permissions, installed review applications, Dependabot,
secrets, and branch and tag rules.
Afterward, republish Pages, update the Codelabs redirect and verify web, clone,
fetch, CI and release access through both the old and new repository URLs.

Expand All @@ -37,8 +36,9 @@ both at one pathname would open and truncate it twice and would not preserve
the historical ordered combined build log, so component `make` remains an
intentional shell caller.

Define and test one shared open-file-description stream policy, either as an
explicitly compatible V1 completion before release or as protocol V2. Preserve
V2 development implements `Merge_Stdout` for stderr. The released version-1
wire contract remains unchanged.
Deliver the dependency before the Make caller step. Preserve
ordered stdout/stderr, cgroup placement, complete argv logging and Ctrl-C
cleanup, then migrate `make -s -jN -C PATH` to structured execution.

Expand All @@ -52,25 +52,25 @@ than pretending it is argv data.

### Explicit privilege-gaining execution

Version 1 applies `no_new_privs` to every shell and structured request and has
no exception. Add an opt-in only for a concrete transformation which requires
privilege metadata during `execve`. Specify it in a new protocol contract and
require cgroup containment because privileged exec may clear the direct
leader's parent-death signal.
Protocols 1 and 2 apply `no_new_privs` to every shell and structured request
and have no exception. Add an opt-in only for a concrete transformation which
requires privilege metadata during `execve`. Specify it in a new protocol
contract and require cgroup containment because privileged exec may clear the
direct leader's parent-death signal.

### Individual request cancellation

Version 1 cancels the complete pool through `Cleanup`; it has no request
identifier or single-request cancel operation. Add this only for a concrete
concurrent consumer. Define identity, races, result classification and manager
reuse before choosing a wire format.
Protocols 1 and 2 cancel the complete pool through `Cleanup`; they have no
request identifier or single-request cancel operation. Add this only for a
concrete concurrent consumer. Define identity, races, result classification
and manager reuse before choosing a wire format.

### Descriptor-ceiling fallbacks

A failed `/proc/self/fd` scan with a finite soft descriptor limit selects and
caches that limit. If the limit is `RLIM_INFINITY`, the current implementation
consults `sysconf(_SC_OPEN_MAX)`; its result is not yet an accepted portable
version-1 contract. Add fault injection for an unlimited limit with both
protocol-1/2 contract. Add fault injection for an unlimited limit with both
procfs and `close_range` unavailable. The request must fail at descriptor
closure instead of attempting an effectively unbounded sweep.

Expand Down
11 changes: 9 additions & 2 deletions doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ HTML_DOCUMENTS = \
$(DESTDIR)/CHANGELOG.html \
$(DESTDIR)/WORK_QUEUE.html \
$(DESTDIR)/protocol-v1.html \
$(DESTDIR)/protocol-v2.html \
$(DESTDIR)/lifecycle-v1.html \
$(DESTDIR)/styles.css

Expand All @@ -30,6 +31,7 @@ $(DESTDIR)/index.html: ../README.md Makefile | $(DESTDIR)
-e 's|href="CHANGELOG.md"|href="CHANGELOG.html"|g' \
-e 's|href="WORK_QUEUE.md"|href="WORK_QUEUE.html"|g' \
-e 's|href="doc/protocol-v1.md"|href="protocol-v1.html"|g' \
-e 's|href="doc/protocol-v2.md"|href="protocol-v2.html"|g' \
-e 's|href="doc/lifecycle-v1.md"|href="lifecycle-v1.html"|g' \
-e 's|href="src/spawn-pool.ads"|href="$(SOURCE_BASE_URL)/src/spawn-pool.ads"|g' \
-e 's|href="AUTHORS"|href="$(SOURCE_BASE_URL)/AUTHORS"|g' \
Expand All @@ -45,12 +47,16 @@ $(DESTDIR)/WORK_QUEUE.html: ../WORK_QUEUE.md Makefile | $(DESTDIR)
$(PANDOC) $(PANDOC_OPTS) --metadata pagetitle="Spawn Manager work queue" \
-o $@ $<

$(DESTDIR)/protocol-v1.html: protocol-v1.md Makefile | $(DESTDIR)
$(PANDOC) $(PANDOC_OPTS) --metadata pagetitle="Protocol version 1" \
$(DESTDIR)/protocol-%.html: protocol-%.md Makefile | $(DESTDIR)
$(PANDOC) $(PANDOC_OPTS) \
--metadata pagetitle="Protocol version $(patsubst v%,%,$*)" \
-o $@.tmp $<
sed \
-e 's|href="../src/spawn-protocol.ads"|href="$(SOURCE_BASE_URL)/src/spawn-protocol.ads"|g' \
-e 's|href="../README.md#anet-boundary"|href="index.html#anet-boundary"|g' \
-e 's|href="protocol-v1.md|href="protocol-v1.html|g' \
-e 's|href="protocol-v2.md|href="protocol-v2.html|g' \
-e 's|href="lifecycle-v1.md|href="lifecycle-v1.html|g' \
$@.tmp > $@
rm -f $@.tmp

Expand All @@ -59,6 +65,7 @@ $(DESTDIR)/lifecycle-v1.html: lifecycle-v1.md Makefile | $(DESTDIR)
-o $@.tmp $<
sed \
-e 's|href="protocol-v1.md|href="protocol-v1.html|g' \
-e 's|href="protocol-v2.md|href="protocol-v2.html|g' \
-e 's|href="../src/spawn-pool.ads"|href="$(SOURCE_BASE_URL)/src/spawn-pool.ads"|g' \
$@.tmp > $@
rm -f $@.tmp
Expand Down
26 changes: 14 additions & 12 deletions doc/lifecycle-v1.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Spawn Manager lifecycle version 1

This document defines the process ownership, cancellation and containment
contract of Spawn Manager version 1. The public API is declared in
[`Spawn.Pool`](../src/spawn-pool.ads); the request and result encoding is
defined by [protocol version 1](protocol-v1.md).
This document defines lifecycle contract version 1: process ownership,
cancellation and containment. The unchanged lifecycle contract applies to wire
protocol versions 1 and 2; its version is independent of the wire version.
The public API is declared in [`Spawn.Pool`](../src/spawn-pool.ads). Current
request and result encoding is defined by [protocol version 2](protocol-v2.md);
[protocol version 1](protocol-v1.md) describes the historical 0.2.0 wire format.

## Pool and leases

Expand Down Expand Up @@ -52,9 +54,9 @@ Leader and descendant reaping after termination have fixed grace-period
deadlines. An uninterruptible child therefore fail-stops its manager instead
of blocking pool cancellation indefinitely.

Version 1 has no independent single-request cancellation operation.
`Spawn.Pool.Cleanup` stops new leases, interrupts all managers, waits for
active callers to leave and cancels the complete pool.
This lifecycle contract has no independent cancellation of a single request.
`Spawn.Pool.Cleanup` stops new leases, interrupts all managers, waits for active
callers to leave and cancels the complete pool.

## Privilege and descendant containment

Expand All @@ -63,7 +65,7 @@ Set-user-ID, set-group-ID and file-capability metadata cannot grant the child
new privileges or clear that signal during `execve`. The restriction is
inherited by shell children and structured-exec descendants.

This is a deliberate version-1 compatibility boundary with no opt-out. A
This is a deliberate lifecycle compatibility boundary with no opt-out. A
future exception requires a concrete consumer, an explicit request contract
and cgroup containment because a privileged exec can clear the direct
leader's parent-death signal.
Expand All @@ -77,7 +79,7 @@ group signal requires separately selected cgroup containment.

Cgroup containment is also required for descendants left by uncatchable
manager loss such as `SIGKILL` or OOM, and for a descendant that deliberately
escapes with `setpgid()` or `setsid()`. Version 1 does not claim those cases
escapes with `setpgid()` or `setsid()`. This contract does not claim those cases
from its process-group fallback alone.

## Signal and fork boundary
Expand All @@ -102,7 +104,7 @@ group supervision.
The manager binds its descriptor ceiling to `/proc/self/fd` only after a
complete scan. An open, read or close error with a finite soft descriptor
limit selects that limit before `fork`. With `RLIM_INFINITY`, the
implementation consults `sysconf(_SC_OPEN_MAX)` instead; version 1 does not
implementation consults `sysconf(_SC_OPEN_MAX)` instead; this contract does not
claim portable support for an effectively unbounded result. If no usable
finite ceiling exists and `close_range` is unavailable, the child reports the
pre-exec `Close_Descriptors` stage instead of proceeding to `execve`. The Work
Expand All @@ -126,11 +128,11 @@ socket-path cleanup after the manager has terminated.

## Failure classification

Version 1 reports internal and child-spawn failures through one result kind.
Protocols 1 and 2 use one result kind for internal and child-spawn failures.
Stages that can represent manager-side supervision poison the complete pool,
including ambiguous error-pipe and process-group failures. This also includes
manager-side signal-mask restoration failures reported as `Reset_Signals`.

Stages that can only represent child-side pre-exec failures remain reusable.
The exact stage encoding is part of the
[protocol result format](protocol-v1.md#result).
[current protocol result format](protocol-v2.md#result).
Loading