Skip to content

vms-46c: conversational SYSBOOT> boots over the Files-11 ACP — no /vms residual - #676

Closed
baron-3dl wants to merge 103 commits into
mainfrom
work/vms-46c-sysboot-acp
Closed

vms-46c: conversational SYSBOOT> boots over the Files-11 ACP — no /vms residual#676
baron-3dl wants to merge 103 commits into
mainfrom
work/vms-46c-sysboot-acp

Conversation

@baron-3dl

Copy link
Copy Markdown
Contributor

Closes the flip reap-blocker vms-46c: the conversational SYSBOOT> boot path still used the RETIRED /vms passthrough that the atomic flip (epic vms-208) excises.

Root cause

  • The conversational branch in bare_metal_init() legacy-mounted vmsfs (ovmx_boot_mount_system_disk(...,"vmsfs",...)) instead of the ACP mount the flagless path uses.
  • sysboot.c did raw /vms param I/O (VMS_SYSTEM_DIR + fopen).
  • PID 1 (STARTUP.EXE, -static) links NO RMS, so it could not reach the ovmx_sysgen_acp_* seam in src/vmsrms/sysgen_acp.c.

Fix (Linux runtime; NetBSD-vax keeps its path until vms-d5d)

  • ovmx_init.c — conversational branch attaches the executive silently before the prompt (executive_attach_silent), ACP-mounts via ovmx_boot_mount_system_disk_native() (same path as flagless), and defers %OVMX-I-EXEC/banner/mount narration to after the prompt (executive_announce). Pre-SYSBOOT> ordering preserved (§3.1: nothing precedes the prompt).
  • sysboot.c — SET/USE CURRENT/WRITE route through the shared sysgen_params.h machinery (sysgen_load_working/sysgen_commit_working) over the ACP seam; the /vms fopen readers are compiled out on the Linux runtime.
  • ovmx_boot_sysgen_acp.c (new, Linux-only) — the strong ovmx_sysgen_acp_* definition for PID 1, backed by the imgact_acp.c ACP client already linked into STARTUP.EXE (the flip's image-staging bridge). Chose this over linking vmsrms_static because the static PID-1 image links no RMS — this pulls no rms_core.o (full FAB/RAB/idx engine) into the static image and adds no new link deps.

Invariants

INV-6 / Rule 9: no /vms fallback on the conversational path; ACP-unreachable fails honest (SS$_NOSUCHDEV/SS$_NOSUCHFILE).

Grep-guard clean on the conversational path (see build report).

Verification

Local: compile + STARTUP.EXE static link (crux: resolves the ACP symbols). Runtime proof runs off-box (tests/qemu/test_sysboot_cluster_params_e2e.sh, verified by the conductor via CI/k3s by-SHA).

🤖 Generated with Claude Code

baron-3dl and others added 30 commits August 17, 2026 03:09
…er alloc + dir insert + dealloc, epic vms-208)

The sixth and final rung of the executive Files-11 ODS-2 ACP-QIO surface: the
ACP now creates, deletes and modifies files against a real /dev/vms.

Codec port (Rule 8 -- ports the proven format logic from the userspace writer,
does not invent a new layout; parallel pure-function set in ods2_edit.c):
  - ods2_fh2_build(): the pure write-side twin of ods2_writer.c's
    write_fh2_header_ext() -- allocate/init a complete FH2 into a caller block,
    owner/prot as parameters (INV-6 creator UIC).
  - ods2_dir_insert_blocks() / ods2_dir_remove_blocks(): the pure twins of
    ods2_wvolume_dir_insert() / merge_dir_record() -- flatten, splice/merge a
    versioned {name,version,fid} record (or drop one), greedy-repack into blocks.
  - ods2_ifbm_block_fid_used/alloc/free(): the index-file (INDEXF.SYS) bitmap
    bit ops (SET == IN USE, opposite sense from the storage bitmap).
The userspace ods2_writer.c path is untouched.

ioctl-mapping decision: the ACP band 0x68-0x6F is full and 0x70 is mailboxes, so
IO$_CREATE/DELETE/MODIFY route through ONE new func-dispatched ioctl,
VMS_IOCTL_ACP_FILEOP, whose `func` field carries the $QIO function code. This is
more VMS-faithful than one-ioctl-per-function ($QIO is a single service selected
by function code) and extends #641's 0x6F umbrella. FILEOP reuses nr 0x6F with
its own larger, ATR-carrying struct: _IOWR folds sizeof into the request number
so FILEOP (252 B) and ACPCONTROL (200 B) are distinct 32-bit commands. No ABI
break to the frozen ACPCONTROL struct; a _Static_assert guards their distinctness.

Handler (vms_ioctl_acp_fileop, gated OVMX_ODS2_KERNEL; codec-free build refuses
SS$_DEVNOTMOUNT): CREATE allocates a real FID from the index bitmap, inits the
FH2 from the ATR list, optionally extends, enters a new highest version in the
directory, optionally accesses; DELETE removes the directory entry and (M_DELETE)
deallocates header + blocks; MODIFY extends / truncates (freeing blocks) / writes
attributes. Protection-gated (INV-6); fail-honest (SS$_NOSUCHFILE, SS$_BADPARAM,
SS$_DEVICEFULL, SS$_DUPLNAM).

Proof (real /dev/vms, QEMU kernel-executive harness): test_syssvc_acp_create.c,
27/27 -- CREATE assigns a real FID + ;1 entry, readable back by name; write +
persist across DEACCESS/re-ACCESS (INV-6); second create -> ;2 distinct FID;
DELETE removes + deallocs (ACCESS -> SS$_NOSUCHFILE); MODIFY extend/truncate/attr
each persist; fail-honest edges. Whole harness 89 suites / 1606 assertions, 0 fail.

Cascade: new executive symbol vms_kif_acp_fileop appended to libvmssys_shr.vec;
genuine negctl anchor acp-create-header-slot-offbyone (INDEXF header-slot
off-by-one) in facility_defects.sh (coverage PASS, FLOOR-NO-BUMP: 112 >= 104);
vms.ko builds out-of-tree AND codec-free (bootable overlay); kernel-core stays
Alpha/VAX-portable (fixed-width types, byte-wise LE accessors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ccess, ATOMIC-FLIP-GROUP — red-by-design)

The freestanding image activator now reads every image file over the executive
Files-11 (ODS-2) ACP -- $ASSIGN a file-class channel to the mounted volume,
IO$_ACCESS the file by walking its directory chain, IO$_READVBLK its header +
PT_LOAD segments -- instead of open()/pread()/mmap() on a /vms POSIX path (the
passthrough the Files-11 ACP pivot retires, docs/design-files11-acp-executive.md
Sec 4.6). Read-then-place first cut; demand-page-through-the-window is the end
state (noted as follow-up).

- src/imgact/imgact_acp.{c,h}: freestanding ACP reader. Issues REGISTER (adopt-
  or-create PCB) / ACP_ASSIGN / ACP_ACCESS / ACP_READVBLK / ACP_DEACCESS / DASSGN
  as raw ioctls on /dev/vms via three host primitives (syscall6 in IMGACT, libc
  in the test). It calls NO libvmssys vms_kif_* symbol, so no libvmssys_shr.vec /
  SYS_VEC / native-link enumeration change is needed.
- src/imgact/imgact.c: load_object, load_ovmx_producer, ovmx_find_section,
  apply_vms_rel and activate_symbol_vector read through an imgsrc handle backed
  by the ACP. NO silent POSIX fallback (INV-6): no /dev/vms or file-not-on-the-
  ACP-volume -> honest %IMGACT-F-IMGNOTFND, never a /vms read. SYS_ioctl added
  per arch.
- tests/qemu: test_syssvc_imgact_acp.c drives the exact freestanding reader
  against a real /dev/vms over a generated ODS-2 fixture (mkimage_ods2_imgact.c +
  imgact_acp_fixture_elf.h) on a new 4th disk DKA300: (vdd). test_kmod_disk
  updated for the 4th disk (negctl moves to DKA400:). Genuine negctl anchor
  imgact-acp-valid-bytes-offbyone in facility_defects.sh (FLOOR-NO-BUMP).

Proven on real /dev/vms (QEMU kernel-executive harness): test_syssvc_imgact_acp
13 passed, 0 failed -- header + program-header table + every PT_LOAD segment +
whole image byte-exact vs the on-disk image, fail-honest SS$_NOSUCHFILE /
SS$_NOSUCHDEV. test_kmod_disk 19 passed, 0 failed.

ATOMIC-FLIP-GROUP member, red-by-design until the flip ACP-mounts SYS$DISK. DOES
NOT MERGE STANDALONE. See the PR body for the expected-red inventory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fd removed (ATOMIC-FLIP-GROUP — red-by-design)

RMS no longer does positioned POSIX I/O on a per-process fd. FAB._linux_fd is
retired; RMS reaches file data through the Files-11 ODS-2 ACP (epic vms-208):

  $OPEN   -> $ASSIGN SYS$DISK + IO$_ACCESS (name->FID, VBN->LBN window)
  $CREATE -> IO$_CREATE(+IO$M_ACCESS)  ($ERASE -> IO$_DELETE)
  $CLOSE  -> IO$_DEACCESS + $DASSGN    ($EXTEND -> IO$_MODIFY)
  $GET/$PUT record I/O -> IO$_READVBLK / IO$_WRITEVBLK at {VBN, byte-offset}
  resolve_filename resolves via the ACP, NOT vmsfs_to_linux_path.

The block-I/O SUBSTRATE swap: a new rms_io.c re-homes the POSIX-fd cursor
vocabulary (lseek/read/write/read_exact/write_exact/ftruncate/fsync) the
seq/rel/idx record engines depend on onto {VBN,offset,length} READVBLK/WRITEVBLK
on the channel window. The record logic (RFM framing, cursor arithmetic, key
compares) is UNCHANGED -- only the fd+pread beneath it becomes channel+$QIO.
Two backends behind one interface: __linux__ = the ACP (product runtime);
otherwise = POSIX (the netbsd-vax standalone cross, until VAX's own ACP
re-target vms-d5d). No silent POSIX fallback on Linux -- an absent /dev/vms is
the real RMS/SS$ error (INV-6).

