Skip to content
Closed
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
64 changes: 64 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# .dockerignore -- build-context exclusions for every image built from the
# repo root (Dockerfile, distro/Dockerfile.bootable, src/kernel/Dockerfile,
# tests/qemu/Dockerfile; the last one does `COPY . /src/repo/`).
#
# WHY IT EXISTS: without it, `docker build .` ships the whole working copy
# into the context -- .git history, every local build-*/ tree, and (in the
# orchestrator's checkout) .claude/worktrees/, which contains entire nested
# clones of this repo. Slow, fat, and a disclosure risk.
#
# WHY IT IS SHORT NOW (vms-1d9 round 5): round 4 shipped a broad exclusion
# list validated by `grep -rn` across CMakeLists.txt and the Dockerfiles.
# That method cannot see dependencies that flow through add_test()-invoked
# shell scripts, and it missed two real ones:
# * CLAUDE.md -- tests/integration/test_runtime_target.sh:85 greps it for
# "One runtime target: the kernel/QEMU path". Excluding it
# broke THIS EPIC'S OWN Rule 9 standing gate inside the
# image (`ctest -R runtime_target_gate` went red in there
# while CI stayed green, because CI only ran ctest on the
# host checkout -- a silently broken guardrail). 30K.
# * third-party/ -- src/imgact/test/run_tcc_native.sh, run_tcc_rms.sh,
# run_tcc_object_native.sh, run_tcc_selfhost.sh and
# src/vmslink/mk_tcc.sh all hard-require
# third-party/tcc/src. 6.0M.
# Both are now un-excluded, along with docs/, tracking/, .github/, .beads/,
# README.md, LICENSE, .clang-tidy and cppcheck.suppressions.
#
# STANDING RULE FOR EDITING THIS FILE: exclude ONLY things that are not
# version-controlled source. Do NOT exclude tracked files to save space --
# the whole tracked tree minus .git and build-*/ is ~22M, which is not worth
# one broken gate. Any new exclusion must be justified by RUNNING the test
# suite inside the resulting image (`ctest` in the image's build dir) and
# diffing the result against the host checkout, NOT by grepping the build
# files. "The file is gone from the image" is not evidence that nothing
# needed it.

# Version control object database. The tracked worktree itself is still
# copied; only .git is dropped. Verified by running the full ctest suite
# inside the built image: no test shells out to git.
.git

# Local/CI build trees. These are gitignored (.gitignore's `build-*/`) but
# gitignore has NO effect on `docker build`'s context -- only .dockerignore
# does. Present on disk as build-ci/, build-docker/, build-test/,
# build-verify/ (~3MB each). Every Dockerfile configures its own fresh build
# directory, so a stale host build tree is never an input.
build/
build-*/
dist/

# Agent/session state -- generated, not source, and not read by any build or
# test step. .claude/ matters most: .claude/worktrees/ holds entire nested
# checkouts of this repo in the orchestrator's copy, so leaving it in the
# context can multiply the build context several times over.
.claude/
.campfire/
.ready/
.ready.old/
.attestations/

# Editor/OS cruft
*.swp
*.swo
*~
.DS_Store
272 changes: 255 additions & 17 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ if(BUILD_TESTS)
add_subdirectory(tests/dcl)
add_subdirectory(tests/vmsscs)

# Public sys$ API tests for the QEMU kernel-executive harness (vms-1d9,
# epic vms-6b8 Phase 0.5). Links real src/libvms against a real
# /dev/vms when present; SKIP (ctest exit 77), never a fake PASS,
# when it is not (see tests/qemu/CMakeLists.txt).
add_subdirectory(tests/qemu)

