Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
3fa116b
mason: implement Windows host fs module and raw-spelling validation
ualtinok Oct 10, 2026
76b9008
mason: add comprehensive Windows fs refusal tests and deterministic t…
ualtinok Oct 10, 2026
3d184fc
mason: suppress unused import warning in fs test module on non-unix
ualtinok Oct 10, 2026
c63f497
Merge: the Windows fs built-in, with Windows refusal tests
ualtinok Oct 10, 2026
0611455
Refuse the superscript COM and LPT device names on Windows
ualtinok Oct 10, 2026
5621897
mason: implement Windows host git module and job containment
ualtinok Oct 10, 2026
7d24263
mason: fix the Windows fs built-in from its security review
ualtinok Oct 10, 2026
51662ad
mason: add basal-launch, the Windows confined worker launcher
ualtinok Oct 10, 2026
1f8d083
mason: clarify basal-launch comments for a reader without context
ualtinok Oct 10, 2026
8c40332
mason: treat a job-killed worker as killed, and give the worker a lon…
ualtinok Oct 10, 2026
02b46b1
Merge commit '7d24263' into alfonso/task/bg_d6985ae075a52784-basal-wi…
ualtinok Oct 10, 2026
9ea2994
Merge: Windows fs built-in fixes from its security review (44 Windows…
ualtinok Oct 10, 2026
651c119
Draw host entropy from BCryptGenRandom on Windows
ualtinok Oct 10, 2026
9cafad6
Run basal-host's tests on their own in the Windows baseline job
ualtinok Oct 10, 2026
0897adc
Re-anchor the random-source control on the shared entropy call
ualtinok Oct 10, 2026
4ada060
Merge: basal-launch, the Windows confined worker launcher (all 18 lau…
ualtinok Oct 10, 2026
0eb28a0
mason: confine Windows git reads and pin their approved directories
ualtinok Oct 10, 2026
80c4314
mason: accept the host Windows temp directory separator
ualtinok Oct 10, 2026
4bd6090
mason: observe handle inheritance in the exact Windows git child
ualtinok Oct 10, 2026
c61d815
mason: Windows worker entry and startup checks
ualtinok Oct 10, 2026
8cad58c
Merge: the Windows git built-in, redone from its security review (all…
ualtinok Oct 10, 2026
719a928
mason: run the Windows worker tests under a development name
ualtinok Oct 10, 2026
74cfacf
Merge: the Windows worker entry and startup checks (every new Windows…
ualtinok Oct 10, 2026
bafd737
Check text out with LF on every OS
ualtinok Oct 10, 2026
90525fc
mason: basal-launch shared spawn lock, OwnedProcess and spawn_plain f…
ualtinok Oct 10, 2026
6721037
mason: add Windows worker probes and production measurement tooling
ualtinok Oct 10, 2026
998fc1e
mason: port the shared test kit and rig to Windows
ualtinok Oct 10, 2026
ce788d3
mason: basal-module spawns, supervises and kills Windows workers thro…
ualtinok Oct 10, 2026
794a020
mason: preload probe-only Winsock and refine measured provenance
ualtinok Oct 10, 2026
c40cfc8
mason: record production Windows handle provenance and passing probes
ualtinok Oct 10, 2026
3f896a5
mason: fix measured Windows test-kit and rig build failures
ualtinok Oct 10, 2026
a65a248
Merge: Windows probes and production handle provenance (all probes pa…
ualtinok Oct 10, 2026
94973a0
mason: align native Windows path fixtures with verified roots
ualtinok Oct 10, 2026
0d42e48
mason: name each launch's station and TEMP directory randomly
ualtinok Oct 10, 2026
e9f980f
mason: Windows manifest roots, store-closing test fixtures and codemo…
ualtinok Oct 10, 2026
ea0759e
mason: 8 MiB main-thread stack reserve for ck-basal-worker on Windows
ualtinok Oct 10, 2026
feb4ab4
mason: assert the Windows raw-path dispatch refusal code
ualtinok Oct 10, 2026
0fc2af7
Merge: an 8 MiB main-thread stack for the Windows worker (the four st…
ualtinok Oct 10, 2026
ec19512
Merge: the module spawns, supervises and kills Windows workers throug…
ualtinok Oct 10, 2026
98c4a54
mason: exercise native rig process discovery with a real image
ualtinok Oct 10, 2026
2249366
mason: protect the rig image locator raw spelling
ualtinok Oct 10, 2026
251cb46
mason: assert the raw-path refusal kind in its audit row
ualtinok Oct 10, 2026
2d3a87b
mason: use the public git surface in host privacy regressions
ualtinok Oct 10, 2026
4f66e2b
mason: record native Windows test-kit and rig acceptance
ualtinok Oct 10, 2026
b176dc2
Merge: the test kit and rig run on Windows, and the worker's temporar…
ualtinok Oct 10, 2026
e2beaaf
Re-anchor the worker dependency control on basal-rig's Windows lockfi…
ualtinok Oct 10, 2026
0931a51
mason: verify pinned mutation runner on Windows
ualtinok Oct 10, 2026
bae5a5d
mason: require Windows CI and cover confinement guards
ualtinok Oct 11, 2026
115629d
mason: fix Windows mutation targets and isolate fixtures
ualtinok Oct 11, 2026
8317c46
mason: provide Git fixture temp space and remove fsmonitor override
ualtinok Oct 11, 2026
ea5adca
Merge: Windows becomes a required CI platform, with Windows mutation …
ualtinok Oct 11, 2026
8d9cdf7
mason: merge Windows confinement into current main
ualtinok Oct 11, 2026
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
6 changes: 6 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[env]
# Allocation accounting is unused; its global mutex serializes SQLite callers.
LIBSQLITE3_FLAGS = "-DSQLITE_DEFAULT_MEMSTATUS=0"

# Link the C runtime statically on Windows, so no image imports a C runtime
# DLL. The confined worker should load only the system's own DLLs, and the
# dynamic C runtime is a separately installed component.
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Check text out with LF on every OS. Tests read docs, fixtures and digest
# test vectors byte for byte, and a CRLF checkout on Windows would change them.
* text=auto eol=lf
22 changes: 14 additions & 8 deletions .github/workflows/check_mutation_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,29 @@
import tomllib


PLATFORMS = ("linux", "macos")
PLATFORMS = ("linux", "macos", "windows")
PORTABLE_PACKAGES = {"basal-proto", "basal-core", "basal-host"}
SUCCESS = {"CAUGHT", "CAUGHT_BROADLY", "HUB", "EQUIVALENT", "UNREACHABLE"}
ARTIFACT_PATTERN = re.compile(r"mutations-(linux|macos|windows)-[1-9][0-9]*")


def partition(rows):
"""Fail closed when a proof has no CI host, two hosts, or an unsafe host."""
selected = {platform: set() for platform in PLATFORMS}
seen = set()
for row in rows:
hosts = set(row.get("platforms", PLATFORMS)) & set(PLATFORMS)
if len(hosts) != 1:
platforms = row.get("platforms", PLATFORMS)
hosts = set(platforms) & set(PLATFORMS)
if len(platforms) != 1 or len(hosts) != 1:
raise ValueError(f"{row['id']}: expected exactly one CI platform, got {sorted(hosts)}")
host = hosts.pop()
package = row.get("package")
if package in PORTABLE_PACKAGES and host != "linux":
raise ValueError(f"{row['id']}: {package} proofs belong on Linux")
file = Path(row.get("file", ""))
windows_module = "windows" in file.parts or file.name == "windows.rs"
if package in PORTABLE_PACKAGES and host != "linux" and not (
host == "windows" and windows_module
):
raise ValueError(f"{row['id']}: {package} proofs belong on Linux unless targeting a Windows-only module on Windows")
if row["id"] in seen:
raise ValueError(f"duplicate catalogue ID: {row['id']}")
seen.add(row["id"])
Expand Down Expand Up @@ -55,7 +61,7 @@ def verify_reports(selected, reports):
executed[platform][identity] += 1
for platform in PLATFORMS:
# Each host shards the entire catalogue; the runner reports the other
# host's rows as skips. Checking both views also detects a missing shard.
# hosts' rows as skips. Checking every view also detects a missing shard.
if reported[platform] != Counter({identity: 1 for identity in expected}):
raise ValueError(f"{platform}: reports must contain every catalogue ID exactly once")
if executed[platform] != Counter({identity: 1 for identity in selected[platform]}):
Expand All @@ -74,12 +80,12 @@ def main():
if args.reports:
reports = []
for path in sorted(args.reports.glob("mutations-*/all.json")):
match = re.fullmatch(r"mutations-(linux|macos)-[1-9][0-9]*", path.parent.name)
match = ARTIFACT_PATTERN.fullmatch(path.parent.name)
if not match:
raise ValueError(f"unrecognized mutation artifact: {path.parent.name}")
reports.append((match[1], json.loads(path.read_text())))
counts = verify_reports(selected, reports)
print(f"Mutation coverage: linux={counts['linux']}, macos={counts['macos']}, "
print(f"Mutation coverage: linux={counts['linux']}, macos={counts['macos']}, windows={counts['windows']}, "
f"total={sum(counts.values())} catalogue rows; exactly one platform per row")


Expand Down
208 changes: 160 additions & 48 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

46 changes: 36 additions & 10 deletions .github/workflows/test_mutation_coverage.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
"""Tests for check_mutation_coverage.py: every catalogue row is assigned to
exactly one CI host (Linux or macOS), with portable packages kept on Linux,
exactly one CI host, with portable packages on Linux or Windows-only modules on Windows,
and the shards' replay reports contain one successful replay of every row."""

import unittest

from check_mutation_coverage import partition, verify_reports
from check_mutation_coverage import ARTIFACT_PATTERN, PLATFORMS, partition, verify_reports


class MutationCoverageChecks(unittest.TestCase):
def setUp(self):
self.rows = [
{"id": "portable", "package": "basal-host", "platforms": ["linux"]},
{"id": "worker", "package": "basal-worker", "platforms": ["macos"]},
{"id": "native", "package": "basal-host", "platforms": ["windows"],
"file": "crates/basal-host/src/builtins/fs/windows.rs"},
]
self.reports = [
("linux", [{"id": "portable", "outcome": "CAUGHT"},
{"id": "worker", "outcome": "SKIPPED_PLATFORM"}]),
{"id": "worker", "outcome": "SKIPPED_PLATFORM"},
{"id": "native", "outcome": "SKIPPED_PLATFORM"}]),
("macos", [{"id": "portable", "outcome": "SKIPPED_PLATFORM"},
{"id": "worker", "outcome": "CAUGHT"}]),
{"id": "worker", "outcome": "CAUGHT"},
{"id": "native", "outcome": "SKIPPED_PLATFORM"}]),
("windows", [{"id": "portable", "outcome": "SKIPPED_PLATFORM"},
{"id": "worker", "outcome": "SKIPPED_PLATFORM"},
{"id": "native", "outcome": "CAUGHT"}]),
]

def test_partition_accepts_one_host_per_row(self):
self.assertEqual(partition(self.rows), {"linux": {"portable"}, "macos": {"worker"}})
self.assertEqual(partition(self.rows), {"linux": {"portable"}, "macos": {"worker"}, "windows": {"native"}})

def test_partition_rejects_rows_selected_by_no_job_or_both(self):
for package in (None, "basal-worker", "basal-module", "basal-testkit", "basal-rig"):
for platforms in ([], ["windows"], ["linux", "macos"], None):
for platforms in ([], ["unknown"], ["linux", "macos"], ["linux", "windows"],
["macos", "windows"], ["windows", "unknown"], ["windows", "windows"], None):
with self.subTest(package=package, platforms=platforms):
row = {"id": "unassigned", "runner": "command", "package": package}
if platforms is not None:
Expand All @@ -35,22 +43,40 @@ def test_partition_rejects_rows_selected_by_no_job_or_both(self):

def test_partition_accepts_worker_packages_on_either_host(self):
for package in ("basal-worker", "basal-module", "basal-testkit", "basal-rig"):
for host in ("linux", "macos"):
for host in PLATFORMS:
with self.subTest(package=package, host=host):
row = {"id": "worker", "package": package, "platforms": [host]}
expected = {"linux": set(), "macos": set()}
expected = {platform: set() for platform in PLATFORMS}
expected[host] = {"worker"}
self.assertEqual(partition([row]), expected)

def test_partition_keeps_portable_packages_on_linux(self):
for package in ("basal-proto", "basal-core", "basal-host"):
with self.subTest(package=package):
row = {"id": "portable", "package": package, "platforms": ["linux"]}
self.assertEqual(partition([row]), {"linux": {"portable"}, "macos": set()})
self.assertEqual(partition([row]), {"linux": {"portable"}, "macos": set(), "windows": set()})
row["platforms"] = ["macos"]
with self.assertRaisesRegex(ValueError, "belong on Linux"):
partition([row])

def test_windows_portable_rows_require_a_windows_only_module(self):
for package in ("basal-proto", "basal-core", "basal-host"):
for file in ("src/windows.rs", "src/windows/guard.rs", "src/nested/windows/check.rs"):
with self.subTest(package=package, file=file):
row = {"id": "native", "package": package, "file": file, "platforms": ["windows"]}
self.assertEqual(partition([row]), {"linux": set(), "macos": set(), "windows": {"native"}})
for file in ("", "src/lib.rs", "src/notwindows/guard.rs", "src/windows_guard.rs", "src/windows.rs.bak"):
with self.subTest(package=package, file=file):
row = {"id": "shared", "package": package, "file": file, "platforms": ["windows"]}
with self.assertRaisesRegex(ValueError, "Windows-only module"):
partition([row])

def test_artifact_names_accept_windows_shards_but_not_pr_or_unknown_hosts(self):
for name in ("mutations-linux-1", "mutations-macos-5", "mutations-windows-1", "mutations-windows-12"):
self.assertIsNotNone(ARTIFACT_PATTERN.fullmatch(name), name)
for name in ("mutations-pr-windows", "mutations-windows-0", "mutations-other-1", "mutations-windows-one"):
self.assertIsNone(ARTIFACT_PATTERN.fullmatch(name), name)

def test_partition_rejects_duplicate_ids(self):
for duplicate in (self.rows[0], {"id": "portable", "platforms": ["macos"]}):
with self.subTest(duplicate=duplicate):
Expand All @@ -59,7 +85,7 @@ def test_partition_rejects_duplicate_ids(self):

def test_reports_count_replayed_rows_not_platform_skips(self):
self.assertEqual(verify_reports(partition(self.rows), self.reports),
{"linux": 1, "macos": 1})
{"linux": 1, "macos": 1, "windows": 1})

def test_reports_reject_missing_shard(self):
with self.assertRaisesRegex(ValueError, "every catalogue ID exactly once"):
Expand Down
31 changes: 25 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"crates/basal-core",
"crates/basal-testkit",
"crates/basal-module",
"crates/basal-launch",
"crates/basal-rig",
]

Expand All @@ -20,6 +21,7 @@ rust-version = "1.88"
# Dependencies do not require sibling checkouts.
basal-core = { path = "crates/basal-core" }
basal-host = { path = "crates/basal-host" }
basal-launch = { path = "crates/basal-launch" }
basal-proto = { path = "crates/basal-proto" }
basal-testkit = { path = "crates/basal-testkit" }
blake3 = "1.8"
Expand All @@ -35,7 +37,7 @@ croner = { version = "4.0", default-features = false, features = ["jiff"] }
jiff = { version = "0.2.37", default-features = false, features = ["std", "tzdb-bundle-always"] }
# The fleet's storage mechanics: open with the single-writer lease, WAL and a
# busy timeout, and versioned migrations. Pinned exactly, as astrocyte does.
cortexkit-store = { version = "=0.2.1", features = ["sqlite"] }
cortexkit-store = { version = "=0.2.4", features = ["sqlite"] }
cortexkit-store-types = "0.2.2"
cortexkit-role-tool-provider = "=0.6.0"
cortexkit-role-tool-provider-conformance = "=0.6.0"
Expand Down Expand Up @@ -74,7 +76,7 @@ futures-util = "0.3"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
# The fleet's log format and retention; the module logs through it as
# entorhinal does.
cortexkit-log = "0.3.3"
cortexkit-log = "0.3.5"
tracing = "0.1"
# net.fetch, the network built-in. basal writes its own small HTTP/1.1
# client over rustls so it controls every step the safety rules name:
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,24 @@ basal runs as a module supervised by the subc daemon, with the binary `ck-basal`

Basal provides one agent-facing tool, `codemode({code, description?, limits?})`, on its tool route, where the daemon verifies the caller's principal and stamps its agent and scope identities. A short JavaScript program can await the agent's own tools, capture console output and return one JSON result. prefrontal-core, which manages agents and their tool grants, grants access to codemode and opens the run scope; basal owns the description, run records, budgets and rendered result. Tool schemas come from the daemon catalog, and each provider decides allow, ask and deny using the daemon-recorded agent identity and run scope, just as for a direct call. Model tools are a later extension.

A keyed call derives its run ID from the stamped agent and call key; retries attach without replay. A keyless call executes once under an OS-random ID and warns that its reply cannot be recovered. Basal's parent process (outside the confined worker) caps wall time at 30 minutes plus a separate cumulative 10 minutes waiting only on people: the worker must be blocked and every in-flight tool call waiting for a person's approval or decision. It closes the scope before cancellation kills its worker, and interrupts leftover runs at startup. Long keyed calls can return before the program finishes; `late_results` lets the caller retrieve the finished result later, and `tool.withdraw` cancels the run. Windows refuses `unsupported_platform`. See [the tool and role operations](docs/ops.md#codemode).
A keyed call derives its run ID from the stamped agent and call key; retries attach without replay. A keyless call executes once under an OS-random ID and warns that its reply cannot be recovered. Basal's parent process (outside the confined worker) caps wall time at 30 minutes plus a separate cumulative 10 minutes waiting only on people: the worker must be blocked and every in-flight tool call waiting for a person's approval or decision. It closes the scope before cancellation kills its worker, and interrupts leftover runs at startup. Long keyed calls can return before the program finishes; `late_results` lets the caller retrieve the finished result later, and `tool.withdraw` cancels the run. Codemode runs on macOS, Linux and Windows under each OS's full worker confinement. See [the tool and role operations](docs/ops.md#codemode).

## Crates

| Crate | What |
|---|---|
| `basal-proto` | The versioned IPC protocol between the parent and its worker processes. |
| `basal-worker` | `ck-basal-worker`: the confined QuickJS engine. It confines itself before reading anything: Seatbelt on macOS, seccomp and Landlock on Linux. See [docs/sandbox.md](docs/sandbox.md). |
| `basal-worker` | `ck-basal-worker`: the confined QuickJS engine. Before reading anything it checks its confinement: Seatbelt on macOS, seccomp and Landlock on Linux, and the launcher's restricted LPAC token, job and mitigations on Windows. See [docs/sandbox.md](docs/sandbox.md). |
| `basal-core` | Runtime state: the SQLite store, admission, the run state machine, the journal and mailbox, manifests and authorization, the scheduler and the activation driver. |
| `basal-host` | The `Host` boundary that flow calls go through (module ops, facts, model calls, sinks), the adapters to the fleet's modules, and a deterministic mock. |
| `basal-module` | `ck-basal`: the supervised module, with its subc manifest, the codemode tool-provider role, the worker pool, the flow ops, dry runs and consent cards. |
| `basal-launch` | Windows process creation: restricted LPAC workers and job-owned child processes, with a shared spawn lock. |
| `basal-testkit` | Test parents, crash and cut harnesses, and benchmarks. |
| `basal-rig` | Test support for the isolated ckdev-flows rig only: a callosum stub and the live contract suite against a real prefrontal-core. Never deployed. |

## Building and testing

basal needs Rust 1.88 or newer (edition 2024) on macOS or Linux (x86_64 or aarch64). Dependencies are published on crates.io or pinned to an immutable Git revision.
basal needs Rust 1.88 or newer (edition 2024) on macOS, Linux (x86_64 or aarch64), or Windows. CI runs natively on both Windows runner images, windows-latest and windows-2022. Dependencies are published on crates.io or pinned to an immutable Git revision.

```sh
cargo build --workspace
Expand Down
Loading
Loading