Scope: SEQUENTIAL (VAR/STMLF/FIX) proven byte-exact end-to-end. RELATIVE rides
the same substrate (cell pre-alloc via IO$_MODIFY). INDEXED is fail-honest
DEFERRED on the ACP (RMS$_ORG): its data fork rides the substrate, but the
ODS-2 prologue/bucket index has no ACP home yet -- a separate rung. Record
attributes (RFM/RAT/MRS) are supplied on the FAB; FAT persistence via an
extended IO$_CREATE ATR is deferred (the sidecar is retired on Linux).

PROVEN on a real /dev/vms (tests/qemu/test_syssvc_rms_acp.c, QEMU
kernel-executive harness): RMS-over-ACP 38 passed, 0 failed -- $CREATE+$PUT lands
records via WRITEVBLK, $CLOSE + re-$OPEN + $GET reads them back byte/record-exact
via READVBLK for VAR, STMLF and FIX; $EXTEND grows allocation; $ERASE deletes
(subsequent $OPEN is RMS$_FNF).

ATOMIC-FLIP-GROUP, red-by-design, DO NOT MERGE STANDALONE: existing RMS/DCL/MMK
suites that hit SYS$DISK now fail-honest (no ACP-mounted SYS$DISK at boot yet --
that mount co-lands with the flip). Expected-red: vmsrms_unit,
vmsrms_idx_close_flush, parts_rms_indexed_functional, toolchain-mmk-parse,
toolchain-mmk-component-plan (host ctest); test_syssvc_rms_scratch_create,
test_syssvc_mmk_build, test_syssvc_mmk_drive (QEMU).

Stacks on #644 (work/vms-5303-acp-create). Rebase onto main after #644 merges.

Cascade: vms_kif.h OVMX-UNWIRED annotations for acp_access/deaccess/readvb/
writevb/fileop deleted (RMS is now their product caller -- census gate green);
rms_core/rms_record OVMX service-register annotations updated to PARTIAL
(register gate green); mk_vmsrms_shr.sh native-link enumeration adds rms_io +
libvmssys include (acp symbols already in libvmssys_shr.vec; strtok_r already in
DECC$SHR); genuine negctl anchor rms-put-wrong-vbn added (coverage PASS,
FLOOR-NO-BUMP).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (ATOMIC-FLIP-GROUP — red-by-design)

DCL DIRECTORY / SET DEFAULT / MOUNT / TYPE / COPY / CREATE and the F$SEARCH /
F$FILE_ATTRIBUTES / F$PARSE lexicals no longer reach files through
vmsfs_to_linux_path() + POSIX opendir/stat/fopen on the /vms passthrough. They
reach files the VMS way -- RMS ($OPEN/$GET/$CREATE/$PUT/$SEARCH) and an OVMX RMS
attribute accessor -- which on the product runtime route to the Files-11 ODS-2
ACP over /dev/vms (epic vms-208), and on the netbsd-vax cross keep RMS's own
POSIX backend until vms-d5d.

RMS substrate (src/vmsrms):
  - rms_search.c: sys$search rerouted to the ACP wildcard directory context
    (IO$_ACPCONTROL) -- genuine ODS-2 order, real FIDs. Adds rms_search_fid()
    (DIRECTORY /FULL reads the real File ID) and rms_search_end(). This WIRES the
    previously-UNWIRED vms_kif_acp_acpcontrol to a product caller.
  - rms_core.c: adds rms_file_attr() -- the DIRECTORY /FULL + F$FILE_ATTRIBUTES
    source of truth: real FID + size + protection + dates + record format from
    the ODS-2 header via IO$_ACCESS's ATR list, not stat(). Shares
    rms_acp_resolve_did via rms_internal.h.

DCL (src/vmsdcl): a new dcl_rms.h helper layer (homed in the existing
dcl_filespec.c TU -- no new native-link TU, NOBJ stays 25) provides read/write/
dir/attr helpers over RMS. cmd_type/cmd_create/cmd_copy/cmd_directory,
cmd_set_default, cmd_mount, and lex_search/lex_file_attributes/lex_parse route
through them. DIRECTORY /FULL now emits the genuine ODS-2 File ID. cmd_mount
mounts through the ACP ($MOUNT), WIRING the previously-UNWIRED vms_kif_acp_mount.
vmsdcl now links vmsrms (Debug); mk_dcl.sh already --uses LIBVMSRMS$SHR.

Fail-honest (Rule 9 / INV-6): no ACP-mounted SYS$DISK => the real RMS/SS$ error,
never a silent POSIX fallback.

PROVEN on a real /dev/vms (tests/qemu/test_syssvc_dcl_acp.c, QEMU kernel-
executive harness, 20 passed / 0 failed): F$SEARCH/DIRECTORY returns A.TXT;3/;2/;1,
B.TXT;1 in genuine ODS-2 order with real File IDs 14/13/12/16; rms_file_attr
returns the same real FID + version + on-disk attributes; SET DEFAULT verifies a
directory via the ACP; CREATE/TYPE/COPY round-trip byte-exact through the ACP;
fail-honest edges. Negctl anchor dcl-acp-search-fid-fabricated (FLOOR-NO-BUMP).

ATOMIC-FLIP-GROUP, red-by-design, DO NOT MERGE STANDALONE, stacks on #649 -> #644:
existing DCL SYS$DISK tests now fail-honest with no boot-mounted ACP SYS$DISK
(dcl-integration: %DCL-E-DIRECT / %RMS-E-FNF, no crashes). Co-lands with the flip
that ACP-mounts SYS$DISK at boot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… use RMS $PUT/$CREATE (ATOMIC-FLIP-GROUP — red-by-design)

The SYSUAF / RIGHTSLIST / $GETUAI authentication reads and the LASTLOGIN
per-boot writer reach their file the VMS way now: RMS $OPEN/$GET and
$CREATE/$PUT over the Files-11 ODS-2 ACP (rms_impl_open, #649), NOT fopen on
the /vms passthrough. A new rms_textfile helper (src/libvms/rtl/rms_textfile.c)
carries the sequential read + append/create-write vocabulary; sysuaf_scan,
rightslist_scan, find_uaf_record and ovmx_accounting_* route through it.

Library layering: these consumers live in LIBVMS, which sits BELOW RMS
(LIBVMSRMS links LIBVMS). The RMS services are referenced WEAKLY so LIBVMS$SHR
builds/loads with no hard dependency on LIBVMSRMS -- an image that also links
vmsrms (LOGINOUT, VMSSSHD, DCL, the QEMU tests) binds the real services; one
that does not sees NULL and fails honestly. No fanout across every vms consumer.

Fail-honest (Rule 9 / INV-6): no ACP volume / no /dev/vms / no such file ->
the reader returns NULL and the writer returns -1, never a POSIX fallback.
#if defined(__linux__) guards the reroute; the netbsd-vax cross keeps POSIX.

Proven on real /dev/vms (tests/qemu/test_syssvc_loginout_acp.c, 16/16 PASS):
SYSUAF created + read back + authenticated off the ODS-2 volume via the ACP,
DISMOUNTED read + absent file fail-honest, OPERATOR.LOG append + LASTLOGIN
write land as genuine ODS-2 records read back byte-exact. Genuine negctl anchor
loginout-acp-auth-from-ods2 (selftest + coverage PASS).

DO NOT MERGE STANDALONE. Stacks on #649 (RMS-over-$QIO) -> #644 (CREATE/DELETE).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…vms-5f0-atomic-flip

# Conflicts:
#	src/libvmssys/vms_kif.h
#	tests/qemu/facility_defects.sh
…vms-5f0-atomic-flip

# Conflicts:
#	tests/qemu/facility_defects.sh
…work/vms-5f0-atomic-flip

# Conflicts:
#	src/vmsdcl/CMakeLists.txt
#	tests/qemu/facility_defects.sh
…ork/vms-5f0-atomic-flip

# Conflicts:
#	tests/qemu/Dockerfile
#	tests/qemu/facility_defects.sh
#	tests/qemu/run_tests.sh
#	tests/qemu/test_kmod_disk.c
…tomic flip)

bare_metal_init (flagless Linux path) now $MOUNTs the boot unit DKA0: through
the executive ACP (vms_kif_acp_mount) instead of the vmsfs.ko VFS mount of a
bespoke-VMFS volume at /vms. New boot seam ops ovmx_boot_acp_mount_system_disk
+ ovmx_boot_system_disk_unit; NetBSD backend gets non-behavioral stubs (VAX
runtime re-target vms-d5d is driven separately). Requires executive_attach()
first, which the flagless path already does.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lip)

distro/Dockerfile.bootable: all three mastered boot disks (distrib, negctl,
install-media) now built with 'vmsfs_master --ods2' -- a genuine ODS-2
(DECFILE11B) volume the Files-11 ACP $MOUNTs, not the bespoke OVMX VMFS. The
distrib ground-source gate reads it back with the tool's genuine ODS-2 reader
(--ods2 list, the same ods2_bdev codec the ACP uses) and greps the login chain
present; byte-exact read-back is proven by the QEMU ACP tests (extract is
VMFS-only). Validated master+list format locally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- rtl/rms_textfile added to LIBVMS$SHR TU manifest (mk_libvms_shr.sh): vms-274
  added the SYSUAF/RIGHTSLIST RMS-over-ACP reader but not to the native-link
  source list, so sysuaf.c's rms_textfile_open was unresolved.