# INV-1 standing gate (vms-e652): system identity has ONE owner, and the
# login banner stays driven by SYS$WELCOME rather than a compiled-in
# printf. Source scan — no build artifacts needed.
Expand Down
48 changes: 48 additions & 0 deletions src/libvms/include/lksdef.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* LKSDEF.H - OVMX Lock Status Block (LKSB) Layout
*
* OVMX DESIGN CHOICE, NOT VMS-AUTHENTIC (CLAUDE.md Rule 8). This is NOT a
* transcription of a VSI/HPE-published byte layout. The OpenVMS Programming
* Concepts Manual's $ENQ/$ENQW description documents the LKSB only at the
* field level (a status word, a reserved word, a longword lock ID, and --
* when LCK$M_VALBLK is set -- a 16-byte value block); it does NOT publish a
* byte-offset table. Checked directly against the live OpenVMS VAX 7.3
* oracle (~/vax/cluster) during vms-1d9 round-2 adversarial review:
* SYS$LIBRARY:STARLET.MLB contains NO $LKSB macro at all (LIBRARIAN
* reports %LIBRAR-W-NOMTCHFOU, "no such module") -- there is no VMS-
* authentic macro to pin this layout against, because OpenVMS callers are
* expected to declare the LKSB storage themselves (traditionally 2
* longwords, or a language-specific record) rather than including a
* library-supplied structure definition.
*
* The layout below (status, reserved, lkid, valblk[16]) reproduces
* src/libvms/syssvc/sys_lock.c's existing, already-implemented private
* struct field order, promoted here to a public header (zero behavior
* change) so external callers (tests, future DCL/RTL code) have one
* shared definition instead of each call site guessing sys_lock.c's
* internal layout. It satisfies the field-level LKSB CONTRACT the manual
* describes (same fields, same order, same sizes) -- it is an OVMX
* IMPLEMENTATION of that contract, not a lift of a published VMS struct.
*/

#ifndef __LKSDEF_H
#define __LKSDEF_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

struct lksb {
uint16_t lksb$w_status; /* Completion status (SS$_xxx) */
uint16_t lksb$w_reserved;
uint32_t lksb$l_lkid; /* Lock ID, assigned by the lock manager */
char lksb$b_valblk[16]; /* Lock value block (valid iff LCK$M_VALBLK) */
};

#ifdef __cplusplus
}
#endif

#endif /* __LKSDEF_H */
11 changes: 3 additions & 8 deletions src/libvms/syssvc/sys_lock.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,9 @@
#include <unistd.h>
#include "starlet.h"
#include "vms_kif.h"

/* Lock Status Block (VMS-compatible layout) */
struct lksb {
uint16_t lksb$w_status;
uint16_t lksb$w_reserved;
uint32_t lksb$l_lkid;
char lksb$b_valblk[16]; /* Lock value block */
};
#include "lksdef.h" /* struct lksb — now a public header (vms-1d9); same
* field layout this file always used, just no longer
* a private duplicate only sys_lock.c could see. */

/*
* Lazily open /dev/vms for this thread. vms_kif_open() is idempotent
Expand Down
76 changes: 76 additions & 0 deletions tests/qemu/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# tests/qemu/CMakeLists.txt
#
# Public sys$ system-service tests for the QEMU kernel-executive harness
# (vms-1d9, epic vms-6b8 Phase 0.5). Unlike tests/qemu/test_kmod_*.c (raw
# ioctl(2) against /dev/vms, compiled separately by tests/qemu/Dockerfile
# with plain gcc -static), these programs link the REAL src/libvms public
# sys$ API and are built through the normal OVMX CMake graph, so that
# reverting production code under src/libvms/syssvc/ is visible to them.
#
# Requires a real, insmod'd vms.ko at /dev/vms to do anything beyond prove
# buildability. When /dev/vms is absent (e.g. every dev/CI container build,
# which is NOT the OVMX runtime -- CLAUDE.md Rule 9), each test exits 77 and
# ctest reports SKIPPED, never a fake PASS.
#
# HONESTY NOTE on what this ctest registration actually buys: in every
# environment where `ctest` itself runs (dev machine, the regular CI build
# job -- none of which has /dev/vms, since Docker/podman is not the OVMX
# runtime), this add_test() entry SKIPs (exit 77) -- it never PASSes
# anywhere ctest runs. Inside the one environment where /dev/vms IS real
# (the QEMU kernel-executive job), the binary is invoked directly by
# tests/qemu/init.sh (see the test_kmod_*/test_syssvc_* loop there), NOT
# through ctest -- ctest is not present in that initramfs at all. So this
# ctest entry never executes as a PASSing assertion anywhere: it reports
# Skipped everywhere it can run, and where it would matter it isn't the one
# running. What it buys is build-graph inclusion (qemu_syssvc_tests below
# fails to configure/build if the source doesn't compile) and an honest,
# visible SKIP rather than a silent gap in `ctest` output -- it is NOT
# coverage, despite appearing in the ctest listing next to tests that are.
#
# GENERALITY (vms-1d9 round 3): every test_syssvc_*.c source under this
# directory is discovered by glob and registered automatically -- there is
# no per-file list to remember to update. This is the fix for a real defect
# found by adversarial review of round 2: the harness build/initramfs steps
# named exactly ONE binary (test_syssvc_lock) by literal string in
# tests/qemu/Dockerfile, so the next test_syssvc_*.c a future item adds
# would build and SKIP fine locally, then silently never run in QEMU CI.
# tests/qemu/Dockerfile builds the qemu_syssvc_tests target below (not any
# one test by name) and copies build-static/bin/test_syssvc_* by glob.