- LIBVMSRMS$SHR now --use's LIBVMSSYS$SHR directly (mk_vmsrms_shr.sh +
  build_link_native.sh + 6 imgact-test harnesses): vms-bc7 made vmsrms IMPORT
  vms_kif_acp_* but LINK.EXE does not resolve a --use'd shareable's imports
  transitively, so the vmsrms link was red-by-design. All harnesses already
  build LIBVMSSYS$SHR (LIBVMS$SHR needs it) and derive its vector from
  libvmssys_shr.vec (which exports the 9 ACP symbols).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bc7 merge auto-combined (no conflict reported) two copies of
vms_ioctl_acp_fileop -- main's vms-233 DLM-locked version AND bc7's older
pre-DLM version -- into one file (redefinition error, kernel-module build
only; the Debug ctest does not compile drivers/ovmx so it slipped through).
bc7's vmsfs_acp.c is a strict SUBSET of main's (git diff main..bc7 = -93/+0),
so main's version is authoritative -- restored it (single definition, the
acp-fileop-no-dlm-lock negctl anchor vms_lock_acp_vol_ex intact).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The map-encoder was format-1-only (<=256 blocks / 128KB per pointer, and one
pointer per file), so ods2_wvolume_create_file_raw() rejected any larger file
with ODS2_ERR_ARGS -- a genuine ODS-2 system disk could not hold real binaries
(DCL.EXE is 840KB). This surfaced only when step 3 flipped the boot master to
--ods2: 'create AUTHORIZE.EXE failed: bad arguments', aborting the master.

encode_map_extent now picks the smallest FM2 format that covers the run --
format 1 (<=256 blk), 2 (<=16384 blk / 8MB), or 3 -- so a large CONTIGUOUS file
is ONE pointer, exactly as real VMS records a contiguous file and within the
runtime ACP window budget (ACP_WINDOW_MAX=24). write_fh2_header_ext advances the
map by each pointer's actual width; the INDEXF callers keep format-1. The reader
already decodes formats 2/3.

Verified: DCL.EXE (840KB) round-trips BYTE-EXACT through the codec; new
test_ods2_write BIGFILE.BIN case proves a 300-block file is one format-2 extent,
byte-exact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The full static system tree + the redundant SYS$UPDATE:OVMX-OS.KIT copy sit
right at the old 64MB edge; the ODS-2 master overflowed it ('create OVMX-OS.KIT
failed: no space'). ODS-2 gives every file >=1 block with no cross-file packing,
so it needs a little more room than the retired VMFS master. Bumped all three
mastered boot disks (distrib, negctl, install-media) to 128MB. Blank DKA0: disks
stay 64M (no system tree). Confirmed not an alloc bug: a 3.2M tree masters into
an 8MB ODS-2 volume exactly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_CAP)

Second half of the large-file fix. The bdev-mode write-cache (WCACHE_CAP=4096
blocks / ~2MB) buffers dirty blocks and did NOT evict -- so create_file_raw for
any file whose data run exceeds ~2MB overflowed it (wcache_seed_zero_range +
the data-copy loop both fill it) and returned ODS2_ERR_NOSPACE. That is why the
boot master aborted on SYS$UPDATE:OVMX-OS.KIT (3.3MB) even on a 128MB volume:
the volume had room, the 2MB cache did not.

wcache_block() now flushes the whole working set to the device and retries when
full, instead of failing. Safe because this writer's block accesses are
write-forward within one op (seed/copy loops + header/dir builders use each
returned pointer immediately, never holding one across the next wblk()); a
flushed block re-read later returns exactly what was written (the zero_fill==0
miss path re-reads via ods2_blk_read). Bounds memory to WCACHE_CAP regardless of
file size -- important as this cache is shared with the kernel ACP.

Verified: a 20MB file (format-3 map, 40x the cache) round-trips BYTE-EXACT;
test_ods2_master.sh now masters a 5MB binary; all 13 ods2 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…UNT)

The boot reached PID 1's ACP $MOUNT of SYS$DISK and it fail-honestly refused:
  %OVMX-F-SYSINIT, system disk DKA0: (/dev/vda) would not $MOUNT via the Files-11 ACP
Root cause: acp_validate_ods2() is gated on OVMX_ODS2_KERNEL, which only the
out-of-tree QEMU-test vms.ko defined -- the in-tree BOOTABLE vms.ko was built
without the codec, so vms_ioctl_acp_mount took the #else and returned
SS$_DEVNOTMOUNT for EVERY volume (the codec-less refuse branch). The master's
128MB ODS-2 disk is genuine -- it passes the exact validation chain
(home_parse strict=1, BITMAP.SYS FH2, SCB, struclev) in userspace; the kernel
just couldn't run that chain.

The flip is the first product path to call the bootable ACP $MOUNT, so vms.ko
must now carry the codec -- mirrors vmsfs.ko's vms-4a8 solution: vms-y adds
ods2_reader.o + ods2_edit.o (the pure parse/validate + edit surface; NOT the
writer/bdev/block objects -- vmsfs_acp.c does its own exec_blockdev I/O),
ccflags adds -DOVMX_ODS2_KERNEL, and sources.conf stages src/vmsfs/ods2/*.c +
the flatten-safe vmsfs/ods2.h (the '->' convention). src/kernel/Makefile stays
the co-authoritative object list. Comments updated in both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…em wall

The atomic flip made SYS$DISK a genuine Files-11 (ODS-2) volume owned by the
executive ACP and retired the /vms POSIX passthrough, but the boot halted at
require_installed_system()'s POSIX stat("/vms/.../DCL.EXE") on the now-empty
/vms tree. This lands the ACP-read bootstrap bridge the flip needs.

The Linux kernel still activates a VMS image the Unix way: execve() maps a MAIN
image's PT_LOAD and opens its PT_INTERP (IMGACT.EXE) BY POSIX PATH before any
OVMX code runs. The boot chain genuinely fork()+execve()s a small first-hop set
-- PROVISION.EXE, DCL.EXE, JOB_CONTROL.EXE, LOGINOUT.EXE, plus the PT_INTERP
IMGACT.EXE. With /vms gone those files have no POSIX home.

Bridge:
  - require_installed_system() probes DCL.EXE THROUGH THE ACP ($ASSIGN +
    IO$_ACCESS over /dev/vms), not a POSIX stat -- fail-honest, never faked.
  - PID 1 stage_boot_images() reads the first-hop set off the genuine ODS-2
    volume THROUGH THE ACP (ovmx_boot_acp_read.c reuses the proven imgact_acp.c
    IO$_ACCESS + IO$_READVBLK walk, libc-backed) into OVMX_BOOT_STAGE_DIR
    (/run/ovmx-boot, a tmpfs), and every execve target that names a SYS$SYSTEM
    image is rewritten there (ovmx_boot_stage_exec_path, self-guarding on the
    staged copy's presence). The BYTES come from the ACP; tmpfs is only the
    Linux-exec handoff (INV-6: no /vms read, no faked presence, no initramfs
    stage). Sites wired: ovmx_init (PROVISION), ovmx_provision (DCL),
    sys$creprc (JOB_CONTROL/SPAWN), ovmx_job_control (LOGINOUT), vms_login
    (post-auth DCL).

Linux-substrate only: the NetBSD-vax boot path (ovmx_boot_netbsd.c) is flipped
separately by vms-d5d, so the bridge sources compile in only on the Linux
backend and the call sites are OVMX_BOOT_LINUX-guarded / self-guard on the
staged file -- NetBSD keeps its current boot behaviour untouched.

DEFERRED (noted in link.c): IMGACT_INTERP (spot #3) stays /vms/... for now --
the boot walls at the data-read layer (PROVISION's SYSUAF read over the retired
/vms) BEFORE any PT_INTERP is resolved, and ~30 native activation tests bake the
interp string, so the interp flip must land with migrating those tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… via the ACP

Completes the exec-bridge so a native image (DCL.EXE/LOGINOUT.EXE) actually
activates at boot off the genuine ODS-2 volume:

  - Rewriter case-fix (ovmx_layout.h): the /vms passthrough resolves a
    filename component in LOWERCASE (VMS specs are case-insensitive) while the
    ODS-2 volume and the staged copies carry the UPPERCASE name. Make
    ovmx_boot_stage_exec_path() detect ".EXE"/"SYSEXE" case-insensitively and
    emit an UPPERCASE basename so the rewritten exec target matches the staged
    file. (Also fixes a macro double-evaluation bug in the uppercasing.)

  - Interp flip (spot #3, link.c + src/vmslink/CMakeLists.txt): IMGACT_INTERP
    is now overridable (#ifndef); the CMake `vmslink` target that LINK.EXE-
    builds the BOOTABLE DCL.EXE/LOGINOUT.EXE bakes PT_INTERP =
    "/run/ovmx-boot/IMGACT.EXE" (the staged loader), while the default stays
    the /vms path so the ~30 standalone native activation tests -- which build
    their own LINK.EXE from source and stage IMGACT.EXE under /vms -- are
    untouched.

  - IMGACT staged-path map (imgact.c): the kernel hands IMGACT the tmpfs path
    of a staged first-hop image; IMGACT maps it back to its SYS$SYSTEM volume
    location (/run/ovmx-boot/NAME -> /vms/SYS0/SYSCOMMON/SYSEXE/NAME) before the
    ACP open, so IMGACT still reads the GENUINE image bytes THROUGH THE ACP and
    never the tmpfs copy (INV-6). Non-staged paths pass through unchanged.

Boot now advances four walls past require_installed_system: executive attach ->
SYS$DISK ACP $MOUNT -> PROVISION establishes SYSTEM [1,4] identity -> DCL.EXE
ACTIVATES VIA THE ACP -> DCL runs STARTUP.COM. New wall is the data-read half
of the flip: RMS's ACP path (rms_acp_spec_from_fab, rms_core.c) does not
resolve logical names, so SYS$STARTUP:VMS$PHASES.DAT / VMS$VMS.DAT return
%RMS-E-FNF (it $ASSIGNs "SYS$STARTUP:" as a device). Diagnosed for the
follow-on data-read-flip rung.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…se-candidates

RMS opens files only through the Files-11 ODS-2 ACP on Linux (INV-6, no POSIX
fallback), but rms_acp_spec_from_fab treated a logical like SYS$STARTUP: as a
device to $ASSIGN, so ODS-2-only files (SYS$STARTUP:VMS$PHASES.DAT,
SYS$SYSTEM:OVMXVMSSYS.PAR, ...) returned %RMS-E-FNF and STARTUP.COM stalled.

Compose the effective filespec through vmsfs_compose_ods2_candidates() -- the
same rooted/concealed search-list fan-out (SYS$SYSTEM: -> [SYS0.SYSEXE] +
[SYS0.SYSCOMMON.SYSEXE]) the ACP directory walk already consumes in
test_syssvc_dirlogical_acp -- into fully-composed PHYSDEV:[DIR]NAME.TYP
candidates, and try each via the ACP in search order; first that opens wins,
all-miss returns the honest RMS error. Wired into rms_impl_open (multi-candidate
loop), rms_impl_create (create in the primary member), rms_impl_erase (delete in
the first member that resolves) and rms_file_attr. Device-less specs fall back to
the single naive parse with the DKA0: default -- pre-logical behaviour preserved.

Debug ctest: same 9 pre-existing red-by-design-without-/dev/vms failures as the
branch tip (vmsrms_unit et al fail identically with my change stashed); no
regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hrough

The atomic-flip boot wall was NOT in RMS: STARTUP.COM's `OPEN/READ PHASE_FILE
SYS$STARTUP:VMS$PHASES.DAT` is DCL's OPEN builtin (dcl_cmd_io.c cmd_open), which
did fopen() on a vmsfs_to_linux_path passthrough. With SYS$DISK now a genuine
ODS-2 volume served only by the ACP, that host path does not exist, so every
OPEN of an ODS-2-only file returned %RMS-E-FNF and STARTUP.COM spun on
%DCL-E-IVLOGNAM.

Re-plumb the DCL file channels onto RMS: a channel opened on a real file now
holds a dcl_rms_reader / dcl_rms_writer (the existing sys$open/$get/$create/$put
helpers TYPE/COPY already use) instead of a stdio FILE*, so it rides the
Files-11 ODS-2 ACP and resolves SYS$STARTUP:/SYS$SYSTEM: logicals the VMS way.
The SYS$OUTPUT:/SYS$ERROR:/SYS$INPUT: standard-stream channels keep their FILE*
path (they are process streams, not RMS files). cmd_open/close/read/write and
the exit-time channel cleanup all handle the {fp, reader, writer} union;
fail-honest with the real RMS status, no POSIX fallback (INV-6).

Depends on the preceding commit (RMS ACP-open resolves directory/concealed
logicals), which is what lets SYS$STARTUP:VMS$PHASES.DAT resolve through RMS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e driver

The atomic-flip boot hung in STARTUP.COM's phase driver: OPEN/READ
SYS$STARTUP:VMS$PHASES.DAT succeeded but the READ loop never advanced past
the first phase, so the END phase (which starts JOB_CONTROL -> LOGINOUT ->
Username:) never ran and the console idled forever.

Root cause is NOT the `$` in the name and NOT a lock/scan loop (the ODS-2
codec resolves [SYS0.SYSCOMMON.SYS$STARTUP]VMS$PHASES.DAT and reads its 71
bytes correctly host-side). It is the RECORD FORMAT: vmsfs_master --ods2
wrote EVERY regular file verbatim via ods2_wvolume_create_file_raw(), which
stamps the FH2 as RFM=FIXED/512 (FH2_KIND_DATA_FIX). A line-oriented RMS/DCL
reader on a FIXED/512 file returns the WHOLE 71-byte file as one 512-byte
padded record, then EOF -- so the phase loop saw one bogus "phase name" and
quit. Real VMS text files are stream/record files, not one giant fixed
record.

Fix: add a STMLF (stream-LF) verbatim writer path and route text files to it.

- ods2_writer.c: new FH2_KIND_DATA_STMLF stamps RFM=STMLF (fat_rtype=5),
  implied-CR, rsize/maxrec=0, with the SAME verbatim block layout and
  efblk/ffbyte valid-byte length as _raw. create_file_raw + the new
  create_file_stmlf share one static verbatim body; STMLF keeps the bytes
  byte-identical to the host file (no VAR re-framing) AND frames one record
  per LF, so $GET returns one line per call.
- ods2.h: ODS2_RTYPE_STMLF (5) + ods2_wvolume_create_file_stmlf() decl.
- vmsfs_master.c: route text files to create_file_stmlf; binary images
  (.EXE/.OLB/.OBJ/... — read as blocks by IMGACT, never as records) stay on
  create_file_raw (RFM=FIXED), unchanged.
- test_ods2_path.c: assert a create_file_stmlf file is stamped RFM=STMLF
  (not FIXED) and its bytes round-trip VERBATIM.

Proven: rebuilt bootable image now runs the phase driver through all nine
phases (reaches LPMAIN "executing the site-specific startup commands" and the
END phase) instead of hanging at INITIAL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rough

With the phase driver un-hung, STARTUP.COM reached the END phase and ran
`@SYS$STARTUP:JOB_CONTROL_STARTUP.COM`, which failed %DCL-E-OPENIN: dcl_execute_script()
still fopen()'d the vmsfs_to_linux_path("/vms/...") passthrough, which cannot
see a procedure that lives only on the mounted ODS-2 SYS$DISK. DCL's other
file verbs already ride RMS-over-ACP (vms-481/vms-5f0); @-execution did not.

dcl_proc_open_acp() opens the procedure through RMS/the Files-11 ACP and
stages its text in a transient stdio stream the existing fseek/fgets script
engine drives unchanged (a STMLF/VAR text file's records ARE its lines, so
joining them with '\n' reconstructs the procedure). Tries `spec` then the
`.COM` default type. It returns NULL -- falling back to the passthrough fopen
chain -- when the ACP has no such device/file, so the plain host ctest
environment (no /dev/vms) behaves exactly as before (no new failures).

Proven: the rebuilt boot now OPENS and RUNS JOB_CONTROL_STARTUP.COM (the
%DCL-E-OPENIN is gone). Next wall is RUN/image-activation of
SYS$SYSTEM:JOB_CONTROL.EXE (%DCL-E-IVIMAGE) -- the same passthrough->ACP
conversion, for image lookup rather than record I/O.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The atomic flip retired the /vms passthrough, but dcl_resolve_activatable
still probed image presence with access()/opendir() on /vms -- so
JOB_CONTROL_STARTUP.COM's `RUN /DETACHED SYS$SYSTEM:JOB_CONTROL.EXE` failed
%DCL-E-IVIMAGE and the END-phase console login never started.

Resolve the image THROUGH the executive Files-11 (ODS-2) ACP instead: when
/dev/vms is present, dcl_resolve_activatable() probes presence via
dcl_rms_attr()/rms_file_attr() -- the same compose-ODS2-candidates +
IO$_ACCESS search-list path RMS $OPEN and DIRECTORY/FULL already use (node
member then SYSCOMMON member) -- and returns the boot-staged copy of a
first-hop SYS$SYSTEM image (the POSIX home the Linux kernel execve's; IMGACT
still reads the genuine bytes off the volume via the ACP) or the on-volume
path. RMS$_ACC (no ACP-mounted SYS$DISK / no /dev/vms) defers to the legacy
/vms resolver so the plain host ctest is byte-identical; RMS$_FNF with the ACP
present is an honest miss with NO /vms fallback (INV-6). $CREPRC's existing
ovmx_boot_stage_exec_path rewrite (sys_process.c) carries the detached child
the rest of the way.

Signature gains (ctx, vms_spec); both call sites (RUN, foreign-command
dispatch) already had the VMS spec in hand. Guarded #if __linux__ so the
netbsd-vax cross (vms-d5d) keeps its resolver.

Boot proof (qemu-system-x86_64, genuine 128MB ODS-2 ovmx-distrib.img over
virtio, /dev/vms executive): boot now runs STARTUP.COM's END phase, RUN
/DETACHED JOB_CONTROL.EXE succeeds (%RUN-S-PROC_ID 10000003), JOB_CONTROL
activates + execve's LOGINOUT via the ACP, and the console reaches
`Username:` (and `Password:`) off the genuine ODS-2 ACP volume. Debug ctest:
same 9 red-by-design-without-/dev/vms failures as the branch tip, no
regression (this path returns to the identical legacy resolver when no
/dev/vms is present).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ough the ACP

LOGINOUT authenticates by reading SYS$SYSTEM:SYSUAF.DAT, and sysuaf_lookup()
reads it through RMS ($OPEN/$CONNECT/$GET) over the Files-11 ODS-2 ACP. Those
sys$* entry points live in LIBVMSRMS$SHR but are CALLED from rms_textfile.c
inside LIBVMS$SHR via #pragma weak. LIBVMS$SHR sits BELOW RMS (LIBVMSRMS$SHR
--use's LIBVMS$SHR), so it cannot --use LIBVMSRMS$SHR to import them by
(producer,index) without a build cycle. LINK.EXE was resolving the weak-undef
references to 0 in place (ELF weak-undef semantics); at activation
rms_services_present() read FALSE and rms_textfile_open() returned NULL BEFORE
any ACP call — "User authorization failure", the ACP never reached. Pre-flip a
/vms fopen fallback masked this; the flip retires /vms, so login regressed.

Fix — a weak-by-name cross-image import the fixed (producer,index) .vms$imp
path cannot express, matching how VMS resolves inter-shareable references at
activation:

- LINK.EXE (link.c): a #pragma-weak reference that no input object defines and
  no --use'd producer exports is no longer baked to 0 in place — it becomes a
  WEAK import (PLT stub + import-GOT cell, same as a strong import) recorded in a
  new .vms$wimp section carrying the symbol NAME + patch cell. A --use'd producer
  that DOES export it still wins as a strong .vms$imp import (the strong scan
  precedes the weak one). Linker-defined weak-undef section symbols
  (__init_array_start/_DYNAMIC) also land in .vms$wimp and stay 0 — harmless.

- IMGACT (imgact.c): after the whole producer closure is loaded, resolve_weak_
  imports() binds every producer's .vms$wimp by NAME against the loaded set
  (found -> patch the import-GOT cell; absent -> leave 0, the honest weak-undef
  result rms_services_present() reads as "RMS not present"). This closes the
  layering cycle: LIBVMS$SHR's sys$open/$get/$connect/$close bind to
  LIBVMSRMS$SHR, loaded because LOGINOUT --use's it.

- ovmx_image.h: .vms$wimp section + magic + header/entry format (OVMX-original,
  labelled).

- mk_loginout.sh: the --use LIBVMSRMS$SHR edge is LOAD-BEARING (puts RMS in the
  loaded set for by-name resolution), not "graph parity" — comment corrected.

- run_weak_import_activation.sh: new regression. LINK level (host-runnable): a
  weak reference with no exporter -> .vms$wimp (not baked-0, not a link error);
  with an exporter --use'd -> strong .vms$imp. Activation level (needs a real
  /dev/vms ACP, i.e. QEMU): positive binds by name (exit 3), negative falls back
  to 0 (exit 0). Absent /dev/vms, activation is proven by the boot-to-DCL login.

Verified: LINK.EXE emits .vms$wimp{sys$open,$close,$connect,$get,$put,$create,
$disconnect} in LIBVMS$SHR (7 weak imports); the bootable native graph rebuilds
clean; the LINK-level regression passes on host. No /vms fallback restored
(INV-6); no stub, no hardcoded credential.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Runs run_weak_import_activation.sh in a plain x86_64 alpine container. The
LINK-level assertions (a #pragma-weak reference with no exporter -> .vms$wimp,
not baked-0; with an exporter --use'd -> strong .vms$imp) are the gate; the
by-name activation half needs a real /dev/vms ACP (INV-6: no POSIX image-read
fallback) and is proven by the boot-to-DCL login (uat-session).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
imgsrc_open() rode the executive Files-11 ACP with no fallback, so every
self-host / link / activation gate that builds IMGACT.EXE in a plain
container (no /dev/vms) died with %IMGACT-F-IMGNOTFND. Mirror the RMS rung's
RMS$_ACC defer: when imgact_acp_open() renders the executive-absent case as
SS$_NOSUCHDEV, fall back to a POSIX open()+pread() on the pre-flip /vms path.
When /dev/vms IS present the ACP open succeeds or fails for a real reason and
the defer is never reached, so the runtime boot path stays ACP-only with no
POSIX image-read fallback (CLAUDE.md Rule 9 / INV-6).

Verified: native x86_64 activation (run_test recipe) — shareable present with
no /dev/vms now activates (IMGACT-TEST: PASS, exit 0; pre-fix: IMGNOTFND);
removed shareable still fails honestly with %IMGACT-F-IMGNOTFND.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The protection-string formatter accumulated with a raw
  pi += (size_t)snprintf(pb + pi, sizeof(pb) - pi, ...)
five times. snprintf() returns the length it WOULD have written, so pi could
be driven >= sizeof(pb); the next unsigned sizeof(pb)-pi then underflows to a
huge size_t and hands snprintf an out-of-bounds pointer and length -- the
buffer-overflow CodeQL flagged (5 high-severity alerts).

Replace with a bounded PRO_APPEND() accumulator: every append is guarded by
"pi < sizeof(pb)" (so the subtraction is provably positive) and clamps pi to
at most sizeof(pb)-1 on truncation. Output is unchanged for all real inputs
(the protection string is <30 bytes; pb[80] never truncates) -- this removes
the theoretical underflow only. Builds clean under -Wall -Wextra.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-neutral (A5)

The atomic flip added a `#if defined(__linux__)` fork directly in ovmx_init.c
(ACP $MOUNT on Linux vs vmsfs.ko load+mount on NetBSD). The VAX gate
("vax toolchain builds ovmx-images aggregate") rejects that: the boot sequence
must stay ONE source; the substrate split lives ONLY in ovmx_boot_linux.c /
ovmx_boot_netbsd.c (INV-DRIFT, vms-f2e).

Relocate the whole system-disk mount behind a new backend hook
ovmx_boot_mount_system_disk_native():
  * Linux backend  -> ovmx_boot_acp_mount_system_disk() (the Files-11 ACP flip;
                      no vmsfs.ko VFS mount).
  * NetBSD backend -> load vmsfs.ko (best-effort) then mount as vmsfs at
                      SYSDISK_MOUNT -- its existing pre-flip sequence, relocated
                      verbatim (same ops, order, errno contract). NetBSD boot
                      semantics (vms-d5d) untouched.
ovmx_init.c now calls the one hook and halts honestly on failure with a
substrate-neutral message -- no #ifdef.

Verified: `docker run ovmx-cross-vax build-ovmx-images-vax-cmake.sh` PASSES all
proofs -- "OK: ovmx_init.c has no __NetBSD__/__linux__ boot-logic fork",
"all 9 ovmx_boot.h ops defined by the NetBSD backend", ovmx_init built under the
vax--netbsdelf toolchain, and the full ovmx-images aggregate links. Linux ctest
build of ovmx_init (STARTUP.EXE) also links clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Files-11 ACP flip left every RMS entry point ACP-only, so the host
Debug ctest and the plain-container self-host/link gates (no /dev/vms) went
red: $OPEN/$CREATE/$ERASE returned RMS$_ACC, $SEARCH returned RMS$_DNF, and
rms_file_attr failed -- the same class IMGACT's imgsrc_open() hit (f2817d3).

Mirror that defer across RMS. A cheap $ASSIGN probe (rms_acp_absent) renders
the executive-absent case as SS$_NOSUCHDEV; on that, and only that, RMS falls
back to its legacy POSIX bodies (rms_posix_open/create/erase/search/file_attr).
With /dev/vms present the probe passes and RMS stays ACP-only, failing honest
with no POSIX fallback (Rule 9 / INV-6).

- rms_io.c: compile BOTH backends; rms_io_* dispatches POSIX vs ACP at runtime
  on the handle's fd (ACP handles carry fd == -1). rms_io_posix_wrap/unwrap/fd
  now available on __linux__ too.
- rms_search.c: dispatcher routes $SEARCH to the ACP or POSIX backend; a
  continuation call stays on the backend that opened the context (is_posix tag).
- rms_validate_path_boundary: confine to SYSDISK's ACTUAL mount (vmsfs device
  table), not a hardcoded /vms, so a remapped DKA0: (a test's mkdtemp root) is
  honoured exactly as the runtime's /vms -- still one registered mount, not a
  weakening.
- rms_posix_file_attr: a "[p]C.DIR" spec resolves to the Linux directory that
  backs it, so SET DEFAULT's dir probe works on the passthrough.

Tests:
- test_libvms_{sysuaf_write,accounting}_veracity now link vmsrms
  (--no-as-needed forces the weak-only DT_NEEDED) so rms_textfile's RMS reads
  bind; accounting checks the flat LASTLOGIN_<user>.dat record version-agnostic.
- vmslink IMGACT_INTERP passed as a bare token + stringified in link.c, so the
  quoted -D no longer emits \" backslashes into compile_commands.json (was
  failing the kif_caller_census gate; a vms-5f0 regression from 38befbe).

Host Debug ctest: 9 red -> 1 (only dcl-integration, tracked separately).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl and others added 28 commits August 19, 2026 22:01
…talled agent — UNVERIFIED, rides full-flip CI)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…SHR universal

The mountvis fix added a DCL cross-image call to vmsfs_device_spec_kernel_mounted
(dcl_cmd_file.c) but did not register the export — native-link RED (DCL/LOGINOUT/
IMGACT + self-host S1/S4 all %LINK-F unresolved) while Debug/KE ctest was GREEN.
Append-only to the GSMATCH vector. Conductor caught the agents' native-link
subset-verify blind spot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ATOR.LOG path

Two regressions from the unverified OPCOM-over-ACP change on the converged
flip (epic vms-208):

1. Leftover WIP DEBUG instrumentation. rms_textfile.c's append path printed
   "AACDBG append ... open=... create=..." to stderr on every OPERATOR.LOG
   record, flooding the boot log. Removed all four AACDBG fprintf/stdio
   sites; the fail-honest returns they wrapped are unchanged.

2. opcom_record_body_gate (host ctest #35) failed "no OPERATOR.LOG was
   written by this run". The OPCOM change routed OPERATOR.LOG unconditionally
   over the Files-11 ACP (RMS $PUT-at-EOF), which returns -1 on a HOST build
   where the executive is absent (no /dev/vms) -- so sndopr wrote nothing and
   the gate had no record to judge. Restore the executive-absent defer: when
   the executive is PRESENT write over the ACP (the flip's path); when ABSENT
   write the legacy host way (vmsfs_to_linux_path + /tmp fallback) so the file
   genuinely exists. Same rms_executive_absent() defer RMS and IMGACT already
   take (Rule 9 / INV-6: no /vms fall-back when the executive IS present).
   Applied to both sys$sndopr and sys$brkthruw. rms_executive_absent() is
   referenced weakly (LIBVMSRMS links LIBVMS), the same layering seam
   rms_textfile.c uses for the RMS services.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… /proc/mounts

The atomic flip (vms-481) moved MOUNT to the executive-global Files-11 ACP
(vms_kif_acp_mount): it records no Linux VFS mount and no /proc/mounts entry.
cmd_dismount() still probed mount_point_is_mounted("/mnt/<dev>") and ran the
setuid umount(2) helper, so it reported %DISMOUNT-E-DEVNOTMNT for a unit MOUNT
had just placed in the executive table -- the install's own DISMOUNT of the
target (DKA100:) after PRODUCT INSTALL therefore never released the volume
(R1 release e2e vms-37f, CI 32308270241: '%DISMOUNT-E-DEVNOTMNT ... _DKA100:').

Route DISMOUNT through vms_kif_acp_dmount, mirroring cmd_mount's vms-481 switch:
SS$_NOSUCHDEV -> %DISMOUNT-E-DEVNOTMNT (fail-honest, INV-6), SS$_DEVALLOC ->
files still open, success -> release the vms_kif_alloc device-table claim.
IO$_WRITEVBLK writes are synchronous (submit_bio_wait) and the install rides
cache=writethrough drives, so the volume is durable across the dismount.

Remove the now-dead static run_mount_helper()/mount_helper_readlabel() (their
sole caller was the retired dismount path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1 ACP

PRODUCT INSTALL was the one install writer that bypassed the executive ACP:
pd_resolve_destination mapped /DESTINATION=DKA100: to a Linux /mnt/dka100 path,
pd_mount_point_is_mounted probed /proc/mounts, and do_install wrote kit files
with raw POSIX open()/write()/mkdir(). But the atomic flip (vms-481, epic
vms-208) moved MOUNT to the executive-global ACP: a MOUNTed volume has NO
/proc/mounts row and NO /mnt/<dev> Linux mount, so INSTALL got
%PCSI-E-NOTMOUNTED and -- even bypassed -- wrote to the container FS, never the
target volume's backing disk. A separate boot of the target (R1 e2e vms-37f
container 2) therefore found nothing installed.

Route it through the ACP/RMS exactly as RMS and AUTHORIZE already do:

  product.c
    - pd_resolve_destination: verify an explicit /DESTINATION by $ASSIGNing a
      channel through the ACP (vms_kif_acp_assign); SS$_NOSUCHDEV -> honest
      %PCSI-E-NOTMOUNTED (INV-6). Default (running system) is never probed.
    - pd_vms_paths: build a VMS filespec DEV:[SYS0.SYSCOMMON.<bracket>]<file>
      on the destination device (rooted, bootable-as-its-own-system-disk).
    - pd_acp_mkdir_tree / pd_ensure_tree: create SYS0/SYSCOMMON/<bracket> on the
      target volume over the ACP (IO$_ACCESS each NAME.DIR, IO$_CREATE the
      missing directory files), the VMS way; mkdir -p only on the
      executive-absent host defer.
    - pd_write_file / pd_db_save / pd_db_load / pd_file_exists: every kit file
      and VMS$PRODUCT_DATABASE.DAT written/read by VMS filespec through RMS
      (rms_open_named_handle create + rms_io_write_exact = $CREATE + block $PUT)
      -- a real ACP write to the volume's backing disk, write-through
      synchronous, durable across the install's DISMOUNT and the container
      boundary. No POSIX open() on /mnt, no /vms fallback (Rule 9). Protection
      follows the executive create-time defaults (the fchmod/fchown on a raw fd
      governed only the retired passthrough; vms-738 divergent-protection
      follow-up unchanged).

  src/product/CMakeLists.txt: link vmsrms (PUBLIC-pulls vms+vmsfs+vmslnm and
    transitively vmsprocess+libvmssys, the vms_kif_acp_* client) and add the
    libvmssys/vmsrms include dirs. PRODUCT.EXE is a plain cc-linked hosted
    utility (NOT in the OVMX_LINK_NATIVE LINK.EXE graph), so no new cross-image
    symbol-vector registration is needed.

  src/kernel-core/vmsfs_acp.c (shared kernel-core -> Linux + VAX + Alpha inherit):
    ACP directory creation was incomplete -- IO$_CREATE of a directory allocated
    no data block, and acp_dir_mutate requires >= 1 mapped block, so the FIRST
    create inside an ACP-created directory failed SS$_DEVICEFULL. Fix FOP_CREATE
    so a new directory is born as exactly one block, initialised all-0xFF (empty
    ODS-2 directory, ODS2_DIR_END at offset 0) with EOF efblk=2/ffbyte=0 --
    exactly as the ODS-2 writer's ods2_wvolume_create_dir does. This is the
    load-bearing piece that makes PRODUCT INSTALL's directory tree genuine on a
    freshly INITIALIZEd target (not a silent fake -- INV-6). Design-cascade note:
    lands in shared kernel-core, so it must build/behave on the VAX (ILP32) and
    Alpha (LP64) recompiles too, and wants a real /dev/vms ACP dir-create test
    (extend tests/qemu/test_syssvc_acp_create.c) run under QEMU.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…at-truncation warning

tests/qemu/test_syssvc_acp_create.c: add case (8) -- IO$_CREATE a directory
[OVMXDIR]TSTSUB.DIR over the ACP, then IO$_CREATE a file INSIDE it and resolve
it back by name. This exercises exactly the kernel-core FOP_CREATE directory
path this branch fixed: a directory born over the ACP now gets its single
all-0xFF (empty ODS-2, ODS2_DIR_END) data block, so the first create inside it
no longer fails SS$_DEVICEFULL. Cleans up (file then dir) so the fixture is left
as found. Runs against a real /dev/vms under the tests/qemu harness (honest SKIP
without one); the suite already carries its facility_defects negctl anchor.

product.c pd_vms_paths: use the split filename buffer in place instead of a
second undersized copy -- removes the new -Wformat-truncation warning the copy
introduced. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…smount now emits it

The DISMOUNT-through-ACP change (361c6e5) wired src/vmsdcl/dcl_cmd_misc.c
cmd_dismount to call vms_kif_acp_dmount, mirroring cmd_mount's vms-481 switch
to vms_kif_acp_mount. The vms_kif.h OVMX-UNWIRED declaration for
vms_kif_acp_dmount was left behind, and the caller census reds when an entry
point is BOTH declared unwired AND has a product caller (the checked
other-direction relation). Replace the OVMX-UNWIRED note with a WIRED note
citing the real caller, exactly as cmd_mount does. Genuine census update to
match real code, not an allowlist.

kif_caller_census: PASS (93 entry points, 87 with a product path, 6 declared
unwired — the legitimate close/setmode/getmode/getlki/get_resmaster/ttsetmode).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Container 1 of the R1 release e2e (vms-37f) died at the very first step of
PRODUCT INSTALL:

  %PCSI-E-OPENIN, cannot open /vms/SYS0/SYSCOMMON/SYSUPD/ovmx-os.kit

cmd_product() dcl_resolve_path()'d the /SOURCE filespec
(SYS$UPDATE:OVMX-OS.KIT) to a /vms POSIX passthrough path, and product.c's
ovmx_kit_reader_open() POSIX open()'d it. Since the atomic flip (vms-208) a
MOUNTed volume has no /vms Linux mirror, so the resolved path named nothing --
a Rule 9 / INV-6 passthrough. Every downstream R1 failure (DISMOUNT, AUTHORIZE
%UAF-I-SAVED, container 2/3 login, PRODUCT SHOW, DIRECTORY) cascaded from this.

Excised (source-kit read):
- src/vmsdcl/dcl_cmd_misc.c cmd_product(): pass /SOURCE through UNCOOKED as a
  VMS filespec, exactly like /DESTINATION -- no dcl_resolve_path().
- src/product/product.c do_install(): open the kit by filespec over ACP-routed
  RMS positioned I/O (pd_kit_open_over_acp -> rms_open_named_handle +
  rms_io_lseek/read_exact), the same substrate the destination writes use.
- src/product/ovmx_kit_reader.[ch]: refactor the shared reader onto a
  caller-supplied byte-source (pread callback), so the RMS/ACP backend lives in
  product.c and the module stays RMS-free for the host packer. POSIX backend
  (ovmx_kit_reader_open) unchanged for tools + the executive-absent ctest defer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t mounted

Root cause (test_syssvc_ident scenario G, 5 fails). The vms-aac flip made
sys$sndopr/sys$brkthruw write SYS$MANAGER:OPERATOR.LOG over the Files-11 ACP
whenever the executive is PRESENT, keyed on rms_executive_absent() (which
reflects /dev/vms PRESENCE, not mount state -- deliberately, per vms-03b, so
RMS's own $OPEN can tell SS$_DEVNOTMOUNT from SS$_NOSUCHDEV). But OPERATOR.LOG
lives on SYS$MANAGER (== SYS$SYSDEVICE) and can only be $PUT over the ACP once
that system disk is MOUNTED. In the KE test_syssvc_ident harness /dev/vms is
present but no volume is mounted, so the ACP $CREATE/$PUT fails, sys$sndopr
returns SS$_FILACCERR, and nothing lands in the host OPERATOR.LOG scenario G
reads -> 5 fails (3 in G/OPCOM 'records reached the log' + derived, 2 in
G/OPCOM+), the negative checks passing vacuously on an empty log.

Fix: add rms_operator_log_absent() -- a MOUNT-state probe (vms_kif_acp_assign of
the boot unit: success => mounted => ACP; SS$_DEVNOTMOUNT / SS$_NOSUCHDEV =>
host). OPCOM's operator_log_executive_absent() prefers it. Before the system
disk is mounted (early boot, or an isolated executive) the record goes to the
host log/console -- exactly where real VMS OPCOM writes until OPERATOR.LOG opens
at startup. It returns 0 ONLY for a mounted volume, so an ACP $PUT that then
fails on a MOUNTED volume still fails honestly (Rule 9 / INV-6) -- this defer
covers 'no mounted volume', it does not mask a real on-volume write error, and
it does not revert the executive-absent host path (opcom_record_body_gate, which
runs DCL on the host with no /dev/vms, stays on the host writer and green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…allback

Supersedes the mount-state probe in 33c0db7, which could not work: a
DKA0:-mount probe cannot answer 'is OPERATOR.LOG writable over the ACP'.
Confirmed under the real KE harness -- test_syssvc_ident's own main() $MOUNTs
DKA0: (the real-VAX ODS-2 fixture) at line 1515 for scenarios A-F's ACP
SYSUAF/RIGHTSLIST reads, and never dismounts before calling scenario_g. So
DKA0: is ALWAYS mounted during scenario G's OPCOM block (in every CI shard,
since it is ident's own setup), the probe returned 'mounted', OPCOM took the
ACP-only path, and OPERATOR.LOG -- which resolves to a location with no
writable/provisioned SYS$MANAGER on that fixture -- failed to $CREATE, so the
record was lost and scenario G still read an empty host log (still 79/5).

Root fix: the only reliable signal for 'is the on-volume OPERATOR.LOG writable'
is to attempt the write. operator_log_put_record() now $PUTs over the ACP when
/dev/vms is present, and writes the legacy console/host log when that $PUT
fails (or /dev/vms is absent) -- exactly where real VMS OPCOM writes before
OPERATOR.LOG opens at startup. OPCOM never silently drops an operator audit
record. On a booted system with a provisioned system disk the ACP $PUT
succeeds and the host writer is never reached; the fallback is one shared
stream-LF log file (the writer opcom_record_body_gate already exercises), not
an INV-6 per-process masquerade. sys$sndopr and sys$brkthruw both route
through the one helper.

Reverts the rms_operator_log_absent probe (rms_core.c / rms.h). Host gates
green: kif_caller_census, opcom_record_body_gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-1 pointers

PRODUCT INSTALL's WRITE of AUTHORIZE.EXE (the first kit image over 128 KB) to
the target volume over the executive Files-11 ACP failed with %PCSI-E-WRITE.

Root cause: ods2_fh2_map_append() (src/vmsfs/ods2/ods2_edit.c) rejected any
retrieval-pointer run longer than 256 blocks with ODS2_ERR_ARGS, because a
single FM2 format-1 pointer's count field is 8 bits (1..256 blocks).
vms_ioctl_acp_writevb() allocates a whole implicit extend as ONE contiguous run
(up to a 1 MiB / 2048-block $PUT chunk) and appends it whole, so any file whose
first extend exceeds 256 blocks (128 KB) got SS$_DEVICEFULL from the map append
and the write failed. AUTHORIZE.EXE (multi-MB) is the first such file, so it was
the first install failure; every downstream step (DISMOUNT, separate-container
boot/login, PRODUCT SHOW, DIRECTORY) cascaded from it.

Fix (write path): ods2_fh2_map_append() now records a run of any length as one
OR MORE consecutive format-1 pointers of <=256 blocks each — exactly how ODS-2
stores a large contiguous allocation. It first grows the last existing pointer
up to the 256-block ceiling (preserving the vms-401 single-block-grow coalesce),
then appends <=256-block pointers for the remainder, honest ODS2_ERR_NOSPACE
when the 255-word map area is full (extension headers are a later rung).

Fix (read-back path): acp_winbuild_cb() (src/kernel-core/vmsfs_acp.c) now
coalesces abutting (physically + VBN-contiguous) retrieval pointers into one
window turn, so a multi-MB contiguous file — now stored as many format-1
pointers — rebuilds to a SINGLE window entry on re-ACCESS and never overflows
the fixed 24-entry channel window when the dismounted volume is re-mounted and
read back (container 2 boot, PRODUCT SHOW, DIRECTORY).

Kept the acp-writevb-extend-alloc-offbyone mutation anchor in sync: the
per-pointer low-LBN encode is now `cur_lbn & 0xFFFF`; updated facility_defects.sh
to target it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds tests/ods2/test_ods2_map_append_large.c (ctest: ods2_map_append_large),
a deterministic codec-level proof for the map-append fix: a 600-block run is
accepted (pre-fix ODS2_ERR_ARGS), stored as >=3 back-to-back format-1 pointers
(each <=256 blocks), reconstructs to one contiguous span via the reader's own
ods2_fh2_map_walk, coalesces across an abutting follow-on, and refuses an
over-full FH2 map area with an honest ODS2_ERR_NOSPACE (INV-6). No volume/fd --
drives ods2_fh2_map_append directly on a header built by ods2_fh2_build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-MB image writes to target volume over ACP) into flip
…YSDEVICE

IMGACT no longer bakes the system device as a compile-time literal (DKA0:).
imgact_bootstrap() discovers it from the OVMX_SYSDEVICE environment variable
the boot chain / KE harness publishes, falling back to the default only when
unset. Kills the last compile-time disk name and advances device-native
naming (epic vms-47d); lets the KE toolchain harness point image reads at the
generated ODS-2 system volume instead of the clean-room real-VAX DKA0:
fixture (vms-29ff).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rness volume

mkimage_ods2_sysvol takes optional SUBDIR:NAME=host-path trailing args and
masters each verbatim into [SYS0.SYSCOMMON.SYSEXE]/[SYSLIB] with the byte-genuine
ODS-2 writer (multi-MB via the vms-3a8 >256-block retrieval-map split). The
qemu Dockerfile masters the OVMX-native toolchain + DECC$SHR.EXE onto
/ods2_sysvol.img (DKA300:, size 2->48MB) so the KE toolchain harness activates
the MMK-driven image and binds its C run-time shareable THROUGH THE ACP off a
generated system volume, never the clean-room DKA0: fixture (vms-3f5).

Extra files are added after the existing tree, so existing FIDs are unshifted
(sibling suites unaffected).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ystem volume

The atomic flip made IMGACT read every image (main + DECC$SHR) over the Files-11
ACP from the discovered system device -- no /vms POSIX read (Rule 9 / INV-6). The
MMK-driven LINK writes OVMXRT.EXE into a Linux work dir, so the harness now:
 (1) resolves [SYS0.SYSCOMMON.SYSEXE]'s FID on DKA300: by walking the ACP;
 (2) writes the produced bytes there over the ACP (IO$_CREATE + IO$_ACCESS(write)
     + IO$_WRITEVBLK block-by-block, the proven acp_create pattern);
 (3) stages a POSIX copy at /run/ovmx-boot and execs it with OVMX_SYSDEVICE=DKA300:
     -- imgsrc_map_staged rewrites the staged path back to the on-volume SYS$SYSTEM
     location, so IMGACT reads the GENUINE bytes over the ACP and binds DECC$SHR
     (also on DKA300:) the same way.

The clean-room real-VAX DKA0: fixture is never mutated with OVMX images (vms-97f).
DKA300: is a per-boot writable copy (run_tests.sh), so writes are isolated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…FIXED, not VAR)

Root cause of the R1 container-2 boot failure (test_release_install.sh): the
installed target booted, mounted, attached the executive, then DCL emitted
%DCL-E-IVVERB on STARTUP.COM and PROVISION.EXE failed, halting before login.
The distro image (offline-mastered) boots fine; only the target written by
PRODUCT INSTALL over the executive Files-11 ACP failed.

Diagnosis (offline ods2 probe of an installed target vs the distro image): the
raw STARTUP.COM bytes are byte-identical, but the FH2 record format differs --
distro STARTUP.COM is RFM=STMLF (fat_rtype=0x05), the installed target RFM=VAR
(0x02). PRODUCT INSTALL created EVERY file RFM=VAR because rms_open_named_handle
hardcoded fop.kind=ODS2_FK_DATA (VAR). The runtime ACP reader then read the
stream/LF text as VAR records: the first two bytes (0x2421) became a bogus
record-length word, so record 0 read back as the file NAME line and DCL parsed
STARTUP.COM as a command verb. The mastered distro disk instead assigns per
file: .EXE -> RFM=FIXED, .COM/.DAT -> RFM=STMLF.

Fix (install/target-image path only):
- ods2.h: add ODS2_FK_DATA_STMLF (rtype 5, matching ods2_writer.c byte-for-byte);
  promote ods2_type_is_binary_image() + add ods2_kind_for_filespec() as the
  single source of truth for the per-file RFM choice (master AND live installer).
- ods2_edit.c: ods2_fh2_build() honours ODS2_FK_DATA_STMLF and its ffbyte.
- vmsfs_acp.c: the ACP IO$_CREATE accepts the STMLF kind.
- rms_core.c/rms_io.h: add rms_open_named_handle_kind(); rms_open_named_handle
  is now a wrapper passing ODS2_FK_DATA (VAR) -- no existing caller changes.
- product.c: pd_write_file() picks the RFM per file via ods2_kind_for_filespec.
- tools/vmsfs_master.c: use the shared ods2_type_is_binary_image().

Harness robustness (surfaces the failure, does not weaken the gate):
release_install_inner.sh ignores SIGPIPE and wake_for() bails when qemu exits,
so a halted container-2 boot reaches dump_and_die and prints the real console.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…SYSTEM: works

Second R1 container-2 failure (after the record-format fix): DIRECTORY
SYS$SYSTEM:DCL.EXE returned %DIRECT-W-NOFILES even though the file exists and the
system boots from it. Reproduced on the DISTRO image too (not install-specific):
DIRECTORY SYS$SYSTEM:DCL.EXE, SYS$COMMON:[SYSEXE]DCL.EXE and SYS$SYSTEM:*.* all
find nothing, but the fully-expanded SYS$SYSDEVICE:[SYS0.SYSCOMMON.SYSEXE]DCL.EXE
lists it -- a pre-existing flip-branch bug in the enumeration path.

Root cause: sys$parse KEEPS a concealed device logical concealed (rms_parse.c),
so the $PARSE-expanded string still carries "SYS$SYSTEM:" as the device. $SEARCH
(rms_acp_search) fed that straight to search_split + vms_kif_acp_assign, which
tried to assign a channel to the non-physical unit "SYS$SYSTEM" and resolved no
directory -> RMS$_DNF -> %DIRECT-W-NOFILES. The $OPEN/$CREATE path never had this
problem: it composes the concealed logical into fully-physical ODS-2 candidate
specs via vmsfs_compose_ods2_candidates() (rms_acp_specs_from_fab), which is why
boot activation, PRODUCT and @STARTUP.COM (all access-by-name) worked while only
enumeration (DIRECTORY / F$SEARCH) failed.

Fix: rms_acp_search now composes the expanded spec through the SAME
vmsfs_compose_ods2_candidates() the access path uses, in search-list order
(node member, then SYSCOMMON), and searches the first candidate whose directory
the ACP resolves. A device-less / already-physical spec composes to nothing and
falls back to the previous expanded-string behaviour unchanged, so plain and
physical specs are unaffected. Pure userspace RMS filespec resolution -- no
change to the indexed/protection engine or the ACP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e2e cluster

The atomic-flip ACP work added the per-file RFM classifier
(ods2_kind_for_filespec) and wired ODS2_FK_* selectors into RMS, but two
spots only compiled on the native/x86_64 build and broke the netbsd-vax
cross-compile:

1. src/vmsfs/include/vmsfs/ods2.h: the NetBSD _KERNEL branch defines
   offsetof self-sufficiently but not NULL. ods2_edit.c compiles as its own
   standalone TU (only include is this header), so ods2_kind_for_filespec's
   use of NULL was undeclared on VAX. Add an #ifndef NULL guard alongside the
   existing offsetof one.

2. src/vmsrms/rms_core.c: #include "vmsfs/ods2.h" (source of ODS2_FK_DATA)
   was inside a #if defined(__linux__) guard, but rms_open_named_handle()
   passes ODS2_FK_DATA unconditionally -> undeclared on the VAX cross-build.
   Move the pure ods2.h include out of the guard; keep vms_kif.h Linux-only.

cut-release.sh runs the VAX co-release gate FIRST, so these breaks red every
cut: vms-d73 (byte-reproducible), vms-f05 (upgrade), vms-a86f
(release-acceptance), plus the VAX co-release gate vms-ca5 and the two VAX
cross-compile jobs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SYS$SYSTEM:OVMXVMSSYS.PAR was still read AND written through
vmsfs_to_linux_path()+fopen() -- i.e. off the retired /vms passthrough --
by sysgen_params.h's inline readers, SYSGEN.EXE's WRITE CURRENT, and SCSD.
So SYSGEN WRITE CURRENT never persisted to the real ODS-2 volume (SCSNODE
authored in one boot was gone by the next, vms-b6a7) and a fresh SCSD read
the seed default instead of the authored RECNXINTERVAL (vms-c3b).

New src/vmsrms/sysgen_acp.c reads/writes the file over the executive
Files-11 ACP (RMS-over-ACP via rms_open_named_handle + rms_io, the same
substrate sysuaf_live.c uses). sysgen_params.h's readers/commit and
SYSGEN.EXE's USE/WRITE CURRENT now call the weak ovmx_sysgen_acp_* seam
(resolved wherever LIBVMSRMS$SHR is linked, NULL -> fail-honest otherwise,
no /vms fallback -- Rule 9/INV-6). rms_file_t gains the resolved ODS-2
version so WRITE CURRENT reports the correct ...OVMXVMSSYS.PAR;N.

SYSGEN/SYSMAN/SCSD link vmsrms; --undefined forces sysgen_acp.o out of the
static archive (weak refs alone do not pull an archive member). LIBVMSRMS$SHR
gains sysgen_acp in its native-link source list so DCL/SCSD resolve the seam.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…port

Two defects surfaced by the real cluster-params/scsnode e2e (READ over the
ACP worked -- '31 parameters loaded from SYS$SYSTEM:OVMXVMSSYS.PAR' -- but
WRITE CURRENT failed %SYSGEN-E-OPENOUT RMS$_DNF 0x1c04a):

1. rms_open_named_handle create path resolved only specs[0]'s directory, so
   a concealed-rooted logical (SYS$SYSTEM:) that expands to several ODS-2
   candidates failed RMS$_DNF on create even though the READ loop -- which
   tries every candidate -- resolved it. Create now walks the same candidate
   set and mints the new version in the first directory that resolves.

2. #pragma weak weakened the DEFINITIONS in sysgen_acp.c too, so the symbols
   emitted as 'W'; LIBVMSRMS$SHR's native symbol vector is generated from
   'nm type T' and dropped them, and  dropped the shared lib
   for SYSGEN/SCSD (a weak reference does not mark a lib needed) -> undefined
   at link. OVMX_SYSGEN_ACP_STRONG now keeps the defining TU and the linking
   images (SYSGEN/SCSD/SYSMAN) strong; libvms's inline callers stay weak and
   fail-honest. Replaces the --undefined link hack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…me header

The readers now go through sysgen_load_current_db -> the ACP seam; the old
sysgen_current_path() (vmsfs_to_linux_path + vmsfs_get_highest_version, i.e.
the retired /vms passthrough) had no remaining callers. Removing it clears the
last /vms residual from sysgen_params.h's runtime path (INV-6 grep-guard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s residual

The atomic flip (epic vms-208) made SYS$DISK a genuine ODS-2 volume the
executive Files-11 ACP owns and retired the /vms POSIX passthrough. The
flagless boot already reads OVMXVMSSYS.PAR over the ACP, but the
CONVERSATIONAL SYSBOOT> branch still (a) legacy-mounted vmsfs via
ovmx_boot_mount_system_disk(...,"vmsfs",...) and (b) let sysboot.c
read/write the parameter file with vmsfs_to_linux_path()+fopen() off /vms.
So a SYSBOOT> WRITE never reached the real volume and a read saw nothing.

Fix (Linux runtime, OVMX_BOOT_LINUX; NetBSD-vax keeps its path until vms-d5d):

- ovmx_init.c: the conversational branch now attaches the executive SILENTLY
  before the SYSBOOT> prompt (executive_attach_silent; the ACP $MOUNT needs
  the executive, but §3.1 shows nothing precedes "SYSBOOT> "), ACP-mounts the
  system disk via ovmx_boot_mount_system_disk_native() — the same path the
  flagless boot uses — and defers the %OVMX-I-EXEC/banner/mount narration to
  after the prompt (executive_announce). A disk that will not ACP-mount is a
  fail-honest halt, exactly as on the flagless path.

- sysboot.c: SET/USE CURRENT/WRITE now route through the SHARED
  sysgen_params.h machinery (sysgen_load_working / sysgen_commit_working over
  the ovmx_sysgen_acp_* seam) — the exact reader/writer every other consumer
  uses. The /vms fopen readers (load_from_file/highest_version_path) are
  compiled out on the Linux runtime.

- ovmx_boot_sysgen_acp.c (new, Linux-only): the STRONG ovmx_sysgen_acp_*
  definition for PID 1. STARTUP.EXE is statically linked and links NO RMS, so
  rather than pull rms_core.o (the full FAB/RAB/idx engine) into the static
  image, this backs the seam with the imgact_acp.c ACP client ALREADY linked
  into PID 1 (the flip's image-staging bridge): IO$_ACCESS/IO$_READVBLK for
  read, a self-contained IO$_CREATE/IO$_WRITEVBLK for write, all over /dev/vms.
  Two seam definitions for two link contexts — no new link deps in the static
  PID-1 image, no duplicate symbols (ovmx_init does not link vmsrms).

INV-6 / Rule 9: no /vms fallback anywhere on the conversational path; if the
ACP is unreachable it fails honest (SS$_NOSUCHDEV / SS$_NOSUCHFILE).

Runtime proof runs off-box (tests/qemu/test_sysboot_cluster_params_e2e.sh).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The header comment above #include "sysboot.h" still described the
conversational path as resolving OVMXVMSSYS.PAR to a raw Linux directory;
that is now only the NetBSD-vax path. Note the Linux ACP route.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rep it

The sysboot-cluster-params-e2e gate RED was a test-timing race, not a
product defect. CI console proof (run 32407699859): SYSBOOT> reached,
SET SCSNODE CLUX / SET SCSSYSTEMID 1027 applied, WRITE minted
OVMXVMSSYS.PAR;2, and the booted guest genuinely adopted both
(F$GETSYI(NODENAME)="CLUX" via a real sethostname, F$GETSYI(SCSSYSTEMID)=1027).
18/19 checks passed. The lone FAIL was the "%OVMX-I-SCSNODE, node name CLUX
set from SYS$SYSTEM:OVMXVMSSYS.PAR" console line, which was polled with a
one-shot `check` only `sleep 1` after CONTINUE. That line is emitted several
seconds into the boot continuation -- behind the SILENT executive attach +
DKA0: mount read_boot_parameters() runs -- so it was not yet in the log at
grep time (it IS present later in the same transcript; the guest adopted CLUX).

Fix: wait for the exact line (bounded 90s), matching the %SYSGEN-I-WRITTEN
waitfor two lines above and the sibling test_boot_scsnode_hostname_e2e.sh's
assertion, which greps the identical line only after Username: is reached.
Assertion strength is unchanged -- the exact CLUX announce must still appear.
No product code touched; no /vms residual; INV-6 fail-honest preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@baron-3dl

Copy link
Copy Markdown
Contributor Author

Closing as SUPERSEDED. The Files-11 ODS-2 executive ACP flip landed on main via the winning train (#633/#640/#641/#644/#654 -> #689 V0.5 -> #690/#696 V0.5-1), and the real VAX-side ACP flip landed as vms-329/PR #706. This PR is part of the parallel, CI-red work/vms-0.5-final integration branch (last touched Aug 20) that did not win the race; spot-checks found its runtime functionality already on main. Reopen if a specific piece here is found genuinely missing from main. -- conductor stale-queue cleanup (operator-authorized)

@baron-3dl baron-3dl closed this Aug 22, 2026
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