function(qemu_syssvc_add_test name source)
add_executable(${name} ${source})
target_include_directories(${name} PRIVATE
${VMS_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/src/libvmssys
)
target_link_libraries(${name} PRIVATE
vms
pthread
m
)
add_test(NAME ${name} COMMAND ${name})
set_tests_properties(${name} PROPERTIES
LABELS "integration;authenticity;executive"
SKIP_RETURN_CODE 77
TIMEOUT 60
)
set_property(GLOBAL APPEND PROPERTY QEMU_SYSSVC_TEST_TARGETS ${name})
endfunction()

# Discover every test_syssvc_*.c in this directory and register it. Note:
# CMake's file(GLOB) does not re-run automatically when a file is ADDED
# (only when an existing tracked file changes) -- a fresh `cmake -B` (which
# every clean build, including tests/qemu/Dockerfile's, always does) picks
# up new files. This is a known CMake glob limitation, not a gap specific
# to this harness: an in-place `cmake --build` in a build dir that already
# exists at config time would miss a newly-added file until reconfigured.
file(GLOB QEMU_SYSSVC_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/test_syssvc_*.c)
foreach(_src ${QEMU_SYSSVC_SOURCES})
get_filename_component(_name ${_src} NAME_WE)
qemu_syssvc_add_test(${_name} ${_src})
endforeach()

# Single build target covering every registered test_syssvc_* binary, so
# callers (tests/qemu/Dockerfile) never need to name a specific test.
get_property(_qemu_syssvc_test_targets GLOBAL PROPERTY QEMU_SYSSVC_TEST_TARGETS)
add_custom_target(qemu_syssvc_tests DEPENDS ${_qemu_syssvc_test_targets})
61 changes: 57 additions & 4 deletions tests/qemu/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,20 @@ ENV DEBIAN_FRONTEND=noninteractive
# gate can actually go red — it is never set for the real barrier job.
ARG NEGATIVE_CONTROL=0

# Install: build tools, kernel packages, QEMU, busybox for initramfs
# Install: build tools, kernel packages, QEMU, busybox for initramfs.
# cmake + musl-tools + linux-libc-dev are for the userspace system-service
# layer (src/libvms, statically linked with musl -- see the OVMX_STATIC
# build below, vms-1d9): the raw-ioctl test_kmod_* programs only need
# gcc/libc6-dev, but exercising the PUBLIC sys$ API needs the real
# src/libvms build graph, which this job did not build at all before.
RUN apt-get update && apt-get install -y --no-install-recommends \
make gcc libc6-dev kmod \
linux-headers-generic \
linux-image-generic \
busybox-static \
cpio \
zstd \
cmake musl-tools linux-libc-dev \
&& rm -rf /var/lib/apt/lists/*

# Install QEMU for the correct architecture
Expand Down Expand Up @@ -64,14 +70,53 @@ RUN KVER=$(cat /tmp/kver) && \
ls -la /src/kernel/vmsfs/vmsfs.ko && \
modinfo /src/kernel/vmsfs/vmsfs.ko

# Copy test source and build statically-linked test programs
COPY tests/qemu/test_*.c /src/tests/qemu/
RUN for f in /src/tests/qemu/test_*.c; do \
# Copy test source and build statically-linked test programs. Only
# test_kmod_* here -- those are raw-ioctl programs needing just
# src/kernel/vms_ioctl.h. test_syssvc_* (vms-1d9) needs the real libvms
# build graph instead and is built separately below, via cmake/musl, from
# the full source tree copied in that stage.
COPY tests/qemu/test_kmod_*.c /src/tests/qemu/
RUN for f in /src/tests/qemu/test_kmod_*.c; do \
out="${f%.c}"; \
gcc -static -O2 -Wall -o "$out" "$f" -I/src/kernel && \
echo "Built: $(basename $out)"; \
done

# --- Userspace system-service layer (vms-1d9) ---------------------------
# Everything above proves the KERNEL lock manager (raw ioctls). This stage
# builds the REAL src/libvms public sys$ API statically (musl, same
# OVMX_STATIC mode distro/Dockerfile.bootable already uses to produce the
# bootable distro) so tests/qemu/test_syssvc_lock.c can call sys$enq /
# sys$enqw / sys$deq -- not raw ioctls -- against a real /dev/vms. A static
# musl binary needs no separate C runtime staged into the initramfs: it
# carries its own libc, identically to how STARTUP.EXE/DCL.EXE already
# boot with no dynamic linker present. This is the initramfs's ONLY
# consumer of the OVMX build graph; the raw-ioctl test_kmod_* programs
# above stay plain "gcc -static" against vms_ioctl.h, unchanged.
#
# musl doesn't ship linux/ or asm/ UAPI headers -- symlink from
# linux-libc-dev (same trick as distro/Dockerfile.bootable).
RUN MUSL_INC=$(echo /usr/include/*-linux-musl) && \
ARCH_INC=$(echo /usr/include/*-linux-gnu) && \
ln -s /usr/include/linux "$MUSL_INC/linux" && \
ln -s /usr/include/asm-generic "$MUSL_INC/asm-generic" && \
ln -s "$ARCH_INC/asm" "$MUSL_INC/asm"

# Full source tree needed: the OVMX CMake graph resolves libvms's
# dependency chain (vmsprocess, vmsfs, vmslnm, vmssys) itself -- see
# CMakeLists.txt's "Subdirectories (order matters for dependencies)".
COPY . /src/repo/
RUN cd /src/repo && \
cmake -B build-static \
-DCMAKE_C_COMPILER=musl-gcc \
-DCMAKE_BUILD_TYPE=Release \
-DOVMX_STATIC=ON \
-DBUILD_TESTS=ON \
-DBUILD_TOOLS=OFF \
&& cmake --build build-static --target qemu_syssvc_tests --parallel $(nproc) 2>&1 && \
echo "--- test_syssvc_* binaries (public sys\$ API, static musl) ---" && \
ls -la build-static/bin/test_syssvc_*

# Build mkimage_vmsfs (host tool) and create test image
COPY tests/qemu/mkimage_vmsfs.c /src/tests/qemu/
RUN gcc -O2 -Wall -o /src/tests/qemu/mkimage_vmsfs \
Expand Down Expand Up @@ -122,6 +167,14 @@ RUN mkdir -p /initramfs/bin /initramfs/lib/modules /initramfs/dev \
for f in /src/tests/qemu/test_kmod_*; do \
[ -x "$f" ] && cp "$f" /initramfs/tests/ || true; \
done && \
# Public sys$ API test(s), statically linked against real libvms
# (musl) -- see the OVMX_STATIC build stage above (vms-1d9). Copies
# EVERY test_syssvc_* binary the qemu_syssvc_tests target produced, by
# glob -- not one binary by literal name -- so a future test_syssvc_*.c
# (e.g. test_syssvc_event.c) needs no Dockerfile edit to reach QEMU.
for f in /src/repo/build-static/bin/test_syssvc_*; do \
[ -x "$f" ] && cp "$f" /initramfs/tests/ || true; \
done && \
# Init script (PID 1)
cp /src/tests/qemu/init.sh /initramfs/init && \
if [ "$NEGATIVE_CONTROL" = "1" ]; then \
Expand Down
48 changes: 45 additions & 3 deletions tests/qemu/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,60 @@ fi
# Create directories needed by vmsfs tests
mkdir -p /tmp/vmsfs_backing /mnt/vmsfs

# Run each test program
for test in /tests/test_kmod_*; do
# Run each test program. test_kmod_* drive /dev/vms with raw ioctls
# (kernel lock manager, ASTs, event flags, access modes, vmsfs). test_syssvc_*
# drive the same /dev/vms through the PUBLIC sys$ API in src/libvms instead
# (vms-1d9) -- exercising the userspace system-service layer the ioctl tests
# cannot see at all.
#
# PER-SUITE VERDICT LINE (vms-1d9 round 5). After each suite we print
#
# === SUITE <name> rc=<exit code> ===
#
# and .github/workflows/ci.yml asserts on THAT, per suite, instead of on the
# aggregate "FINAL RESULTS" tally below. Two real defects made this necessary,
# both proven by adversarial review against running artifacts:
#
# 1. The aggregate tally cannot distinguish an honest skip (rc 77) from a
# failed assertion (rc 1) -- the two branches below both increment
# TOTAL_FAIL. An adversary injected a real silent fallback into
# src/libvms/syssvc/sys_lock.c (returning SS$_NORMAL instead of
# SS$_NOSUCHDEV with /dev/vms absent, in both do_enq and sys$deq); the
# test's SS$_NOSUCHDEV assertions all FAILED and its exit code changed
# 77 -> 1, yet the negative-control job's FINAL RESULTS accounting was
# BYTE-IDENTICAL to the clean tree and every CI assertion still passed.
# A per-process fake reporting success was invisible to the whole gate.
# rc is the test binary's real exit status, derived from real production
# status codes -- not a message the harness prints unconditionally.
#
# 2. Any assertion on the aggregate count is either a pin that turns CI red
# when a legitimate new suite is ADDED, or a floor that stops protecting
# every suite added after it was written. Per-suite lines let CI derive
# the expected set from the checkout (`ls tests/qemu/test_*.c`), which is
# addition-tolerant AND drop-detecting with nothing maintained by hand.
#
# The TOTAL_PASS/TOTAL_FAIL tally is kept for human readers and for
# run_tests.sh's exit code; it is no longer the thing CI pins.
for test in /tests/test_kmod_* /tests/test_syssvc_*; do
[ -x "$test" ] || continue
name=$(basename "$test")
echo ""
echo "--- $(basename $test) ---"
echo "--- $name ---"
"$test"
rc=$?
if [ $rc -eq 0 ]; then
TOTAL_PASS=$((TOTAL_PASS+1))
elif [ $rc -eq 77 ]; then
# Honest skip (e.g. /dev/vms absent) -- should never happen in this
# job, since vms.ko was just insmod'd above. Count as a FAIL: if it
# ever fires here, the executive is not actually present, which is
# exactly what this job exists to catch.
echo " SKIP reported inside the kernel-executive job -- treating as FAIL"
TOTAL_FAIL=$((TOTAL_FAIL+1))
else
TOTAL_FAIL=$((TOTAL_FAIL+1))
fi
echo "=== SUITE $name rc=$rc ==="
done

echo ""
Expand Down
Loading
Loading