Skip to content

ci: wire QEMU kernel-executive job into CI (vms-e4d) - #3

Merged
baron-3dl merged 2 commits into
mainfrom
work/vms-e4d
Jul 29, 2026
Merged

baron-3dl merged 2 commits into
mainfrom
work/vms-e4d

Conversation

@baron-3dl

Copy link
Copy Markdown
Contributor

Summary

  • Adds kernel-executive CI job: builds tests/qemu/Dockerfile (vms.ko + vmsfs.ko against a real Ubuntu kernel) and boots QEMU on serial console via run_tests.sh, running the full test_kmod_* suite against a real /dev/vms. Before this job existed, no CI job ever loaded vms.ko.
  • Adds kernel-executive-negative-control: proves the gate can actually fail by building the same harness with NEGATIVE_CONTROL=1 (insmod of vms.ko skipped) and asserting the run goes red for the right reason -- greps for the "cannot open /dev/vms" failure text and the exact "3 suites passed, 7 suites failed" split, paired with a positive assertion that vmsfs.ko (independent of vms.ko) still passes.
  • Draft PR opened specifically so ci.yml's pull_request: branches: [main] trigger fires these two new jobs (and everything else in the workflow) on GitHub's ubuntu-latest (x86_64) runners, which have never compiled src/kernel/ before.

Why draft

Opened to trigger CI on x86_64 for verification (vms-e4d rework, epic vms-6b8). Not requesting merge review yet -- watching the run to confirm the x86_64 leg of run_tests.sh (qemu-system-x86_64, TCG only, hard-coded 120s TIMEOUT, no /dev/kvm on GH runners) actually passes; will fix if it doesn't.

Test plan

  • Local podman build+run of tests/qemu/Dockerfile (positive): exit 0, "10 suites passed, 0 suites failed"
  • Local podman build+run with NEGATIVE_CONTROL=1: exit 1, "3 suites passed, 7 suites failed", "cannot open /dev/vms", vmsfs still PASS
  • CI kernel-executive job green on GH x86_64 runner
  • CI kernel-executive-negative-control job green on GH x86_64 runner

baron-3dl and others added 2 commits July 28, 2026 23:21
No CI job ever loaded vms.ko: persistent-boot runs QEMU inside Docker
(no /dev/vms), and src/kernel/ is excluded even from static analysis.
Every executive facility that couldn't be tested defaulted to a
per-process userspace fake reporting success.

Adds kernel-executive: builds tests/qemu/Dockerfile (vms.ko + vmsfs.ko
against a real Ubuntu kernel) and boots QEMU on the serial console via
tests/qemu/run_tests.sh, running the existing test_kmod_* suite (ENQ/
CONVERT/DEQ round-trips, AST delivery, event-flag clusters, privilege
checks, cross-process lock arbitration, vmsfs) against a real /dev/vms.

Adds kernel-executive-negative-control: proves the gate can actually
fail. Dockerfile gains a NEGATIVE_CONTROL build arg that patches
init.sh to skip insmod of vms.ko while everything else stays real; the
job asserts the harness goes red (honest "cannot open /dev/vms"
failure, no fake fallback) and fails itself if the harness incorrectly
passes with the executive absent.

Verified directly on this host (real 6.8.0-136-generic kernel + headers,
qemu-system-aarch64, outside Docker): built vms.ko + vmsfs.ko, booted
QEMU, ran all 8 test_kmod_* programs against a real /dev/vms — 10
suites passed, 0 failed. Then patched init.sh to skip the insmod and
reran: 3 suites passed (vmsfs-only, no executive dependency), 7 failed
with "cannot open /dev/vms" -- confirmed exit 1. Same Dockerfile/sed
logic, run outside the container for speed; the CI jobs run the
identical path through Docker.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nzero exit (vms-e4d rework)

Adversarial veracity review (C1) found the negative-control job only
checked `docker run ... ; then exit 1; else PASS`, so a QEMU-missing
error, a boot panic, run_tests.sh's 120s timeout, or a runner OOM would
all make the job go green while proving nothing about the executive.

Now the job captures the run output and greps for the actual reason:
"cannot open /dev/vms", the exact "3 suites passed, 7 suites failed"
split (which also proves the harness reached its own FINAL RESULTS
accounting, ruling out timeout/panic paths that never print it), and
a paired POSITIVE assertion that vmsfs.ko (independent of vms.ko) still
passes -- so the job can't go green against a harness that fails
indiscriminately instead of isolating the executive dependency.

Verified locally with podman against tests/qemu/Dockerfile (unchanged):
  podman build -f tests/qemu/Dockerfile -t ovmx-ktest-e4d:pos .   -> exit 0
  podman run --rm ovmx-ktest-e4d:pos                              -> exit 0, "10 suites passed, 0 suites failed"
  podman build --build-arg NEGATIVE_CONTROL=1 -t ovmx-ktest-e4d:neg . -> exit 0
  podman run --rm ovmx-ktest-e4d:neg                              -> exit 1, "3 suites passed, 7 suites failed",
                                                                       "cannot open /dev/vms" x4,
                                                                       "PASS: vmsfs.ko loaded, filesystem registered"
All three grep assertions verified against the captured negative-control
output; the FINAL-RESULTS-split assertion verified to NOT match the
positive-control output (sanity check the string is discriminating).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread .github/workflows/ci.yml
Comment on lines +170 to +200
name: Kernel Executive (vms.ko via /dev/vms, QEMU)
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build kernel executive test image
run: docker build -f tests/qemu/Dockerfile -t ovmx-ktest:latest .

- name: Boot QEMU, insmod vms.ko, run executive assertions against /dev/vms
run: docker run --rm ovmx-ktest:latest

# -----------------------------------------------------------------------
# Job 3c: Kernel Executive — negative control (vms-e4d)
#
# Proves the kernel-executive gate above can actually fail. Builds the SAME
# harness with --build-arg NEGATIVE_CONTROL=1, which patches init.sh to skip
# insmod of vms.ko (simulating the executive being absent) while everything
# else — building vms.ko itself, loading vmsfs.ko, the test binaries — stays
# real. Every test that opens /dev/vms then fails honestly ("cannot open
# /dev/vms", the same SS$_NOSUCHDEV path src/libvms/syssvc/sys_lock.c already
# takes) instead of silently passing. run_tests.sh exits 1 in that case, so
# THIS job inverts the check: it PASSES only if the container run fails,
# and FAILS (catching a regression in the harness itself) if the container
# run unexpectedly succeeds while the executive is supposed to be absent.
# This is what keeps "a CI job that cannot fail is decoration" from
# regressing silently — it re-proves the gate can trip on every push.
# -----------------------------------------------------------------------
kernel-executive-negative-control:
@baron-3dl
baron-3dl marked this pull request as ready for review July 29, 2026 04:21
@baron-3dl
baron-3dl merged commit 69e58a0 into main Jul 29, 2026
35 checks passed
baron-3dl added a commit that referenced this pull request Jul 30, 2026
…g (vms-1d9)

Round 6. Three blocking defects from the round-5 verdict, each closed and each
proven by its own MINIMAL mutation that trips that property and no other. All
runs are real podman builds + real QEMU boots on this host (aarch64, TCG, no
KVM); tools/replay_ci_kernel_executive.py executes the ci.yml assertion blocks
VERBATIM out of the YAML against the captured output, so a local proof cannot
drift from what CI runs.

1. THE GENERALIZATION IS NO LONGER FAKE.
   tests/qemu/Dockerfile dispatches BY PATTERN everywhere a name was once
   literal: `COPY tests/qemu/test_*.c` (was narrowed to test_kmod_*.c, so a
   future non-kmod source was dropped), a case-based build dispatch, the
   aggregate `--target qemu_syssvc_tests`, and a glob cp of every staged
   test_syssvc_* binary. A `|| exit 1` was added to the gcc loop -- without it
   a compile failure only broke that iteration and RUN exited with the LAST
   iteration's status, so a suite that stopped compiling vanished silently.
   The staging step now FAILS THE IMAGE BUILD if zero test_syssvc_* binaries
   were staged.
   PROOF: added a second suite (test_syssvc_evt.c) with ZERO Dockerfile edits
   -> built, staged, RUN ("=== SUITE test_syssvc_evt rc=0 ==="), positive job
   green at 13 derived suites, negative-control job green at 3/12. Then
   narrowed the cp back to the literal test_syssvc_lock: the harness still
   printed "ALL KERNEL MODULE TESTS PASSED" and exited 0, and the positive CI
   job went RED -- "test_syssvc_evt: NEVER RAN (no verdict line)". Throwaway
   suite removed.

2. THE POSITIVE JOB NOW HAS A GATE, NOT JUST AN EXIT CODE (vms-d2d).
   It derives the expected suite set from `ls tests/qemu/test_*.c` at CI time
   and asserts each suite's own "=== SUITE <name> rc= ===" verdict, plus a
   suite-count FLOOR of 12 for the case the derived set cannot see: a source
   deleted outright.
   PROOF: deleted tests/qemu/test_kmod_ast.c. Harness exit 0, "ALL KERNEL
   MODULE TESTS PASSED", 13 suites passed / 0 failed -- and the positive job
   went RED: "only 11 suite sources under tests/qemu (expected at least 12)".
   Restored.

3. THE NEGATIVE CONTROL NOW CALLS A PUBLIC sys$ ENTRY POINT AND JUDGES WHAT
   IT RETURNS, not the test's own printf.
   IMPORTANT SCOPE CORRECTION vs the dispatch: it does NOT assert
   SS$_NOSUCHDEV. vms-0ff ruled OVMX has no executive-absent state and DELETED
   sys_lock.c's per-call SS$_NOSUCHDEV returns; pinning that value would
   freeze a superseded contract into a gate -- the exact failure this epic's
   adversaries keep catching. What survives the ruling is a PROPERTY, not a
   VMS behaviour: a public sys$ entry point must never report SUCCESS when it
   did not reach the executive. The test asserts the odd/even success bit, an
   empty lock ID, and prints the raw status for the record; CI pins the
   suite's rc to exactly 77.
   PROOF: injected a fabricated success into do_enq and sys$deq (SS$_NORMAL +
   lock ID 0x1234 when the executive was unreachable). All four device-absent
   assertions FAILED, rc 77 -> 1, and the negative-control job went RED naming
   the cause -- while FINAL RESULTS stayed BYTE-IDENTICAL to the clean tree
   ("3 suites passed, 11 suites failed"), i.e. the tally pin this replaces
   would have stayed green. Same mutation left the POSITIVE job green (12/12),
   confirming it trips one property and not the others. Restored.

ALSO FIXED, found while proving #3: test_syssvc_lock could HANG the whole VM.
The child's post-release sys$enqw blocks in the kernel, and
src/kernel/vms_lock.c's enq_wait_sync re-arms on every signal wake without
returning to user mode -- so a child-side alarm(20) is swallowed. Measured: an
unreleased lock sat until run_tests.sh's 120s QEMU timeout, every later suite
never ran, and CI saw an unattributable timeout. The bound now lives in the
PARENT (poll-based read_bounded + WNOHANG reap), which is not blocked. With
sys$deq stubbed, the suite now fails in 20s with a named line and the harness
still reaches its own accounting (13 passed / 1 failed).

DELIBERATELY NOT DONE, and why:
 - tests/qemu/CMakeLists.txt's add_test() is REMOVED. It reported Skipped in
   100% of environments where ctest runs and was never invoked in the one
   environment where it can pass (init.sh execs the binary directly), so it
   never executed as a passing assertion anywhere. Rule 10: a permanently-
   skipped test is a failing test, and a comment does not discharge it. No
   coverage is lost -- add_executable keeps it in the default `all` target, so
   it still breaks the host build if it stops compiling, and the QEMU job runs
   and gates it. Host ctest: 40 tests, 40 passed, 0 skipped.
 - src/libvms/include/lksdef.h is DROPPED and sys_lock.c is untouched. The
   LKSB has no VMS-published byte layout (the oracle's STARLET.MLB has no
   $LKSB macro), so per Rule 8 a shared header is an OVMX design choice
   needing operator sign-off. The test declares its own LKSB storage, which is
   what OpenVMS callers do anyway.
 - SS$_NOSUCHDEV 2680-vs-2312, lckdef.h's nine wrong flag bits: untouched,
   separately tracked.
baron-3dl added a commit that referenced this pull request Jul 30, 2026
… point against a real /dev/vms (vms-1d9) (#15)

* tests/qemu: exercise the public sys$ lock API against real /dev/vms (vms-1d9)

Phase 0.5 hard barrier: the merged Kernel Executive CI job (vms-e4d) only
COPYs src/kernel/ + tests/qemu/ into its initramfs, so every test drives
/dev/vms with raw ioctls. An adversary proved that reverting a userspace
syssvc file (src/libvms/syssvc/sys_event.c) to its pre-change stub left
the harness byte-identical green -- every Phase 1/3 item is userspace
wiring the harness cannot see at all.

Adds test_syssvc_lock, statically linked against the REAL src/libvms
(musl, same OVMX_STATIC mode distro/Dockerfile.bootable already uses) and
built into the initramfs alongside the raw-ioctl test_kmod_* programs. It
calls the public sys$enq/sys$enqw/sys$deq entry points across a real
fork()'d second process and, empirically (podman build+run against real
QEMU/vms.ko):

  - GREEN with production sys_lock.c: 11 suites passed, 0 failed.
  - RED after reverting sys$enq/sys$enqw to an always-succeeds stub (same
    defect shape as the sys_event.c regression): test_syssvc_lock's
    cross-process NOQUEUE-denial and post-release-grant assertions fail
    (10 suites passed, 1 failed) while every test_kmod_* stays green --
    proving the ioctl tests are structurally blind to this class of bug
    and the new test is not.
  - GREEN again after restoring sys_lock.c.

Also:
  - src/libvms/include/lksdef.h: promotes sys_lock.c's private LKSB
    struct to a public header (zero behavior change) -- external callers
    had no way to build the lksb parameter sys$enq/sys$enqw/sys$deq
    require.
  - tests/qemu/CMakeLists.txt: builds test_syssvc_lock against real
    /dev/vms when present; ctest SKIP (exit 77), never a fake PASS, when
    it is not (every dev/CI container -- Rule 9, Docker is not a runtime).
  - .github/workflows/ci.yml: updates the kernel-executive negative-
    control job's exact suite-count assertion (3 passed/7 failed -> 3
    passed/8 failed), empirically re-measured against a real
    NEGATIVE_CONTROL=1 build+run -- test_syssvc_lock also depends on
    /dev/vms and joins the "fails honestly when absent" bucket.

Found and flagged, not fixed (out of this item's scope): src/libvms/include/lckdef.h
duplicates starlet.h's LCK$M_* flag constants with DIFFERENT, stale
values (e.g. LCK$M_NOQUEUE 0x8 vs. starlet.h's oracle-pinned 0x4) --
see the file-header comment in test_syssvc_lock.c and this item's
returned findings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* tests/qemu: fix vms-1d9 round-2 adversarial findings (round 3)

Rebased onto current origin/main (past vms-e4d) and fixed the three
merge-blockers an adversary found against round 2, all verified against
real podman build+QEMU runs (never just code-read):

B1 - THE CAPABILITY WAS NOT GENERAL. tests/qemu/Dockerfile named exactly
one binary twice (`--target test_syssvc_lock`, `cp .../test_syssvc_lock`),
so the next test_syssvc_*.c a Phase 1/3 item adds would build, ctest-SKIP
fine locally, and never reach the QEMU initramfs. Fixed:
  - tests/qemu/CMakeLists.txt now globs test_syssvc_*.c and registers each
    automatically via qemu_syssvc_add_test(), collecting every target name
    into a GLOBAL property and exposing a single `qemu_syssvc_tests` custom
    target that depends on all of them.
  - tests/qemu/Dockerfile builds `--target qemu_syssvc_tests` (not a named
    binary) and copies build-static/bin/test_syssvc_* by glob into the
    initramfs.
  - Proved generality empirically: added a throwaway test_syssvc_dummy.c
    with NO Dockerfile/CMakeLists.txt edit, podman-built, and confirmed it
    ran inside QEMU ("test_syssvc_dummy: 1 passed, 0 failed", FINAL RESULTS
    12/0). Removed the dummy and reran -- back to 11/0, matching the new
    ci.yml assertion below. init.sh's existing `/tests/test_syssvc_*` glob
    already handled the run side; only the build/copy side was hardcoded.
  - Added a suite-count assertion to the POSITIVE kernel-executive CI job
    (previously only the negative-control job pinned a count), so a test
    that silently stops being built/staged/run can no longer stay green.

B2 - THE NO-SILENT-FALLBACK PROOF WAS CIRCULAR. test_syssvc_lock bailed at
its own vms_kif_open() bootstrap and exited SKIP(77) before any sys$ call
was made, so sys_lock.c's SS$_NOSUCHDEV return path (do_enq/sys$deq) was
never actually exercised -- constraint #2 was satisfied by code reading.
Fixed: when bootstrap fails, the test now calls the PUBLIC sys$enqw and
sys$deq entry points directly (vms_kif_open() is idempotent on failure, so
this drives the real ensure_kif_open()-fails branch in sys_lock.c) and
CHECKs the returned status AND the LKSB's own status field both equal
SS$_NOSUCHDEV. A failed check now returns exit 1 (real FAIL), not a masked
77. Proved the gate can go red: injected a defect in do_enq() (fake
SS$_NORMAL success instead of SS$_NOSUCHDEV when /dev/vms is absent),
rebuilt+ran the negative-control image, watched the new assertions FAIL
("test_syssvc_lock: 1 passed, 2 failed"), then reverted and reran green.

B3 - lksdef.h WAS PRESENTED AS VMS-AUTHENTIC. Rewrote the header comment
per CLAUDE.md Rule 8: explicitly labeled an OVMX design choice, not a VMS-
published layout, citing the oracle finding that SYS$LIBRARY:STARLET.MLB
has no $LKSB macro at all (%LIBRAR-W-NOMTCHFOU) -- there is nothing
authentic to pin the byte layout against.

Also (low priority, honesty over fixing): documented in
tests/qemu/CMakeLists.txt that the ctest registration of test_syssvc_lock
SKIPs in 100% of environments where ctest runs, and is invoked directly by
init.sh (not through ctest) inside QEMU -- it buys build-graph inclusion
and an honest SKIP, not coverage, despite appearing in the ctest listing.

Verified via podman (docker is absent on this host; podman reproduces CI
exactly):
  - Positive job: FINAL RESULTS 11 suites passed, 0 suites failed.
  - Negative-control job: FINAL RESULTS 3 suites passed, 8 suites failed;
    new no-silent-fallback CHECKs all PASS (SS$_NOSUCHDEV asserted, not a
    string this program authored).
  - Injected-defect run: new CHECKs FAIL as expected, proving the gate can
    trip; reverted and reran green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* tests/qemu: fix vms-1d9 round-4 regressions (F1 dockerignore, F2 suite-count floor)

F1: Dockerfile, distro/Dockerfile.bootable, and tests/qemu/Dockerfile all
`COPY . <dest>` the full build context with no .dockerignore, so any local
build-*/ tree, .git history, or stray artifact in the working copy lands in
the image byte for byte -- slow, fat, and a disclosure risk. Reproduced from
this exact working copy (build-ci/, build-docker/, build-test/,
build-verify/, .git, docs/, tracking/, third-party/ all present on disk).
Added a repo-root .dockerignore excluding VCS/agent-state/historical-doc
directories verified (by grep across all CMakeLists.txt and all three
Dockerfiles) to be unread by any build step. Confirmed via `podman build`
+ exec that /src/repo no longer contains build-ci, .git, docs, tracking, or
third-party, and that all three Dockerfiles (root, bootable, qemu) still
build and smoke-test clean.

F2: the round-3 kernel-executive CI job hard-pinned
'11 suites passed, 0 suites failed', so the very next item that legitimately
adds test_syssvc_event.c would raise the true count to 12 and turn CI red
for succeeding -- defeating the glob-based generality vms-1d9 exists to
build. Replaced the exact pin with three checks that only go red when a
suite is REMOVED or FAILS, never when one is ADDED: zero-failures, a floor
of >=11 passed, and presence of every named suite's init.sh header line.

Proved both directions against real QEMU runs (not just regex review):
  - Removed tests/qemu/test_kmod_access.c, rebuilt, ran in QEMU for real:
    output showed '10 suites passed, 0 suites failed'; new check goes RED
    (floor check) and independently RED (named-suite check, verified with
    the count artificially padded back to 11 to isolate that layer).
  - Restored test_kmod_access.c; added a genuine throwaway
    test_syssvc_throwaway.c (deleted before this commit), rebuilt with zero
    Dockerfile/CMakeLists edits, ran in QEMU for real: output showed
    '12 suites passed, 0 suites failed'; new check stays GREEN.
  - Rebuilt the final tree (no throwaway file) and reran in QEMU: baseline
    '11 suites passed, 0 suites failed' unchanged, new check GREEN.
  - Reran the negative-control image (NEGATIVE_CONTROL=1): unchanged
    '3 suites passed, 8 suites failed', RC=1 -- untouched by this change.

Everything else the round-3->4 adversarial review found (SS$_NOSUCHDEV
2680 vs oracle 2312, lckdef.h bit-value drift, zero production callers of
vms_kif_register(), the QEMU gate's blindness to src/vmsdcl, only the lock
manager reachable) is pre-existing debt, already filed separately, and is
untouched here.

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

* tests/qemu: gate the silent-fallback path and fix round-4 regressions (vms-1d9)

Round 4 fixed two findings and introduced four. This undoes that damage and
closes the one gap that was never gated. Every claim below is proven by a real
podman build + QEMU boot, not by inspection.

G1 .dockerignore no longer excludes CLAUDE.md. Round 4's exclusion broke this
   epic's OWN Rule 9 standing gate inside every image built from the repo root:
   tests/integration/test_runtime_target.sh greps CLAUDE.md, and
   `ctest -R runtime_target_gate` went red in the image while CI stayed green
   (CI only ran ctest on the host checkout) -- a silently broken guardrail.
   Now passes in-image.

G2 .dockerignore no longer excludes third-party/, which src/imgact/test/
   run_tcc_{native,rms,object_native,selfhost}.sh and src/vmslink/mk_tcc.sh
   hard-require. The whole list is narrowed to non-source state only, and it is
   now validated by RUNNING the suite inside the resulting image rather than by
   grepping the build files -- the method that missed both of the above.

G3 The suite gate is strong in both directions with nothing maintained by hand.
   Round 3 pinned an exact tally (red on a legitimate addition); round 4
   replaced it with a floor plus a hand-maintained name list whose own comment
   said it is not updated on addition, leaving every future suite unprotected.
   init.sh now prints a machine-readable per-suite verdict carrying the
   binary's real exit status, and CI derives the expected suite set from
   `ls tests/qemu/test_*.c`. Suite ADDED -> green; suite DROPPED -> red;
   suite FAILS -> red. A monotone floor on the number of suite SOURCES catches
   outright deletion, which a derived set cannot see.

G4 The negative-control job gets the same treatment. Its exact 3/8 tally pin
   turned red when a legitimate test_syssvc_*.c was added (proven: 3/9).

G5 The decisive one. A real silent fallback in sys_lock.c (SS$_NORMAL instead
   of SS$_NOSUCHDEV when /dev/vms is absent, in do_enq and sys$deq) left the
   FINAL RESULTS accounting BYTE-IDENTICAL -- 3 passed / 8 failed, RC 1 -- and
   every assertion in both jobs still passed. Cause: init.sh funnels exit 77
   (honest skip) and exit 1 (assertion failure) into one counter, and CI pinned
   only the total, so a per-process fake that reports success was invisible to
   the entire gate. The negative control now asserts that every test_syssvc_*
   suite exits exactly 77, which holds only when its device-absent
   SS$_NOSUCHDEV assertions all passed. Re-injecting that exact fallback now
   turns the job RED; reverting restores green (identical image SHA).

Unchanged: test_syssvc_lock.c, tests/qemu/CMakeLists.txt, sys_lock.c, lksdef.h.
The proven core -- a QEMU test linking the real libvms catching a userspace
defect all eight raw-ioctl suites miss -- was not touched, and was re-verified:
deleting one kstat_to_ss() line turns test_syssvc_lock red while all 8
test_kmod_* suites stay rc=0.

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

* tests/qemu: fail loudly, not silently, on an empty derived suite set (vms-1d9)

GitHub runs `run:` steps under `bash -e`, and `grep -c` exits 1 on an empty
set, so an empty derived suite list would have aborted the step with no
diagnostic instead of reaching the explicit source-count floor below it.

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

* tests/qemu: make the userspace harness generic, gated and self-proving (vms-1d9)

Round 6. Three blocking defects from the round-5 verdict, each closed and each
proven by its own MINIMAL mutation that trips that property and no other. All
runs are real podman builds + real QEMU boots on this host (aarch64, TCG, no
KVM); tools/replay_ci_kernel_executive.py executes the ci.yml assertion blocks
VERBATIM out of the YAML against the captured output, so a local proof cannot
drift from what CI runs.

1. THE GENERALIZATION IS NO LONGER FAKE.
   tests/qemu/Dockerfile dispatches BY PATTERN everywhere a name was once
   literal: `COPY tests/qemu/test_*.c` (was narrowed to test_kmod_*.c, so a
   future non-kmod source was dropped), a case-based build dispatch, the
   aggregate `--target qemu_syssvc_tests`, and a glob cp of every staged
   test_syssvc_* binary. A `|| exit 1` was added to the gcc loop -- without it
   a compile failure only broke that iteration and RUN exited with the LAST
   iteration's status, so a suite that stopped compiling vanished silently.
   The staging step now FAILS THE IMAGE BUILD if zero test_syssvc_* binaries
   were staged.
   PROOF: added a second suite (test_syssvc_evt.c) with ZERO Dockerfile edits
   -> built, staged, RUN ("=== SUITE test_syssvc_evt rc=0 ==="), positive job
   green at 13 derived suites, negative-control job green at 3/12. Then
   narrowed the cp back to the literal test_syssvc_lock: the harness still
   printed "ALL KERNEL MODULE TESTS PASSED" and exited 0, and the positive CI
   job went RED -- "test_syssvc_evt: NEVER RAN (no verdict line)". Throwaway
   suite removed.

2. THE POSITIVE JOB NOW HAS A GATE, NOT JUST AN EXIT CODE (vms-d2d).
   It derives the expected suite set from `ls tests/qemu/test_*.c` at CI time
   and asserts each suite's own "=== SUITE <name> rc= ===" verdict, plus a
   suite-count FLOOR of 12 for the case the derived set cannot see: a source
   deleted outright.
   PROOF: deleted tests/qemu/test_kmod_ast.c. Harness exit 0, "ALL KERNEL
   MODULE TESTS PASSED", 13 suites passed / 0 failed -- and the positive job
   went RED: "only 11 suite sources under tests/qemu (expected at least 12)".
   Restored.

3. THE NEGATIVE CONTROL NOW CALLS A PUBLIC sys$ ENTRY POINT AND JUDGES WHAT
   IT RETURNS, not the test's own printf.
   IMPORTANT SCOPE CORRECTION vs the dispatch: it does NOT assert
   SS$_NOSUCHDEV. vms-0ff ruled OVMX has no executive-absent state and DELETED
   sys_lock.c's per-call SS$_NOSUCHDEV returns; pinning that value would
   freeze a superseded contract into a gate -- the exact failure this epic's
   adversaries keep catching. What survives the ruling is a PROPERTY, not a
   VMS behaviour: a public sys$ entry point must never report SUCCESS when it
   did not reach the executive. The test asserts the odd/even success bit, an
   empty lock ID, and prints the raw status for the record; CI pins the
   suite's rc to exactly 77.
   PROOF: injected a fabricated success into do_enq and sys$deq (SS$_NORMAL +
   lock ID 0x1234 when the executive was unreachable). All four device-absent
   assertions FAILED, rc 77 -> 1, and the negative-control job went RED naming
   the cause -- while FINAL RESULTS stayed BYTE-IDENTICAL to the clean tree
   ("3 suites passed, 11 suites failed"), i.e. the tally pin this replaces
   would have stayed green. Same mutation left the POSITIVE job green (12/12),
   confirming it trips one property and not the others. Restored.

ALSO FIXED, found while proving #3: test_syssvc_lock could HANG the whole VM.
The child's post-release sys$enqw blocks in the kernel, and
src/kernel/vms_lock.c's enq_wait_sync re-arms on every signal wake without
returning to user mode -- so a child-side alarm(20) is swallowed. Measured: an
unreleased lock sat until run_tests.sh's 120s QEMU timeout, every later suite
never ran, and CI saw an unattributable timeout. The bound now lives in the
PARENT (poll-based read_bounded + WNOHANG reap), which is not blocked. With
sys$deq stubbed, the suite now fails in 20s with a named line and the harness
still reaches its own accounting (13 passed / 1 failed).

DELIBERATELY NOT DONE, and why:
 - tests/qemu/CMakeLists.txt's add_test() is REMOVED. It reported Skipped in
   100% of environments where ctest runs and was never invoked in the one
   environment where it can pass (init.sh execs the binary directly), so it
   never executed as a passing assertion anywhere. Rule 10: a permanently-
   skipped test is a failing test, and a comment does not discharge it. No
   coverage is lost -- add_executable keeps it in the default `all` target, so
   it still breaks the host build if it stops compiling, and the QEMU job runs
   and gates it. Host ctest: 40 tests, 40 passed, 0 skipped.
 - src/libvms/include/lksdef.h is DROPPED and sys_lock.c is untouched. The
   LKSB has no VMS-published byte layout (the oracle's STARLET.MLB has no
   $LKSB macro), so per Rule 8 a shared header is an OVMX design choice
   needing operator sign-off. The test declares its own LKSB storage, which is
   what OpenVMS callers do anyway.
 - SS$_NOSUCHDEV 2680-vs-2312, lckdef.h's nine wrong flag bits: untouched,
   separately tracked.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
baron-3dl pushed a commit that referenced this pull request Aug 7, 2026
…M (vms-2156)

vms-9b7 design cascade check #3. architecture.md Boot Sequence now shows PID 1
as bootstrap-only (not SYSTEM) exec'ing SYS$SYSTEM:PROVISION.EXE, which stamps
SYSTEM identity and execs DCL.EXE on STARTUP.COM in the same process. Adds the
one-SYSUAF-format section. install-0.1.md lists PROVISION.EXE among on-disk
images past STARTUP.EXE and describes the startup-process handoff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 13, 2026
…s) — self-host spine #3 (#429)

* vms-ca9: LIBRARIAN.EXE + .OLB object-library format (self-host spine #3)

New SYS$SYSTEM: toolchain image LIBRARIAN.EXE that produces/maintains OVMX
object libraries (.OLB) which LINK.EXE consumes — the object-library half of
the OVMX-native toolchain (TCC.EXE -> LIBRARIAN.EXE -> LINK.EXE -> IMGACT.EXE).

- src/vmslink/include/ovmx_olb.h: the shared .OLB reader/writer. Per Rule 8,
  VSI does not publish the byte-level LBR .OLB layout, so OVMX defines its own
  representation and LABELS it a design choice: a standard `ar` archive of .OBJ
  (ELF) members. Header-only (static inline) so LIBRARIAN.EXE and the DCL
  LIBRARY/OBJECT path share one byte layout; GNU long-name (//) table supported
  so a stock `ar` can inspect an .OLB (used as an independent test oracle).
- src/vmslink/librarian.c: /CREATE /INSERT /DELETE /LIST /EXTRACT over .OLB,
  VMS status codes, %LIBRAR- messages. A host toolchain tool alongside LINK.EXE
  and OVMXDUMP (reads/writes .OLB/.OBJ; not itself a VMS runtime image).
- CMakeLists: install LIBRARIAN.EXE to SYS$SYSTEM:.

Design settled in docs/design-self-host-mmk-spine.md §3.

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

* vms-ca9: DCL LIBRARY/OBJECT — real .OLB object-library semantics

Promote dcl_library.c's OBJECT path from defined-only to a real object library.
An .OLB (or /OBJECT) LIBRARY command now writes/reads the ar-container format
(shared ovmx_olb.h) that LINK.EXE consumes — LIBRARY/CREATE /INSERT /LIST
/EXTRACT /DELETE — instead of the TEXT/HELP "LBRO" blob that LINK cannot read.
TEXT (.TLB) / HELP (.HLB) libraries keep the LBRO format unchanged.

Module names for OBJECT members derive from the .OBJ basename (upper-cased,
extension stripped) — an OVMX choice, since an OVMX .OBJ carries no VMS
module-name field. No new cross-image symbols (the format code is header-only
static inline), so the native DCL.EXE graph is unaffected beyond an added
include path (src/vmsdcl/CMakeLists.txt + mk_dcl.sh).

tests/dcl/test_library_object.sh: DCL writes an .OLB and the system `ar`
(independent oracle) reads it back with the inserted module.

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

* LINK.EXE: consume .OLB — toolchain core (vms-ca9)

Isolated toolchain-core edit (operator-approved 2026-08-13), separated for
review. LINK.EXE now searches an OVMX object library (.OLB) like a real object
library: it pulls ONLY the members needed to resolve currently-undefined strong
external references, iterating to a fixpoint (a pulled member may reference a
symbol another member defines) — the classic ELF archive-member selection,
keyed on member symbol tables directly (no dependence on an ar `/` symbol index).

A `.a` archive keeps its whole-archive ingestion (unchanged, still wanted for
the musl C-RTL and OVMX shareables); selection is by the `.OLB` extension.
STRONG (STB_GLOBAL) undefined refs force extraction; WEAK undefined refs do not
(matching ld). Emits %LINK-I-LIBRARY, "<lib>: N of M members pulled (selective)".

The .OLB is an OVMX-labeled `ar` container (Rule 8, docs/design-self-host-mmk-
spine.md §3, src/vmslink/include/ovmx_olb.h); the byte-level ar walk here is the
same as the existing load_archive path.

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

* vms-ca9: tests — LIBRARIAN/.OLB/LINK round-trip + native activation

End-to-end veracity for the object-library toolchain path:

- tests/toolchain/run_olb_roundtrip.sh (ctest toolchain-olb-roundtrip): LIBRARIAN
  builds an .OLB from >=2 real .OBJ; `ar` (independent oracle) confirms it; LINK
  resolves an undefined symbol by SELECTIVELY pulling the one member that defines
  it (link succeeds with NO --allow-undefined, so success proves the pull);
  negative control — a library lacking the member makes the same link FAIL;
  OVMXDUMP confirms the produced image is a valid OVMX shareable. Registered in
  the top CMakeLists (BUILD_TOOLS-gated, since it needs the host toolchain bins).
- src/imgact/test/run_olb_native.sh + CI job librarian-olb-native-x86_64:
  the full "activates and runs" proof — LIBRARIAN /CREATE, LINK.EXE --executable
  selective pull (1 of 2), then IMGACT.EXE activates the produced image and it
  RUNS (mul3(14)==42, exit 0), VMS-native (no ld/ld.so). Shares the producer-
  graph builder with run_link_native.sh.

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

* vms-ca9: wire ovmx_olb.h include into run_dcl_native.sh (DCL native-link enum)

run_dcl_native.sh compiles the DCL TUs with its OWN INCS list (not mk_dcl.sh's),
so dcl_library.c's new #include "ovmx_olb.h" failed to resolve there — breaking
the "DCL.EXE VMS-native Link + Activate" jobs (S1 aarch64 + x86_64 vms-bdf) at
compile of the 23 DCL objects. Add -I$SRC/vmslink/include to its INCS, matching
the wiring already added to CMake link_native_graph and mk_dcl.sh (the third DCL
native-link enumeration per the project gotcha).

Reproduced in the alpine x86_64 musl container: 23 DCL objects compile clean,
DCL.EXE links (exit 0) and IMGACT-activates + runs a scripted session (exit 0).

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

---------

Co-authored-by: alice <alice@workspace.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 13, 2026
#445)

Bump OVMX_PRODUCT_VERSION V0.4 → V0.4-1. 24 PRs / 32 commits since V0.4,
packed across dimensions (point release toward the 0.5 milestone):

  self-host (R7)  #409 lib$tparse · #411 CLI$ compiled-CLD · #413 sys$setddir
                  #414 lib$get_foreign · #415 sys$filescan (RTL foundation)
                  #418 parse_tables.mar→C (spine #2) · #429 LIBRARIAN.EXE+.OLB (spine #3)
                  #435 shareable-vector freeze (GSMATCH stability)
  authenticity    #421 veracity rubric (Q1 oracle-source/Q2 real-inject) · #424 30 oracle-pinned constants
                  #433 rmsdef.h 74 fabricated RMS codes → oracle
  UX/DCL/RMS      #422 SHOW CLUSTER real membership · #441 DCL per-@-level local scope
                  #442 RMS XAB dates → VMS 1858-epoch quadword
  networking      #419 virtio NIC (user-mode default + opt-in tap/bridge)
  docs            #423 clustering release train
  + swept: other threads' merged work on main since V0.4

Clustering config-authoring UX (vms-098) + its public-manual grounding gate
remain 0.5 (minor) deliverables — not triggered by this point cut.

Co-authored-by: alice <alice@workspace.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 13, 2026
…ee head must be pointer-width (#463)

MMK.EXE SIGSEGV'd in the guest before sp_open (post-parse/pre-drive),
nondeterministically and only with a real executive. Prior work (vms-b23
#462) characterized it via a SIGSEGV handler but lacked a real backtrace.

Root cause (real backtrace, this commit): a 64-bit pointer-width bug.
objects.c declares the LIB$*_TREE root cell as `static unsigned int
objtree` — 4 bytes. On the VAX a longword IS a pointer so stock MMK is
correct; on a 64-bit OVMX target that cell is too small. lib$insert_tree /
lib$lookup_tree take the head by reference and dereference it as a full
8-byte pointer, so they:
  - over-READ 8 bytes of a 4-byte global (the adjacent global's 4 bytes
    become the high half of a bogus root pointer), and
  - on insert, over-WRITE, truncating the stored root node address.
The reconstructed garbage pointer is later dereferenced -> SIGSEGV. Whether
it faults depends on address-space layout, which is exactly why it was
nondeterministic and "executive-dependent" (the executive changes the heap
layout); on the host the reconstructed pointer happened not to fault, so
MMK reached sp_open cleanly.

Pinned with a real backtrace via ASan on the host mmk_native ELF, run with
the identical input the guest capstone uses
(VMS_FOREIGN_CMD="/DESCRIPTION=OVMXB23.MMS OVMXB23.OUT"):

  ERROR: AddressSanitizer: global-buffer-overflow ... READ of size 8
    #0 lib$lookup_tree            src/libvms/rtl/lib_tree.c:108
    #1 Find_Object               tests/corpus/tier3-mmk/objects.c:102
    #2 make_objrefs              tests/corpus/tier3-mmk/parse_descrip.c:1135
    #3 parse_store               tests/corpus/tier3-mmk/parse_descrip.c:1051
    #4 act_prs                   tests/libvms/mmk_parse_tables.c:85
    ... lib$table_parse -> parse_descrip -> Read_Description
    #9 main                      tests/corpus/tier3-mmk/mmk.c:705
  0 bytes after global variable 'objtree' (size 4)

Fix: declare objtree pointer-width (`void *`), matching symbols.c's
apply_sort() `void *tree` and the LIB$ manual's quadword tree head on
64-bit architectures. After the fix ASan is clean on the same input and
MMK proceeds through the object tree to the drive (sp_open).

Clean-room (Rule 8): objects.c is stock MadGoat freeware; the one-line
width change is an OVMX portability fix, tagged inline.

Proof:
  - ASan before: global-buffer-overflow at lib_tree.c:108 (above).
  - ASan after: clean; MMK reaches the drive (same as host baseline).
  - toolchain-mmk-parse ctest: PASS (no host regression).
  - build-static (musl) mmk_native: builds clean.

Co-authored-by: alice <alice@workspace.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 14, 2026
…er negctl (#519)

VERACITY-OVERTURN (blocks vms-14f). PR #158's $GETUAI/SYSUAF parser fix was
real, but three claims shipped alongside it were hand-recited, not measured,
and are measurably false. Each correction below is DERIVED by running the
relevant parse/format over the shipped SYSUAF.DAT, not re-typed.

1. src/libvms/rtl/sysuaf.c (strtok comment): was "five of the six rows OVMX
   ships have an empty field". MEASURED 6 of 6: every shipped row carries an
   empty FLAGS field, so the strtok split dropped PRIVILEGES on ALL SIX rows
   and $GETUAI(UAI$_PRIV) returned mask 0 for every account (SYSTEM's "ALL"
   and GUEST's "TMPMBX" alike), not "nearly the whole" file.

2. src/libvms/rtl/sysuaf.c + test_syssvc_setuai.c (both-bases comment): implied
   SYSTEM's 1|4 is the unique row that reads the same octal and decimal.
   MEASURED: TWO rows do -- SYSTEM (1|4) and OPERATOR (1|6) -- because both UIC
   components are single octal digits; the four [200,20x] rows discriminate.
   Corrected in both spots and in the radix negctl's `why` (Rule 10 consistency).

3. test_syssvc_setuai.c (write-back comment): claimed a %u write of USER1's
   128|130 "reads back as octal 88|88". MEASURED 10|88: '8' is not an octal
   digit, so strtoul("128",8) stops at "12" (=10); strtoul("130",8)=88.

Unexecuted negative control: the WRITER's octal UIC formatting
(sysuaf_format_record's %o) had no dedicated control -- sysuaf-uic-radix-decimal
mutates the READER radix, and the manifest itself noted "a WRITE alone is not
reached". Scenario 4's two write-back assertions were the exact subject of the
false claim #3 yet nothing injected a writer defect to prove they have teeth.
Added sysuaf-uic-writeback-decimal: flips both %o->%u in the writer so a rewritten
record whose UIC digits differ between bases is written decimal. Scenario 4
rewrites USER1 (200|202 -> struct 128|130) and reads the row text back, so %u
reddens exactly the two "still reads 200/202" assertions (measured host-side:
%o of 128/130 = "200"/"202", %u = "128"/"130"). No other qemu suite writes via
sysuaf_format_record and reads a UIC field back, so nothing else reddens.
Anchored both assertions, added the manifest entry + inject, raised the derived
floor 97->98. manifest selftest + coverage: the new defect injects with teeth,
require_fail texts exist literally, 98>=floor 98. (The 3 remaining selftest/
coverage FAILs -- test_kmod_vmsfs_mountvis/sysgroup, test_syssvc_initialize
unanchored -- pre-exist identically on origin/main; unrelated to this item.)

No security fix or test was weakened; source changes are comment-only + a new
injected-defect control. INV-6.

Co-authored-by: alice <alice@workspace.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 14, 2026
…JBC ones (#530)

The DCL queue/submit handlers emitted invented message idents — QMANERR,
SUBMITERR, PRINTERR, ENTNOTFND (and NOENTRY/BADENTRY/NOQUNAM) — presented in
the %FACILITY-S-IDENT shape as if VMS-authentic. They are not real VMS idents.
Tier-0 authenticity (clean-room Rule 8): ground every emittable ident to public
VSI/HP OpenVMS documentation or label it OVMX-design.

Replacements (all VERIFIED, cited in docs/audit-message-idents-vms-916.md):
  queue manager unavailable  -> %JBC-E-JOBQUEDIS, system job queue manager is
                                not running
  submit/print to bad queue  -> %JBC-E-NOSUCHQUE, no such queue
  no such queue entry        -> %JBC-E-NOSUCHENT, no such entry
                                (DELETE/ENTRY renders the faithful two-line VMS
                                 chain %DELETE-W-SEARCHFAIL + -JBC-E-NOSUCHENT
                                 verbatim from the DCL Dictionary example)
  missing required parameter -> %DCL-W-INSFPRM, missing command parameters
  SHOW/SET QUEUE no-such-queue: facility corrected DCL-verb -> JBC

Two conditions have no VMS-authentic ident and are LABELLED OVMX-design under
facility OVMX so no reader mistakes them for VMS: %OVMX-E-IVENTNUM (non-numeric
entry value, rejected by the CLD parser OVMX does not reach here) and
%OVMX-E-QUESETERR (internal queue-state write fault).

The success-line idents (SUBMITTED/QUEUED/QUEMOD/MODIFIED/DELETED) diverge from
real VMS plain-text output but are gated by existing tests and are a separable
success-FORMAT change — cataloged in the audit for a coordinated follow-up, not
touched here.

Declaration repointing (task item #3): sys_msg.c's OVMX-USERSPACE declarations
already cite the live owner vms-916 (not the closed vms-5b4 — that appears only
in the shared register-header line common to every sys_*.c). The
tracking/rd-citations.tsv + tools/gen_rd_citations.py apparatus no longer exists
(torn down by operator ruling vms-dc7), so there is nothing to regenerate.

Test: tests/dcl/test_queue_messages.sh drives the three failure paths and
asserts the real JBC text is emitted with EXPECT_NOT guards on the invented
idents. Verified it fails on origin/main source and passes with this fix; the
DCL suite is 139 passed / 5 failed (the 5 are pre-existing environmental fails:
HELP library, INSTALL.EXE, SCSNODE config, companion .EXEs). The
userspace_service_register authenticity gate passes.

Requires operator sign-off (D3) to close.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 14, 2026
…arams (#550)

* vms-46c (gap #2): prove & CI-wire conversational boot for cluster params

The conversational-boot MECHANISM (SYSBOOT> halts pre-banner on the boot
flag; SHOW/SET/USE/WRITE/CONTINUE against SYS$SYSTEM:OVMXVMSSYS.PAR) landed
with vms-b81. Two gaps remained against gap #2's outcome, both closed here:

1. test_sysboot_conversational.sh (vms-b81's own proof: pre-banner halt,
   byte-shaped SHOW table, SET SCSNODE + CONTINUE, in-memory persistence
   semantics) was NEVER wired into any CI job -- an unrun test is an absent
   test (Rule 7). Wired into the persistent-boot (boot-smoke) job + the
   `boot` paths filter; job budget bumped 30->40m for the added expect run.

2. The NUMERIC cluster-param path through SYSBOOT was untested -- the
   existing proof only drives SET SCSNODE (string). New e2e
   (test_sysboot_cluster_params_e2e.sh + run wrapper + ctest reg + CI job
   sysboot-cluster-params-e2e) authors a string (SCSNODE) AND a numeric
   cluster-identity param (SCSSYSTEMID) at the SYSBOOT> prompt, WRITEs a real
   vmsfs ;2, CONTINUEs, then proves in the booted logged-in guest that
   F$GETSYI reads BOTH authored values back -- with a flagless bracket boot
   on a fresh disk showing the seeded defaults. This is the "cluster params
   authored interactively before boot" proof (docs/design-boot-faithful.md
   sec 2.2/4.2) -- the clustering relevance of conversational boot.

No source, boot goldens, or the seeded OVMXVMSSYS.PAR were touched; the
pinned faithful-boot conformance sequence is unchanged. Gaps #1/#3/#4 of the
epic remain open (separate dispatches).

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

* vms-46c gap #2: fix two never-run SYSBOOT> proof assertions to match real boot

Both boot proofs the PR wires into CI failed on genuine ASSERTION bugs, not
boot bugs. The conversational-boot mechanism and cluster-param authoring are
correct; the authored values (SCSNODE=CLUX, SCSSYSTEMID=1027) are provably in
effect in the booted guest.

1. test_sysboot_cluster_params_e2e.sh — F$GETSYI("SCSSYSTEMID") returns an
   INTEGER (unlike the string NODENAME), so DCL renders the symbol UNQUOTED
   with Hex/Octal columns: "SIDP = 1027   Hex = 00000403  Octal = ...". The
   test wrongly expected the string form 'SIDP = "1027"' / 'SIDD = "0"'. Anchor
   on the value AND its hex (0x403 == 1027, 0x0 == 0) so a wrong value cannot
   pass. CASE 1 + CASE 2 both fixed.

2. test_sysboot_conversational.sh (Boot B, vms-b81) — the "nothing precedes
   SYSBOOT>" check demanded the pre-prompt console region be byte-empty, which
   is impossible: expect's own spawn echo, SeaBIOS, "Booting from ROM", the ANSI
   clear-screen, and the substrate identity line "OVMX/Linux -- SYSKRNL" always
   precede it. That is why it never passed — it had never been RUN in CI (the
   gap this PR closes). The design's real claim (design-boot-faithful.md §3.1:
   "No banner precedes SYSBOOT>") and the oracle itself (the SRM `P00>>>`
   bootstrap block precedes SYSBOOT>) show the load-bearing proof is: no VMS
   BANNER and no executive narration precedes the prompt. Assert exactly that —
   the pre-prompt slice carries no `%OVMX-` line and no `OpenVMX Vx.x` banner —
   which still fails hard if the executive-attach line leaks before SYSBOOT>.

No source, boot goldens, or seeded OVMXVMSSYS.PAR touched.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 17, 2026
…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>
baron-3dl added a commit that referenced this pull request Aug 17, 2026
… 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>
baron-3dl added a commit that referenced this pull request Aug 21, 2026
…ructural, TCG-robust)

Run #3 (32500383365, with the 1800s wall from the prior commit) STILL
truncated shard 1 on eflag-readef: GitHub's TCG runner is 2-4x slower
than the KVM rail the ~776s estimate came from, so any fixed wall is
fragile against TCG's wide variance. Fix it at the ROOT instead.

The balloon source: nine multi-process suites (ef_mproc, eflag_mproc,
hiber_ast, mbx_{cmdresp,crossproc,wrtattn}, lnm_{crossproc,groupjob,
searchlist}) read peer tokens with read_bounded(fd, ..., PEER_TIMEOUT_MS)
== a poll() with a 20000ms (20s) safety bound. When a mutation breaks the
suite's inter-process signalling, the peer never delivers and the parent
blocks the FULL 20s -- across several suites (and several reads) that is
hundreds of seconds of pure wall-clock wait, and being wall-clock it is
TCG-INDEPENDENT, which is exactly why chasing the wall bigger did not
converge.

A poll() returns THE INSTANT the peer writes, so a pristine run never
approaches the bound -- it is a hung-peer safety net, not legit timing.
Shortening it therefore bounds only the mutation-broken case and does NOT
touch legitimate delivery (which is sub-second poll-on-write). Make
PEER_TIMEOUT_MS env-tunable in all nine suites (default 20000 unchanged
for host ctest and the positive job) and have inject_and_run.sh set
OVMX_KE_PEER_TIMEOUT_MS=4000 for the negctl full-suite run -- a ~5x cut in
the broken-peer balloon, TCG-independent. The control still reddens, just
fast. The 1800s KE_WALL_TIMEOUT is kept as belt-and-suspenders margin for
the TCG-dependent BASE run, no longer the primary fix.

No control weakened, no defect deleted, no suite dropped: every control
still reddens its suite AND the run reaches FINAL RESULTS; a genuine hang
still fails the bounded wall. Helper compiles clean under -Wall -Wextra
-Werror.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 21, 2026
…-host userland (epic vms-d0c) (#689)

* vms-5303: ACP answers IO$_CREATE / IO$_DELETE / IO$_MODIFY (file header 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>

* vms-3e8e: IMGACT activates images via IO$_ACCESS+READVBLK (ACP file access, 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>

* vms-bc7: RMS reaches files via channel + $QIO to the ACP; FAB._linux_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>

* vms-481: DCL file commands + F$ lexicals reach files via RMS/$QIO-ACP (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>

* vms-274: LOGINOUT authenticates from SYSUAF via the ACP; boot writers 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: PID-1 $MOUNTs SYS$DISK via the Files-11 ODS-2 ACP (step 2, atomic 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>

* vms-5f0: boot master emits genuine ODS-2 by default (step 3, atomic flip)

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>

* vms-5f0: wire the flip-group's new native-link symbol edges

- 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>

* vms-5f0: fix auto-merge duplicate of vms_ioctl_acp_fileop in vmsfs_acp.c

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>

* vms-5f0: ODS-2 writer emits format-2/3 FM2 pointers for files >128KB

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>

* vms-5f0: size the genuine ODS-2 boot volumes at 128MB (step 3 sizing)

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>

* vms-5f0: ODS-2 write-cache flushes when full (files may exceed WCACHE_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>

* vms-5f0: ship the ODS-2 codec in the in-tree bootable vms.ko (ACP $MOUNT)

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>

* vms-5f0: ACP-read bootstrap bridge — clear the require_installed_system 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>

* vms-5f0: interp flip + IMGACT staged-path map — DCL.EXE now activates 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>

* vms-5f0: RMS ACP-open resolves directory/concealed logicals via compose-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>

* vms-5f0: DCL OPEN/READ/WRITE/CLOSE ride RMS (ACP), not fopen-on-passthrough

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>

* vms-5f0: master text files as RFM=STMLF, not FIXED — un-hang the phase 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>

* vms-5f0: DCL @-procedure execution rides the ACP, not fopen-on-passthrough

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>

* vms-5f0: DCL RUN/$CREPRC image activation resolves via the ACP, not /vms

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>

* vms-5f0: LINK/IMGACT weak-by-name imports — LOGINOUT reads SYSUAF through 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>

* vms-5f0: CI — weak-import (.vms$wimp) LINK-level gate

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>

* vms-5f0: IMGACT legacy POSIX defer when /dev/vms absent (A1)

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>

* vms-5f0: bound F$FILE_ATTRIBUTES PRO accumulator (A3, CodeQL)

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>

* vms-5f0: move ACP-mount behind the boot seam -- ovmx_init.c substrate-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>

* vms-5f0: RMS legacy-POSIX defer when /dev/vms absent (A2a)

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 (f2817d31).

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 38befbe0).

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

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

* vms-5f0: POSIX $SEARCH composes the resultant from the searched DEV:[DIR] (A2a)

The atomic-flip DCL COPY/DIRECTORY rewrite (dcl_cmd_file.c) resolves filespecs
through sys$search. On the executive-absent defer that routes to the POSIX
backend, whose resultant spec was round-tripped through vmsfs_to_vms_spec --
which maps /vms/X/Y to the malformed "X:[000000]Y" (dir promoted to device).
COPY/DIRECTORY then re-opened that bogus spec and got %RMS-E-FNF.

Compose the resultant as DEV:[DIR]NAME from the ORIGINAL expanded spec's
device/directory prefix (as the ACP backend does from ctx->devnam/dirpath) plus
the matched entry name upper-cased, and store that VMS spec on _resolved_path
(the record engines re-parse it, exactly as the ACP path leaves it). Also fixes
the two `vmsfs_*_path(...) < 0` success checks -- those return VMS status codes
(odd == success), never < 0 (the project's standing vmsfs gotcha).

dcl-integration: from every file subtest failing to 133/148 passing. The
residual 15 are DIRECTORY/ODS-2 listing-fidelity cases (subdirectories as
NAME.DIR;1, version synthesis) the passthrough search does not yet emulate --
tracked separately, not a defer regression.

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

* vms-5f0: export pread/pwrite from DECC$SHR for the RMS legacy-POSIX defer (A2a fix)

A2a's RMS legacy-POSIX defer (0c1055fa) added a positioned-I/O POSIX backend
to rms_io.c: rms_io_read_posix/rms_io_write_posix pread()/pwrite() an ODS-2
block at f->cursor without moving the file position (the byte-offset form of
the ACP backend's IO$_READVBLK-by-VBN). Those two libc entry points were never
exported by DECC$SHR's symbol vector, so the STRICT VMS-native LINK.EXE link of
LIBVMSRMS$SHR failed:

  %LINK-F-ERROR, unresolved external symbol 'pread' (no --use'd shareable
  exports it as a universal ... must be appended to DECC$SHR's symbol vector)

and every downstream native-link/self-host/IMGACT gate (LINK/DCL/LOGINOUT/TCC/
LIBRARIAN/BUILD.COM/self-host fixpoint) red'd behind it. (readdir, the search
backend's other new libc call, was already exported.)

Fix: append pread=PROCEDURE,pwrite=PROCEDURE to the DECC$SHR vector in
mk_decc_shr.sh (append-only -> prior consumers' indices unchanged, GSMATCH
LEQUAL-compatible). open/close/read/write/lseek were already there; pread/pwrite
are their positioned-I/O companions that real OpenVMS DECC$SHR exports and
musl's libc.a defines, so DECC$SHR is the correct producer -- the faithful fix,
not --allow-undefined.

Verified (alpine musl, linux/amd64): link_native_graph builds clean, 9
EM_X86_64 artifacts, zero DT_NEEDED; OVMX_IMGACT x86_64 build+activate proof
passes (IMGACT_INTERP intact). No compiled code touched -> host Debug ctest
unaffected (uses ld, not LINK.EXE).

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

* vms-5f0: sysvol carries the REAL SYSUAF/RIGHTSLIST; login tests read them via the ACP (R1)

Make the login/rights kernel suites read the ACTUAL shipped records off a
genuine ODS-2 volume through the RMS-over-ACP rooted-logical open, and root-cause
the remaining boot-login blocker.

FIXTURE (verified GREEN via test_syssvc_dirlogical_acp):
- mkimage_ods2_sysvol.c now masters the REAL shipped SYSUAF.DAT / RIGHTSLIST.DAT
  (verbatim from distro/rootfs) into [SYS0.SYSCOMMON.SYSEXE], as RFM=STMLF
  (ods2_wvolume_create_file_stmlf) so RMS $GET frames one LF record per line --
  create_file_raw's RFM=FIXED made $GET see one 512-byte record then EOF, the
  exact "LOGINOUT's SYSUAF scan" failure that primitive's own doc warns about.
- Dockerfile passes the two distro paths to the generator.
- test_syssvc_dirlogical_acp verifies VBN 1 begins with the real shipped header
  (was a synthetic 0x5A^ pattern) -- byte-exact against what boots.

TESTS (authentic; fail-honest until the product blocker below is fixed):
- test_syssvc_sysuaf_uic_base / test_syssvc_rightslist now $MOUNT the ODS-2
  sysvol on DKA300:, seed the concealed-rooted system logicals (SYS$SYSDEVICE=
  DKA300:), and read the REAL records through sysuaf_lookup / rightslist_name_to_
  value -> rms_impl_open -> vmsfs_compose_ods2_candidates -> ACP $GET. Each keeps
  a DISMOUNT-then-fail-honest provenance check so a /vms read cannot masquerade
  as an ACP read (INV-6).

ROOT CAUSE of the boot-login failure (ESCALATED, product fix reserved):
  rms_acp_absent() (src/vmsrms/rms_core.c) and rms_impl_search()'s probe
  (rms_search.c) decide ACP-vs-legacy-POSIX by $ASSIGNing a HARDCODED DKA0: and
  reading SS$_NOSUCHDEV as "executive absent". But vms_ioctl_acp_assign returns
  SS$_NOSUCHDEV for BOTH "no /dev/vms" AND "that unit is simply not mounted"
  (vmsfs_acp.c). So whenever /dev/vms is present but SYS$DISK is $MOUNTed on a
  unit other than DKA0:, the probe wrongly reports "absent" and RMS silently
  defers to the /vms POSIX passthrough -- the exact /vms-on-a-/dev/vms-present
  read INV-6 forbids. The RMS-over-ACP open path is thus NEVER REACHED (proven:
  an instrumented rms_acp_open_file printed nothing; only the POSIX body ran).
  On the real boot DKA0: is the mounted system disk so this is masked, but any
  volume mounted elsewhere -- incl. these fixtures -- is diverted to /vms.

  The obvious fixes each hit a reserved wall:
   - probing /dev/vms directly (vms_kif_open() < 0) is REJECTED by the standing
     runtime_target_gate ("do not branch on whether the executive opened").
   - making vms_ioctl_acp_assign return SS$_DEVNOTMOUNT for an existing-but-
     unmounted unit (so the acp_assign probe becomes correct and the gate stays
     green) is a kernel-core semantic change that also needs the INV-6
     assertions in test_syssvc_acp_channel / test_syssvc_acp_mount updated
     (they pin unmounted -> SS$_NOSUCHDEV) and a device-existence check to keep
     a truly-absent unit honest. Kernel-core => 3-way VAX/Alpha gate; could not
     be verified in QEMU this session (shared-host disk at ~1.8G, docker build
     cache un-reclaimable under the standing constraint).

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

* vms-5f0: disambiguate ACP $ASSIGN unmounted-vs-absent — close the INV-6 POSIX-masquerade hole (vms-03b)

vms_ioctl_acp_assign returned SS$_NOSUCHDEV for BOTH "no /dev/vms" and "unit
exists but no volume mounted." RMS's executive-presence probe (rms_acp_absent /
rms_impl_search) and IMGACT's imgsrc_open defer to the legacy /vms POSIX
passthrough on SS$_NOSUCHDEV, so with /dev/vms present but SYS$DISK mounted on
any unit other than the probe's hardcoded DKA0:, the probe wrongly concluded
"executive absent" and RMS silently read the POSIX passthrough — the exact
INV-6 masquerade the atomic flip exists to kill. Masked on real boot only
because DKA0: happens to be the system disk.

Fix: the executive's $ASSIGN handler now returns SS$_DEVNOTMOUNT (device
present, no volume) for the not-a-mounted-volume path, DISTINCT from the
SS$_NOSUCHDEV that ONLY the userspace KIF (acp_bind_ok) emits when /dev/vms is
absent. Real VMS $ASSIGN never conflates them: an existing device assigns
regardless of mount state; SS$_NOSUCHDEV is reserved for a non-existent device.
The presence probe now reflects /dev/vms PRESENCE, not DKA0:'s mount/existence,
so an unmounted or non-DKA0: unit takes the ACP path and fails honestly with no
POSIX fallback. The deferral sites already gate on == SS$_NOSUCHDEV only, so no
runtime logic change was needed — the single status disambiguation fixes RMS
and IMGACT together; comments hardened to make the invariant legible.

Test assertions corrected to the authentic status (unmounted $ASSIGN ->
SS$_DEVNOTMOUNT, not NOSUCHDEV) in test_syssvc_acp_channel / acp_mount /
imgact_acp; negctl acp-assign-unmounted-fabricates-channel re-anchored to the
new line (still flips fail-honest -> SS$_NORMAL, still caught). Expected to flip
test_syssvc_sysuaf_uic_base / rightslist GREEN: they mount on DKA300: and assert
the read fails after DISMOUNT — the provenance the masquerade defeated.

Residual: the hardcoded DKA0: probe unit is now harmless (DEVNOTMOUNT != NOSUCHDEV);
the OPEN-path DKA0: default remains a vms-47d device-native-naming follow-up.

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

* vms-5f0: bind the RMS-over-ACP service seam in the syssvc suites — sys$open was NULL, not the codec (vms-058)

The RMS-over-ACP record read of SYSUAF/RIGHTSLIST was failing in the login
suites, but NOT for the reasons the two candidate root causes assumed.

WHAT IT IS NOT (both disproven with ground truth):
  - NOT a record-format / binary-indexed-ISAM problem. OVMX's shipped
    SYSUAF.DAT / RIGHTSLIST.DAT are the product's OWN pipe/colon-delimited
    ASCII TEXT files (distro/rootfs/.../SYSEXE), not VMS binary indexed files.
    RFM=STMLF is the correct framing.
  - NOT the octal-vs-decimal UIC parse. sysuaf.c / rightslist.c already parse
    UIC fields in octal (SYSUAF_UIC_RADIX / strtoul base 8).
  The ODS-2 codec, the fixture, the map and the multi-block read are all
  byte-correct: a host build of the genuine writer + block-backed reader
  (ods2_bdev_read_file) resolves [SYS0.SYSCOMMON.SYSEXE]SYSUAF.DAT and reads
  all 2966/3322 bytes BYTE-EXACT (efblk=6/ffbyte=406 single 6-block extent).

WHAT IT IS: the reader never opened the file. src/libvms/rtl/rms_textfile.c
references sys$open/$get/... with `#pragma weak` (the LIBVMS-below-RMS layering
seam). A WEAK UNDEFINED reference does NOT force the linker to pull the member
from the vmsrms archive (static) NOR record LIBVMSRMS$SHR as DT_NEEDED (shared,
--as-needed). So a suite that only reaches RMS through that weak seam got
sys$open == NULL, rms_services_present() returned FALSE, and rms_textfile_open()
bailed to NULL BEFORE any ACP call — every sysuaf_lookup / rightslist read
failed as "record not found", masquerading as a missing file.

Proven directly: `nm test_syssvc_sysuaf_uic_base` had NO sys$open at all, while
test_syssvc_loginout_acp / test_syssvc_rms_acp — which STRONGLY call sys$create /
sys$open — carried `T sys$open` and read fine.

FIX (general, one place): tests/qemu/rms_acp_bind.c makes a strong reference to
the seven RMS entry points and is linked into every syssvc suite via the shared
qemu_syssvc_add_test() recipe, so the linker resolves them from the vmsrms the
recipe already links. Verified in the Debug tree: the test binaries now carry
LIBVMSRMS$SHR as a runtime dependency (ldd) and import sys$open (nm). General:
every current and future syssvc suite that reads a file via the RMS-over-ACP
layer inherits the binding — no per-suite change. Touches tests/ only (no
src/, no kernel-core/codec → 3-way cross gate N/A; mutation-sandbox negctls copy
only src/+top CMakeLists, unaffected).

Fixes the in-process pure-read suites (sysuaf_uic_base, rightslist, setuai).
The activated production images that read SYSUAF via the ACP after IMGACT
(LOGINOUT.EXE — the boot login gate; the spawned DCL that answers F$IDENTIFIER;
MMK.EXE) hit the SAME weak seam through a DIFFERENT mechanism (symbol-vector
weak-import binding of a --use'd LIBVMSRMS$SHR at activation) and/or the
pre-existing cross-process ACP mount-visibility gap (test_kmod_vmsfs_mountvis,
red on the prior commit too) — tracked follow-on, see the vms-058 report.

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

* vms-5f0: unbloat the kernel-test initramfs + bind LOGINOUT's weak RMS seam at activation (vms-058)

Two faces of the same RMS-over-ACP weak seam, clearing the two dominant CI
failures on the atomic flip.

FIX 1 — kernel-test initramfs 36M -> 31M (clears the Kernel Executive boot
overflow). c63d700c linked tests/qemu/rms_acp_bind.c into EVERY test_syssvc_*
binary via the shared qemu_syssvc_add_test() recipe. A strong reference to the
seven RMS entry points EXTRACTS the whole vmsrms archive + ODS-2 codec into each
static musl binary, so all 31 suites gained ~200KB, bloating the shared
initramfs 31M->36M and overflowing the QEMU test-VM boot ("Initramfs unpacking
failed: write error" -- every shard died before shard selection). Only THREE
suites actually need the anchor: those that read an identity file IN-PROCESS
through the libvms reader AND carry no strong sys$ RMS call of their own
(sysuaf_uic_base, rightslist, setuai). The ACP suites (loginout_acp, rms_acp,
dcl_acp, scratch_writable, acp_*) already pull vmsrms via a direct sys$create.
Made the anchor OPT-IN via target_sources() on exactly those three; removed it
from the blanket recipe. Verified: initramfs back to 32.1MB (31M as run_tests.sh
reports it), shard 0 BOOTS and unpacks clean, all its suites RUN
(test_syssvc_rightslist 34/0 among them).

FIX 2 — LOGINOUT.EXE authenticates against SYSUAF again (clears the boot/login
e2e gates + the LOGINOUT VMS-native Link+Activate gate). LOGINOUT reads SYSUAF
through RMS over the Files-11 ACP; rms_textfile.c (in LIBVMS$SHR, below RMS)
`#pragma weak`-references sys$open/... and IMGACT binds those by name at
activation (resolve_weak_imports) against the LOADED producer set. But IMGACT
loads a producer only when the image names it in a STRONG .vms$imp entry
(bind_imports). LOGINOUT --use's LIBVMSRMS$SHR yet makes no strong reference to
it (all its RMS use is via the weak seam), so LIBVMSRMS$SHR was never loaded,
resolve_weak_imports could not find sys$open, LIBVMS$SHR's weak cell stayed 0,
rms_services_present() read FALSE, SYSUAF was never read, and every login failed
"User authorization failure" -- the exact --as-needed/no-DT_NEEDED root cause the
FIX 1 test anchor closes for the static suites, on the LINK.EXE image path.
Added src/vmslink/loginout_rms_bind.c (a guarded, never-executed strong CALL to
sys$open et al.) to mk_loginout.sh: LINK.EXE now records a strong .vms$imp import
naming LIBVMSRMS$SHR, IMGACT loads it, and the weak seam binds. The facade
ASCII-SHA256 SYSUAF is unchanged (its authentic rebuild is separate follow-on);
this only makes the real read happen. Corrected the stale "--use is enough"
claim in mk_loginout.sh's header.

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

* vms-5f0: test_kmod_disk — correct the disk-topology fixture to 5 disks (vde→DKA400:)

run_tests.sh now attaches FIVE virtio disks (vms-3e8e added vde → DKA400:,
the ODS-2 image volume the IMGACT-over-ACP test mounts). test_kmod_disk's
negative control still asserted "DKA400: → SS$_NOSUCHDEV (no fifth disk
attached)", which is stale: DKA400: now resolves to vde. This turned a real
fixture drift into a red suite.

Fix (a genuine fixture correction, not a weakening):
  - stat /dev/vde and assert it is present (fifth virtio disk);
  - add the DKA400: → vde positive resolution block (parallel to the
    DKA0..DKA300 blocks: unit exists, backing == "vde", dev_t matches);
  - move the truly-absent negative control up one unit to DKA500:, which
    still proves an unenumerated unit reports SS$_NOSUCHDEV.

The negctl keeps its teeth (a resolver that always succeeded would fail the
DKA500: SS$_NOSUCHDEV check); only the boundary moved to match the rig.

Verified in-guest against a real /dev/vms: test_kmod_disk 23/0 (was red).

* vms-5f0: genuine Files-11 Prolog-3 indexed READ over the ACP (supersede .rms_idx fake)

Root cause of the RMS$_ORG on a real indexed open: rms_impl_open's ACP branch
hard-rejected FAB$C_IDX ("the index has no ACP home yet") before ever accessing
the file, so every indexed $OPEN over /dev/vms failed. The only indexed path
that worked was the executive-absent defer to the in-memory `.rms_idx` B-tree
sidecar (a private, non-VMS structure).

This lands the real read engine. A new substrate-agnostic module
(src/vmsrms/rms_prolog3.{c,h}) parses a genuine on-disk Prolog-3 file --
fixed-prolog + area descriptors (VBN 3, 64 bytes) + per-key descriptors (root
VBN, first-data VBN, key flags, seg-0 position/size) -- then walks the
key-of-reference index buckets (14-byte header, records at 0x0E, 2-byte child
pointers) down to the primary data bucket and returns the record whose embedded
key matches ($GET/$FIND by key, incl. KGE/KGT). Every on-disk field is a
fixed-width uint read through le16()/le32() accessors and there is no substrate
#ifdef, so VAX ILP32 and Alpha/x86_64 LP64 share the file byte-for-byte.

All block reads ride rms_io_read_exact (rms_io.h) -> IO$_READVBLK on the ACP
channel window when /dev/vms is present (Rule 9 / INV-6). rms_impl_open now
ACCESSes the indexed data fork over the ACP and binds the Prolog-3 prologue;
rms_idx_get/find/cleanup dispatch to the engine when the FAB carries a bound
Prolog-3 context (tagged P3_CTX_MAGIC to disambiguate from the legacy btree in
_rms_state). The `.rms_idx` sidecar remains ONLY behind the executive-absent
host defer; the /dev/vms-present runtime path uses the real engine.

Fail-honest: a non-Prolog-3 / malformed / compression-bearing prologue returns
RMS$_PLG rather than mis-decoding. Scope is the smallest genuine increment --
read by primary key over a single-level (Root Level 1) index, uncompressed
keys/records; compression decode, multi-level descent, bucket split/overflow
chains, SIDR (secondary-key) read, and the WRITE engine (vms-045) are labelled
follow-on rungs.

Oracle grounding (docs/oracle/vax73-alpha84-rms-prolog3.md, vms-8438): pinned
geometry ([PIN]) honored -- Prolog Version 3, area descriptors @VBN 3 x64,
key descriptor @VBN 1 chain, key-flag bit positions, 14-byte bucket header +
records@0x0E, 2-byte index pointers, data-record control-flags/Record-ID/RRV
lead. Byte offsets the oracle does not publish are OVMX design choices
([OVMX]-labelled in rms_prolog3.h) so the writer (vms-045) and reader agree.

Test: tests/vmsrms/test_prolog3_read.c authors a real Prolog-3 image (prologue +
single-level index + two data buckets) and reads records BY KEY across both
buckets (index walk chooses the child), plus RNF miss, KGE, RTB, and
fail-honest-on-compression -- 20 assertions, host-side (POSIX rms_io backend, no
/dev/vms). The QEMU/ACP end-to-end is the paired positive pending the write rung.

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

* vms-5f0: genuine Files-11 Prolog-3 indexed WRITE over the ACP (create/put/split/update)

Completes the read/write loop the READ rung (dd89246f) opened: rms_prolog3.c now
authors a real Prolog-3 indexed file and maintains it as records insert, over the
same IO$_WRITEVBLK/READVBLK ACP substrate (Rule 9/INV-6) -- no .rms_idx sidecar,
no /vms write, no flat file.

WRITE engine (src/vmsrms/rms_prolog3.{c,h}), all fixed-width LE, no substrate
#ifdef (VAX ILP32 + Alpha/x86_64 LP64 identical):
  - rms_p3_create: writes the prologue (fixed prolog + key descriptor + area
    descriptor), the root index bucket (Root Level 1) and the first data bucket.
  - rms_p3_put: sorted keyed insert with control-flags/Record-ID/RRV lead and
    index high-key maintenance.
  - genuine data-bucket SPLIT: allocates a new bucket, redistributes records by
    key, leaves an RRV stub in the original bucket per moved record (RFA
    stability, IRC$V_RRV), and inserts the new bucket's 2-byte child pointer +
    high-key into the parent index bucket. Index-full -> fail-honest RMS$_ORG
    (2-level growth is the labelled follow-on), never a mis-write.
  - rms_p3_update: in place when same-size, else compact-delete + reinsert.
  Reader updated to read the write high-water and skip RRV stubs. Emits EXACTLY
  the [PIN]/[OVMX] byte offsets the reader parses -- writer<->reader round-trip.

Verify:
  - tests/vmsrms/test_prolog3_write.c (host round-trip, POSIX rms_io backend):
    authors an indexed file, $PUTs 40 records forcing 4 real bucket splits, reads
    them ALL back BY KEY via the read engine, asserts content + strict key order
    (KGT walk) + that a split happened; $UPDATE same-size + grow; duplicate ->
    RMS$_DUP; plus a seg0_siz<key_size (padded index key) file. PASSES.
  - tests/qemu/test_syssvc_rms_p3_acp.c (ACP e2e over real /dev/vms): IO$_CREATE
    + rms_p3_create/put/get over the window, split, read-back by key, and
    durability across DEACCESS+re-ACCESS. Compiles+links, honest-SKIPs 77 with no
    executive; CI-pending on the QEMU harness (local disk 97%, kernel/QEMU build
    deferred to CI). Anchored to rms-put-wrong-vbn negctl (facility_defects.sh).

Oracle docs/oracle/vax73-alpha84-rms-prolog3.md (vms-8438): 14-byte bucket header,
records@0x0E, area desc @VBN3 (64B), key desc 102B stride, 2-byte index pointers,
control-flags/Record-ID/RRV record lead, key-flag bit positions, Prolog Version 3.

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

* vms-5f0: genuine Files-11 Prolog-3 SECONDARY keys / SIDR over the ACP (vms-2ae)

The last RMS Prolog-3 rung before real SYSUAF/RIGHTSLIST: a second key of
reference with its OWN index tree whose level-0 leaves are SIDR (secondary
index data record) buckets mapping a secondary-key VALUE to the primary
record(s) carrying it -- real index descent + real RFA resolution to the
primary record, NEVER a flat scan filtered by the secondary field, no sidecar.

Engine (src/vmsrms/rms_prolog3.{c,h}):
- SIDR on-disk record [OVMX] (oracle §5 leaves the per-pointer sub-layout
  unpinned): [u8 ctrl][u16 payload_len][key_size key][u16 nptr][nptr*{u32 vbn,
  u16 id}], in a level-0 bucket sharing the [PIN] 14-byte header. Duplicate
  secondary values grow the pointer array; NODUP rejects with RMS$_DUP.
- rms_p3_add_secondary_key: defines a key of reference on the empty file
  (chains the descriptor in VBN1, allocates its root + first SIDR bucket).
- rms_p3_put now maintains every secondary SIDR after the primary insert,
  keyed on the record's STABLE home RFA {home_vbn,home_recid}.
- RFA stability across a primary split: the split now PRESERVES the moved
  record's home rrv-ptr/rrv-id (instead of self), and rms_p3_get_by_rfa follows
  the RRV stub chain home->current, so a SIDR pointer resolves after the primary
  record moves. rms_p3_delete recovers that home RFA to purge the SIDRs.
- rms_p3_sidr_lookup / rms_p3_get_by_rfa; rms_p3_get_by_key(krf>=1) routes
  through them. Genuine SIDR-bucket split (p3_split_sidr_bucket); 2-level
  secondary index growth fails honest (RMS$_ORG), never a mis-write (INV-6).
- Fixed-width le16/le32 only, no substrate #ifdef (VAX ILP32 + LP64 identical).

Verify:
- Host round-trip tests/vmsrms/test_prolog3_seckey.c (SYSUAF-shaped: 32-byte
  username primary + 4-byte UIC secondary, dups allowed): 40 records past BOTH
  a primary data-bucket split AND a SIDR-bucket split; reads BY UIC resolve to
  the byte-exact primary; a 3-member duplicate group resolves all three; RFA
  stable across the primary split; $DELETE purges the SIDR (dup array shrinks,
  unique-UIC SIDR removed). rms ctests 10/10 -> 11/11.
- ACP e2e tests/qemu/test_syssvc_rms_p3_acp.c extended with a UIC-secondary
  file read BY SECONDARY KEY over a real /dev/vms (IO$_WRITEVBLK/READVBLK),
  dup array + $DELETE + DEACCESS/re-ACCESS durability. Reuses the existing
  rms-put-wrong-vbn negctl anchor (no new suite, no floor bump).

Deferred (fail-honest, labelled): 2-level secondary index growth; adding a
secondary key to a non-empty file (back-fill); KGE/KGT on a secondary key;
data-type-aware key ordering (string memcmp, as the primary rung).

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

* vms-5f0: real binary $UAFDEF SYSUAF over the Prolog-3 indexed engine (vms-f88)

Drop the ASCII+SHA-256 / 368-byte SYSUAF facade the operator caught and build
the genuine binary $UAFDEF record on the Files-11 Prolog-3 indexed engine
(rms_prolog3.c: read/write/secondary-keys already complete).

Record (src/libvms/include/sysuaf.h): sysuaf_rms_record_t is now the 644-byte
$UAFDEF record from docs/oracle/vax73-alpha84-uafdef.md (644B on both VAX V7.3
and Alpha V8.4). Oracle-[PIN] offsets asserted at compile time: USERNAME@0x04,
UIC@0x24, owner-id@0x2C, UAF$Q_PWD@0x154 (quadword), UAF$W_SALT@0x166,
UAF$B_ENCRYPT@0x168 (0x03=UAI$C_PURDY_S), PWD_LENGTH@0x16A, UAF$Q_PWD2@0x16C.
Fields whose byte offset the oracle does not publish are labelled [OVMX] (Rule
8). All on-disk fields are fixed-width LE byte arrays (p3_le/put_le, incl. new
p3_le64/put_le64) with alignment 1 so the record is byte-identical on LP64 and
VAX ILP32 -- no substrate #ifdef. Password i…
baron-3dl added a commit that referenced this pull request Aug 30, 2026
…VAX-lane guardrail catch)

VAX-lane cross-review of 269d662 flagged a guardrail-2 hole: the SHOW CPU MP-STATE
mask used '.*', which matches an EMPTY value -- so a future hollow "Multiprocessing
is " (label present, state BLANK) would pass the mask, unlike the model mask (bounded
by the required ' (Series|system)$' suffix) and the CPU-list masks (bounded by
'[#0-9 ]+'). Not a current fidelity issue (OVMX prints a real ENABLED/DISABLED, and a
MISSING line still reds) -- a regression-protection hole a future empty MP-state would
slip. Fix: '.*' -> '.+' so a blank MP-state reds like the other two masks, making the
present+non-empty (anti-hollow) property UNIFORM across all three cpu masks.

Adds the matching selftest case (mirrors guardrail 3): a '.+'-terminated value mask
matches a present MP-state (ENABLED -> MATCH) but keeps a BLANK one RED -- a future
regression back to '.*' fails this test. diff_surface selftest 14/14; show-cpu still
MATCHes with the '.+' mask (acceptance leg re-passes identically, OVMX's MP-state is
non-empty). Structure VAX already OK'd (#1 anti-facade, #3 note()-driver) unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 30, 2026
…nuous oracle golden-diff gate (part B) (#952)

* vms-c38 part B: wire the DCL/SHOW acceptance battery to diff_surface — continuous oracle golden-diff gate

The gate itself: dcl_acceptance_battery.sh now runs a golden_diff over the 5 core
SHOW-family goldens (MEMORY/SYSTEM/CPU/DEVICE/PROCESS), upgrading the piecewise
must_haves to a continuous whole-layout diff against the real-VMS oracle.

Two required proofs, per surface, inside the battery:
- GREENS: run the surface's OWN commands (self-contained -- the battery ran
  'SHOW DEVICE DKA0:' not the golden's 'SHOW DEVICE D', so golden_diff runs the
  golden's exact commands), capture $SEG, and diff_surface -> MATCH (modulo the
  surface's grounded MAY_OMIT).
- REDS: a golden_diff_negctl injects a divergence into the SAME output and asserts
  it does NOT MATCH -- proving the gate can actually fail (not vacuously green).

diff_surface strip_console: the oracle golden ("$ CMD\n<out>", capture_oracle's
prompt-prefixed echo, no trailing prompt) and run_cmd's $SEG ("CMD\n<out>\n$ ",
bare echo + returned prompt) frame the console differently; strip_console drops
the command-echo + bare-prompt lines from BOTH so the gate compares the OUTPUT
LAYOUT, not console framing. Validated locally: a simulated run_cmd $SEG
faithful-subset -> MATCH; an injected divergence -> FORMAT-DIVERGENT.

Builds on part A (diff_surface MAY_OMIT, #951 -- stacked until it reaps, then
rebased to main). selftest 8/8 still green. The GREENS proof + any ADDITIONAL
grounded MAY_OMIT (Dynamic Memory / Paging File, only if substrate-absent) are
CI-verified on the real OVMX boot -- multi-round expected, never a MAY_OMIT added
just to turn a red green (INV-6).

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

* vms-c38 round 2: mount the oracle tooling into the acceptance container so golden_diff can find it

Round-1 CI reds were NOT a GREENS content mismatch -- all 5 golden_diff calls
failed "surface/tooling not found". Root cause: the acceptance test runs in a
Docker container where run_dcl_acceptance_e2e.sh mounts ONLY /test.sh and
/lib/dcl_acceptance_battery.sh -- tools/oracle (diff_surface + surfaces) and the
goldens are not present inside, so the battery's repo-relative _ORACLE_DIR
resolved empty.

- run_dcl_acceptance_e2e.sh: mount tools/oracle -> /oracle/tools/oracle and
  docs/oracle/golden -> /oracle/docs/oracle/golden (a repo-root-like /oracle
  prefix so diff_surface's own HERE/REPO/GOLDEN_DIR path math resolves), and pass
  OVMX_ORACLE_DIR=/oracle/tools/oracle.
- dcl_acceptance_battery.sh: _ORACLE_DIR = ${OVMX_ORACLE_DIR:-<repo-relative
  fallback>} so the container uses the mount and a local checked-out-tree run
  still uses the relative path.

selftest 8/8 + golden-self MATCH unchanged (the tooling is untouched; only its
availability inside the container). This unblocks the actual GREENS proof --
whether OVMX's output MATCHes each golden -- for the next CI round.

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

* vms-c38 round 3: log the golden_diff FORMAT-DIVERGENT diff for diagnosis

Round 2 (oracle mount fixed) ran the REAL gate: all 5 REDS-negctl PASS (the gate
can fail -- REDS proof done), and all 5 GREENS are FORMAT-DIVERGENT. All-five
diverging is a systematic cross-system tell (not per-surface substrate-omission),
but the battery only logged the classification, not the diff. This logs the full
diff_surface output (normalized golden < vs OVMX >) on a red so the exact
diverging line/section is diagnosable -- to tell a grounded substrate-absent
omission from a value-width/machine-string difference from a real gap, before
choosing the fix.

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

* vms-c38: structure_norm — cross-system value-tolerant compare (mechanism, selftest 12/12)

The conductor-ruled Option-1 core (2026-08-30): a byte-exact column-geometry gate
is impossible cross-system (OVMX's values legitimately differ from the VAX/Alpha
oracle: wider numbers, different machine strings). structure_norm proves STRUCTURAL
fidelity -- same sections/labels/headers/field-structure, value-tolerant -- via
three symmetric transforms applied to BOTH golden and OVMX after MAY_OMIT/
strip_console: (1) grounded per-surface MACHINE_MASK, (2) collapse-digit-RUN->one
token, (3) whitespace-normalize. Guardrails (selftest): a HOLLOW numeric field
(blank, no digits) STILL reds; a MACHINE_MASK'd field that is blank/absent STILL
reds -- a mask means "value varies," never "ignore the field" (INV-6).

Mechanism only; per-surface MACHINE_MASK values await the conductor's sign-off on
the round-3 diff finding (the divergence is MIXED: value/machine-string AND real
structural fidelity gaps that must stay red). NOT pushed until that sign-off.

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

* vms-c38: Option-A standing gate — hard-gate CLAIMED-FAITHFUL, report-only the rest; graduate show-cpu

Conductor ruling 2026-08-30 (Option A). Round-3's diff proved the cross-system
divergence is MIXED — value/machine-string AND real structural fidelity gaps — so
normalization alone cannot green all 5, and masking the real gaps green would be the
exact INV-6 allowlist-cheat. Structure the standing gate in two honest tiers:

- HARD-GATE only CLAIMED-FAITHFUL surfaces (a divergence FAILS the leg =
  regression-proof). vax-show-cpu graduates here: it is genuinely structurally
  faithful — the model line, the "Multiprocessing is ..." state, and the
  Active/Configured CPU-ID list are machine-varying VALUES (masked via grounded,
  label-preserving MACHINE_MASK, DCL-Dictionary-pinned per src/vmsdcl/dcl_cmd_show.c);
  the labelled structure MATCHes the oracle through the full pipeline (proven, not
  masked-to-hide-a-gap). (Refutes the round-3 "Active-CPUs extra token = gap"
  sub-hypothesis: "## ##" is the faithful 2-CPU ID list, not a bug.)
- REPORT-only the not-yet-faithful surfaces (new note() primitive: loud, logged,
  routed to a fidelity item every run, but NO PASS/FAIL touch). Round-3 findings:
  vax-show-memory HOLLOW (omits Dynamic Memory + Paging File sections; OVMX has
  pool+pagefile) -> vms-352; vax-show-system HOLLOW (omits State/Pri/I/O columns)
  -> vms-6b8e; vax-show-device MISSING (%NOSUCHDEV, device-name model) -> vms-ddc
  (+vms-9f5); vax-show-process HOLLOW (omits Terminal/Base priority/Devices
  allocated; UIC not resolved to [SYSTEM]) -> vms-1f7. These are TRUE findings the
  gate exists to drive (vms-050 backlog); each graduates to hard-gate when its item
  lands and it genuinely MATCHes. This tracks + names every gap loudly (anti-LARP),
  it does not silently pass them — and it can't permanently-red main's green-by-SHA.

GREENS proof = show-cpu greens through the full pipeline (hard-gated) + the
diff_surface selftest MATCH cases. REDS proof = the show-cpu negctl (an injected
divergence does NOT MATCH) + the selftest, both retained. Register (docs/compat/
ux-surface-register.md) records the structure-tolerant bar + each surface's honest
status. No brittle per-defect red-set .tsv (vms-49f).

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

* vms-c38: tighten MP-STATE mask .* -> .+ so a blank value still reds (VAX-lane guardrail catch)

VAX-lane cross-review of 269d662 flagged a guardrail-2 hole: the SHOW CPU MP-STATE
mask used '.*', which matches an EMPTY value -- so a future hollow "Multiprocessing
is " (label present, state BLANK) would pass the mask, unlike the model mask (bounded
by the required ' (Series|system)$' suffix) and the CPU-list masks (bounded by
'[#0-9 ]+'). Not a current fidelity issue (OVMX prints a real ENABLED/DISABLED, and a
MISSING line still reds) -- a regression-protection hole a future empty MP-state would
slip. Fix: '.*' -> '.+' so a blank MP-state reds like the other two masks, making the
present+non-empty (anti-hollow) property UNIFORM across all three cpu masks.

Adds the matching selftest case (mirrors guardrail 3): a '.+'-terminated value mask
matches a present MP-state (ENABLED -> MATCH) but keeps a BLANK one RED -- a future
regression back to '.*' fails this test. diff_surface selftest 14/14; show-cpu still
MATCHes with the '.+' mask (acceptance leg re-passes identically, OVMX's MP-state is
non-empty). Structure VAX already OK'd (#1 anti-facade, #3 note()-driver) unchanged.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 31, 2026
…-trip (#972)

Add the NSP (Network Services Protocol) transport-message codec alongside the
rung-1 routing HELLO codec: a pure, engine-agnostic byte-layout library
(stdint + memcpy, no socket/kernel deps) that links into either the AF_DECnet
forward-port harness or the design sec-4b userspace fallback engine.

Covers Connect Initiate, Connect Confirm, Data segment, Data Acknowledgement,
and Disconnect Initiate (encode + decode). The codec owns the NSP transport PDU
only (MSGFLG onward); the Phase IV routing header + data-link length prefix
belong to the routing rung.

Oracle round-trip (vmsdecnet_nsp_unit): decodes the committed vms-3be NSP
Connect Initiate specimen (docs/decnet-provenance-register.md sec 4.6, #3),
asserts every field the register names (MSGFLG=CI, SRCADDR 8193, ver 4.1,
SEGSIZE 1459, access-control 'SYSTEM'), and re-encodes byte-identical to the
captured 29-byte PDU. Connect Confirm/Data/Ack/Disconnect are labelled
SPEC-DERIVED (public DNA Phase IV NSP spec) — the register committed only one
NSP specimen (handshake never completed, VAX2 unconfigured) — and are proven by
codec self round-trip only; no specimen bytes are fabricated for them.
Warning-clean under -Wall -Wextra -Werror.

Rule 8 clean-room: wire format from the public DNA Phase IV NSP functional
spec + the committed oracle specimen only; no VSI/HPE source consulted.


Claude-Session: https://claude.ai/code/session_01V2jrHU9fdTTfKhB5HBQNQH

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 31, 2026
…rung 2) (#1007)

Build the NSP CONNECTION STATE MACHINE on top of the rung-2 NSP codec
(vms-6986) and wire it into the routing engine (rung-1, vms-449d) so
DECNETD can OPEN a logical link over an adjacency it already forms.

- src/vmsdecnet/nsp/dnet_link.{c,h}: a pure, clock-injected NSP connection
  FSM (CLOSED -> CI_SENT/CR_RCVD -> RUN -> DI_SENT -> CLOSED). Establish
  (Connect Initiate/Confirm), run (a data segment carried with its explicit
  acknowledgement + in-order sequencing), tear down (Disconnect
  Initiate/Confirm). No socket/thread/wall-clock, same discipline as the
  adjacency SM. CI retransmit + give-up-as-UNREACHABLE (oracle-informed:
  ~5.5s interval, 8 retransmits, register sec 4.6).
- dnet_nsp: add Disconnect Confirm (DC, MSGFLG 0x48), classified distinct
  from DI; CI oracle round-trip unchanged and still byte-identical.
- dnet_engine: a minimal Phase IV long-data-packet routing header
  carrier (build/parse, grounded on specimen #3's header bytes) + one
  embedded link + link_open/accept/send/close/rx wrappers.
- decnetd: DECNETD.EXE --nsp-selftest (no CAP_NET_RAW): two engines OPEN a
  link, move a data segment+ack, and DISCONNECT over a real socketpair.
- tests/vmsdecnet/test_dnet_link.c: FSM unit + CI-retransmit + two-engine
  end-to-end over a socketpair (payload byte-identical). DC codec coverage
  added to test_dnet_nsp.c. 6/6 vmsdecnet + 26/26 vmsscs green, -Werror.

Clean-room (Rule 8): only the Connect Initiate is oracle-verified; the
CC/data/ack/DI/DC choreography is spec-derived from the public DNA Phase IV
NSP spec, proven by the two-endpoint round-trip, no fabricated bytes.
Register sec 6.0 records rung 2. SET HOST/CTERM (vms-4d2), FAL/DAP
(vms-8c2) and the live-VAX bracket (vms-aac0) are filed as children of
vms-30e; not this rung.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Sep 2, 2026
…fire (vms-74f)

Layer 3 frame builders #2/#3 + the safe scsd checkpoint before the FSM:

- scs_member_build_dlm_op04 / _commit now take OVMX's OWN real per-lock handle
  (lkid) and write it at body[20:24] -- the handle the executive DLM holds for the
  lock (from the vms-1f4 accessor). Per the conductor's handle-chain trace, op-04
  and op-03 carry the JOINER's own node-local handle, NEVER VAX3's un-replayable
  kernel bytes nor the coordinator's granted mst_lkid; the ungrounded second handle
  word @[24:28] stays ZERO (INV-6 -- don't invent). lkid==0 reproduces the old
  content-free frame (null case). test_scs_member pins lkid@[20:24], the zeroed
  second word, NL mode, and no-resname; suite ALL PASSED.

- cm_send_dlm_completion is now lkid-aware (function-pointer signature updated).

- The OPT-A content-free completion FIRE (post-op-06) is DISABLED: it destabilized
  the cluster (op-03 with no real op-01 = dangling transaction -> 2/2 reformations,
  lab-proven). Firing nothing keeps the branch safe until Layer 3's registration
  FSM lands. That FSM (next commit): post-op-06 enumerate the standing locks, send
  op-01 per lock to the coordinator, and on the coordinator's cat-82 op-01 grant
  arrival send op-04 -> op-03 with OVMX's real handle.

scsd.c -fsyntax-only clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhM3QcmFEY3p8YNnHGaYwP
baron-3dl added a commit that referenced this pull request Sep 8, 2026
…in fixes; strip diagnostics

Rung 4 of the vms-b4f ladder. Boots the CRTL->RMS veneer-wired alpha-dec-vms
GCC-port image on qemu-system-alpha + the real /dev/vms executive: its
decc$fopen -> ovmx_crtl veneer -> sys$create -> LIBVMSRMS$SHR -> ioctl(/dev/vms)
-> Files-11 ACP writes PORTTEST.DAT, then an INDEPENDENT reader (DCL
DIRECTORY/FULL, a different accessor than the writer's own CRTL/RMS handle)
asserts PORTTEST.DAT;1 landed on the real ODS-2 volume with a genuine File ID
AND the full 8192-byte content (16 blocks) -- something a ramfs/POSIX write can
never produce in the ACP directory. The gate keys on that fid+content landing
(strictly stronger than a same-CRTL round-trip a ramfs satisfies), with a 7/7
can-fail selftest.

Three first-exercise toolchain bugs the forcing function exposed:

- #1 emutls control-object width (LLP64): unsigned long is 32-bit on
  alpha-dec-vms, so __emutls_object {size,align,loc,templ} packed loc at offset
  8 instead of 16 and __emutls_get_address returned 4 -> SIGSEGV. emutls_word
  widened to unsigned long long. (committed earlier as e2c6cf5)

- #2 DECC$SHR symbol-vector index skew (mk_decc_shr.sh): the veneer pass dropped
  the 4 fopen/fwrite/fread/fclose entries from the middle of the sorted vector
  and re-appended the aliases at the tail, shifting every higher sv# down by 4.
  IMGACT binds cross-image imports BY INDEX, so producers linked against the
  bootstrap DECC dispatched e.g. decc$strlen[sv#414] to decc$strspn at runtime
  (NULL-arg SIGSEGV). Rewrite the 4 entries IN PLACE at their sorted slot,
  restoring the append-only sv# stability the recipe already documented.

- #3 calloc weak-override reloc (link.c): the vms-430 strong-over-weak
  base-redirect matched a section-relative reloc's section BASE before the
  addend was added, so every sibling symbol in a $CODE$ whose offset-0 proc is
  an overridden weak def (calloc.o's __malloc_allzerop) was pulled onto the
  strong def + addend -- decc$_calloc64 (real calloc at $CODE$+0x008) mis-bound
  onto strong __malloc_allzerop+0x008. Match base+addend and consume the addend
  on a hit; the symbol-target path is byte-identical. run_muldef_evax.sh green
  (incl. weak-first + strong-first self-bind redirect).

All diagnostic scaffolding stripped (IMGACT SIGSEGV handler / IMGACT-MAP probe /
qemu -d int injection) for a clean production activator.

The writer program's post-commit mallocng cleanup crash (free -> free_group ->
free(g->mem) hitting get_meta's `assert(meta->mem==base)` with a NULL group
meta) is a separate mallocng-group-release issue on the alpha-dec-vms substrate,
tracked as bug #4 (blocks vms-fd1); it fires AFTER the content commits and does
not affect the proven landing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ
baron-3dl added a commit that referenced this pull request Sep 9, 2026
…fc LLP64 width fix) (#1063)

* vms-f49: rung 4 — un-fakeable ODS-2 independent-reader proof + vms-1fc LLP64 width fix

Lands the MILESTONE of the vms-b4f ladder: prove the alpha-dec-vms GCC-port
image's CRTL fopen genuinely writes to a real Files-11 ODS-2 volume over the
executive ACP, not musl-ramfs — the anti-fabrication payoff (INV-6). Two
coupled items in one PR, because the width fix is VALIDATED by the proof.

PART A — vms-1fc (LLP64 syscall width fix). On the alpha-dec-vms C model
`long`/`unsigned long` are 32 bits while pointers are 64 (LLP64), so the
libvmssys raw-syscall path truncated every pointer argument to the /dev/vms
transport — the ioctl(/dev/vms, ...) RMS-over-ACP write landed on a garbage
address and reached nothing.
  - vms_syscall.h: widen __vms_syscall0..6 params + return to a guaranteed-
    64-bit `vms_reg_t` (== long long), and the vms_sys_* pointer casts with it;
    widen vms_sys_ioctl's `arg` param from `unsigned long` to vms_reg_t.
  - arch/alpha/syscall_vms.c: match the widened prototypes (long long).
  - kif_transport_linux.c: cast the request-block pointer through vms_reg_t,
    not `unsigned long` — THIS is the /dev/vms pointer the proof exercises.
  - vms_bgsock.c: widen its ioctl pointer casts to match.
  No-op on the LP64 targets (x86_64/aarch64/alpha-linux-gnu: long long == long,
  byte-identical codegen); the actual fix only on alpha-dec-vms. VAX is
  untouched by construction — it takes the __NetBSD__ branch
  (arch/vax/vms_syscall_netbsd.h) and compiles none of these declarations.

PART B — vms-f49 (rung 4, the un-fakeable gate). New `crtl-rms-veneer-gate`
mode of run-module-gp-activation-alpha.sh boots the veneer-wired crtl_rms port
image (JOINT_CRTL_RMS_VENEER=1) on the real /dev/vms + qemu-system-alpha; its
decc$fopen -> the crtl_rms_stdio veneer -> sys$create/$put -> LIBVMSRMS$SHR ->
ioctl(/dev/vms) -> ACP writes PORTTEST.DAT. Then an INDEPENDENT reader — DCL
DIRECTORY/FULL, a DIFFERENT accessor than the writer's CRTL/RMS handle, running
its own sys$search over the ACP directory — asserts PORTTEST.DAT;1 exists on the
ODS-2 volume with a genuine ODS-2 File ID that a ramfs write cannot produce.
  - SYSTARTUP_VMS_VENEER_PROOF.COM: RUN JOINT_E2E, then DIRECTORY/FULL PORTTEST.DAT.
  - build-alpha-bootimage.sh: stage LIBVMSRMS$SHR.EXE into SYS$SHARE and swap in
    the veneer-proof SYSTARTUP when a veneer build is present (keyed on the
    shareable), verify it on the mastered volume.
  - assert_veneer(): gates on the independent File-ID reader, NOT console/CRTL
    state; a can-fail selftest proves teeth incl. the NEGATIVE/REJECTION case —
    a same-CRTL success that ramfs satisfies (%DIRECT-W-NOFILES) must FAIL.
  - ci.yml: new PR job alpha-crtl-rms-veneer (alpha_activation scope, 150m). The
    non-veneer alpha-crtl-rms-n7 gate stays green as the control.

Local build+link verified: the veneer graph links zero-deferred under the
alpha-dec-vms cc1 with the width fix (decc$fopen->DECC$SHR, veneer
sys$create/$put/$get->LIBVMSRMS$SHR), LIBVMSRMS$SHR.EXE emitted; assert_veneer
selftest passes all six fixtures. The qemu-alpha runtime proof runs in CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: fix apostrophe quote-break in build-alpha-bootimage.sh staging block

The vms-f49 staging comments landed inside the assemble `docker run ... bash -c
'...'` SINGLE-QUOTED block with apostrophes ("image's"), which closed the quote
mid-body and exposed `decc$fopen` to the outer shell -> `line 91: fopen: unbound
variable` under `set -u`. This broke the boot-image assembly for EVERY alpha
activation gate that calls assemble_boot_image (gate/crtl-rms-gate/mf-gate/
crtl-rms-veneer-gate), before any qemu boot -- NOT a Part A regression (all three
reds died at the identical line-91 quote-break after "step 1 staged", pre-boot;
Part A links clean, proven by the green alpha RMS-substrate STRICT-link + DECC$SHR
jobs). The script's own header warns "no apostrophes in this block". Reworded the
two added comments apostrophe-free; verified the docker -c body now has balanced
single-quotes with zero outer-shell `$` exposure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: stage the full RMS producer graph + fix its DECC producer name (rung-4 activation)

The rung-4 veneer gate booted this time (N=7 control PASSED -> Part A width fix
does NOT regress alpha activation) but the veneer image RUN drew
%IMGACT-F-IMGNOTFND: LIBVMSRMS$SHR is not self-contained. It transitively imports
from the whole executive producer graph (LIBVMS/LIBVMSFS/LIBVMSLNM/LIBVMSPROCESS/
LIBVMSSYS$SHR) AND recorded its DECC producer as the phantom pass-1 bootstrap name
"DECC1$SHR.EXE" -- neither on SYS$SHARE, so IMGACT could not resolve them.

Two in-scope Part-B wiring fixes (no new executive facility):
 - build-joint-image.sh: build the pass-1 bootstrap DECC under $WORK/p1 with the
   BASENAME DECC$SHR.EXE (not DECC1$SHR.EXE). LINK records producers by basename,
   so the graph + LIBVMSRMS$SHR now record "DECC$SHR.EXE" and, at activation,
   IMGACT name-keyed binding resolves them against the SINGLE staged pass-2
   (veneer) DECC$SHR.EXE (GSMATCH LEQUAL). One DECC$SHR at runtime, no duplicate
   musl C-RTL. Also emit the whole producer graph to OUTDIR.
 - run-module-gp-activation-alpha.sh + build-alpha-bootimage.sh: stage the full
   graph (LIBVMSRMS/LIBVMS/LIBVMSFS/LIBVMSLNM/LIBVMSPROCESS/LIBVMSSYS$SHR) into
   SYS$SHARE and verify each on the mastered ODS-2 volume.

Also fixes a second single-quote apostrophe break ("IMGACT's", and 'DECC$SHR.EXE'
exposing $SHR) in the build-joint-image.sh docker bash -c block.

Local verify: veneer graph links zero-deferred; LIBVMSRMS$SHR + all 7 sibling
shareables now record DECC$SHR.EXE (grep DECC1 = 0 across the whole staged set);
full graph emitted to OUTDIR. Runtime activation runs in CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: surface the guest-kernel faulting PC in the veneer-gate failure output

The veneer image now activates (producer graph staged) but SIGSEGVs
(%DCL-F-ABORT signal 11) in the first-ever runtime execution of the alpha RMS
substrate over the veneer. The Alpha guest kernel prints the faulting user
PC/RA/VA to the console at fault time, but the gate's fixed pattern-grep never
surfaced it. Dump the guest fault-signature line(s) + the last 60 console lines
in the veneer-gate FAIL path so the authoritative fault PC is captured in CI
(disk-safe -- no qemu -d flags; the guest kernel already emitted it). This
localizes the crash so it can be fixed. Not a gate weakening -- failure path only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: capture qemu CPU-exception log to pin the veneer SIGSEGV faulting PC

The previous fault-capture confirmed the veneer image crashes at/near ACTIVATION
(no OVMX-CRTL-RMS sys$create trace precedes the %DCL-F-ABORT signal 11), and the
Alpha guest kernel prints no userspace fault line. So add qemu exception logging
(-d int,cpu_reset,guest_errors -D /work/qint.log), enabled ONLY for the
crtl-rms-veneer-gate via QEMU_DBG, and dump the last exceptions (faulting PC/VA)
in the veneer FAIL path. Disk-safe: the boot reaches Username: within ~30-60s so
qint.log stays small; other gates pass QEMU_DBG empty (unchanged). This is the
gdb-equivalent authoritative fault PC needed to decide RMS-substrate truncation
(hyp 1) vs IMGACT mutual-producer-cycle activation crash (hyp 2, the leading
hypothesis: the veneer DECC$SHR<->LIBVMSRMS$SHR cycle the non-veneer control lacks).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: filter qemu exception log (clk-interrupt firehose hid the fault)

The -d int log is dominated by clk_interrupt; the veneer SIGSEGV's Dfault/MMFAULT
exception with the faulting user pc= is buried. Filter out clk/dev interrupts and
add an exception-type histogram so the fault exception + PC is surfaced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: IMGACT-MAP producer base logging to resolve the veneer fault PC

The qemu -d int log localized the veneer SIGSEGV to a repeated mmfault loop at
user pc=0x12005eb00 (and 0x12005a7f4). IMGACT is only ~37KB so the fault is in a
mmap'd producer (likely DECC$SHR, which holds the veneer ovmx_crtl_fopen). Print
each producer's runtime base (IMGACT-MAP: <name> base=0x..) at load so the
faulting pc can be resolved to <image>+offset and then to a symbol. Diagnostic
only; other gates grep their own patterns so the extra lines are inert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: log MAIN-EXE + IMGACT-INTERP bases to place the 0x120000000 fault region

Producer bases are all 0x20000xxxxx, but the veneer fault PCs cluster in the
0x120000000 range (0x12005eb00 repeated 18x = the unrecoverable fault; other
single-hit pcs are benign TLB fills). Log the kernel-mapped main-exe bias and the
PT_INTERP (IMGACT) base so the faulting region can be attributed to the main
image, the interp, or an unmapped bad-jump target (mis-resolved cross-image
linkage = hyp 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: gate IMGACT-MAP behind OVMX_IMGACT_MAP=1 (silent by default)

Housekeeping: the IMGACT-MAP producer-base logging now emits only when the boot
cmdline carries OVMX_IMGACT_MAP=1 (kept for fault-localization, silent in
production). run_boot_a gains an optional QEMU_APPEND injection; the veneer gate
sets QEMU_APPEND=OVMX_IMGACT_MAP=1 so it still gets the map. The QEMU_DBG -d int
fault capture is retained (inert for other gates).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: Option-1 probe — log wild (0x120000000-region) import bindings in IMGACT

The veneer SIGSEGV jumps to 0x120000000+offset (default/stack-top base) while all
images map at 0x200_xxxx_xxxx; the value is COMPUTED at runtime (not stored). Add
a gated (OVMX_IMGACT_MAP=1) probe in bind_imports that logs any binding whose
resolved PV or filled code entry *(PV+8) lands in the wild region -- with the
importing image, the symbol, the cell, the PV, and the entry. A wild PV isolates
an SV-value fault; a sane PV with a wild entry isolates a producer PDSC-entry
rebase fault. Surface IMGACT-WILD/IMGACT-MAP in the veneer-gate failure dump. If
nothing fires, the wild target is code/GP-computed (not a linkage fill) and the
next step is a register (RA) capture. Probe only; no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: correct the Option-1 probe wild-region range (0x1_xx, not the 0x200xxx image region)

The probe flagged all valid 0x200_xxxx_xxxx bindings as wild because the upper
bound (0x200000000000) sat above the real image region. Narrow it to
[0x1_0000_0000, 0x100_0000_0000) -- the 0x120000000 default/stack-top region --
so only genuinely-wild values fire. Confirmed against the CI run: with the correct
range NO binding is wild (all PVs/entries resolve to 0x200xxx), proving the veneer
SIGSEGV is NOT a linkage fill but a wrong-base code jump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: fix emutls control-object width (LLP64) — the veneer rung-4 crash root cause

The alpha emulated-TLS runtime typed the control-object fields as `unsigned long`
(emutls_word), which on the alpha-dec-vms LLP64 target is 32 BITS — but the cc1
emits the control with 64-bit .quad fields (.quad size; .quad align; .quad loc;
.quad templ). So struct __emutls_object packed size+align into the first 8 bytes
and put `loc` at offset 8 (the align field) instead of 16. __emutls_get_address
returned obj->loc = the align value (4), and the first __thread access on the
veneer's sys$create path (a vms_kif 'vms_bound_pid == getpid()' check) then
dereferenced 4 -> SIGSEGV at 0x4 — the rung-4 (vms-f49) blocker, pinned via a
local qemu-system-alpha boot + an IMGACT SIGSEGV-handler RA capture.

Fix: emutls_word -> unsigned long long (64-bit on every target), so loc lands at
offset 16 and templ at 24, matching the .quad emission. Same LLP64 bug class as
vms-1fc. Arch-scoped: the whole file is #if defined(__alpha__), so x86_64/aarch64
(musl TLS) and VAX (NetBSD) never compile it — the non-veneer + VAX/x86_64
controls stay byte-identical.

Verified locally: the v0=4 / gp-as-stack SIGSEGV is gone; the veneer image now
advances past the emutls point into the Files-11 path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-f49: un-fakeable ODS-2 landing proof (rung 4) + bug #2/#3 toolchain fixes; strip diagnostics

Rung 4 of the vms-b4f ladder. Boots the CRTL->RMS veneer-wired alpha-dec-vms
GCC-port image on qemu-system-alpha + the real /dev/vms executive: its
decc$fopen -> ovmx_crtl veneer -> sys$create -> LIBVMSRMS$SHR -> ioctl(/dev/vms)
-> Files-11 ACP writes PORTTEST.DAT, then an INDEPENDENT reader (DCL
DIRECTORY/FULL, a different accessor than the writer's own CRTL/RMS handle)
asserts PORTTEST.DAT;1 landed on the real ODS-2 volume with a genuine File ID
AND the full 8192-byte content (16 blocks) -- something a ramfs/POSIX write can
never produce in the ACP directory. The gate keys on that fid+content landing
(strictly stronger than a same-CRTL round-trip a ramfs satisfies), with a 7/7
can-fail selftest.

Three first-exercise toolchain bugs the forcing function exposed:

- #1 emutls control-object width (LLP64): unsigned long is 32-bit on
  alpha-dec-vms, so __emutls_object {size,align,loc,templ} packed loc at offset
  8 instead of 16 and __emutls_get_address returned 4 -> SIGSEGV. emutls_word
  widened to unsigned long long. (committed earlier as e2c6cf5)

- #2 DECC$SHR symbol-vector index skew (mk_decc_shr.sh): the veneer pass dropped
  the 4 fopen/fwrite/fread/fclose entries from the middle of the sorted vector
  and re-appended the aliases at the tail, shifting every higher sv# down by 4.
  IMGACT binds cross-image imports BY INDEX, so producers linked against the
  bootstrap DECC dispatched e.g. decc$strlen[sv#414] to decc$strspn at runtime
  (NULL-arg SIGSEGV). Rewrite the 4 entries IN PLACE at their sorted slot,
  restoring the append-only sv# stability the recipe already documented.

- #3 calloc weak-override reloc (link.c): the vms-430 strong-over-weak
  base-redirect matched a section-relative reloc's section BASE before the
  addend was added, so every sibling symbol in a $CODE$ whose offset-0 proc is
  an overridden weak def (calloc.o's __malloc_allzerop) was pulled onto the
  strong def + addend -- decc$_calloc64 (real calloc at $CODE$+0x008) mis-bound
  onto strong __malloc_allzerop+0x008. Match base+addend and consume the addend
  on a hit; the symbol-target path is byte-identical. run_muldef_evax.sh green
  (incl. weak-first + strong-first self-bind redirect).

All diagnostic scaffolding stripped (IMGACT SIGSEGV handler / IMGACT-MAP probe /
qemu -d int injection) for a clean production activator.

The writer program's post-commit mallocng cleanup crash (free -> free_group ->
free(g->mem) hitting get_meta's `assert(meta->mem==base)` with a NULL group
meta) is a separate mallocng-group-release issue on the alpha-dec-vms substrate,
tracked as bug #4 (blocks vms-fd1); it fires AFTER the content commits and does
not affect the proven landing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

* vms-b14: bounded calloc-family exception in the section-relative weak-override — land the alpha CRTL->RMS ODS-2 proof without regressing N=7

The rung-4 branch's earlier link.c change (base+addend full-target match)
regressed the crtl_rms N=7 gate (gated-green at #958/#959): it dropped the
weak-alias thunk redirects the mallocng heap needs. Reverting to the base-only
redirect greened N=7 but crashed the veneer's calloc. Root-caused via
print-fatal-signals PC capture (gdb isn't in the images): TWO oppositely-signed
strong-sibling relocs that no reloc-local field can separate —

  - decc$_calloc64 (real calloc at $CODE$+0x8, past the offset-0 overridden weak
    __malloc_allzerop): base-only wrongly redirects it onto strong
    __malloc_allzerop+0x8 and calloc a_crash()es (veneer's early 0x4c618 crash).
    Must be LEFT.
  - the mallocng syscall/stdio thunks (decc$munmap/mremap/mmap/__syscall_cp,
    decc$fclose, __stdio_close): base-only correctly redirects them to their
    strong def; leaving them wild-jumps into the fork/execve code region (the
    0x4a354 crash shared by N=7). Must be REDIRECTED.

Both are byte-identical in every reloc field (psect/to_section/addend/type/
site-sym/target-sym/weak/overridden/self_ref), so this applies base-only to
every base-coincident section-relative reloc EXCEPT the precise, structurally-
detected calloc case: base-only's redirect would land inside strong
__malloc_allzerop while the reloc's real target is a distinct sibling. Bounded
workaround pending the weak_alias-granularity export-path fix (vms-f59).

Gates: OVMX/Alpha crtl_rms N=7 = clean sentinel 7 ($STATUS=%X0035A039,
port_ok=1); CRTL->RMS veneer = decc$fopen lands PORTTEST.DAT;1 on the ODS-2
volume, File ID (71,1,0) + full 16/16 blocks, confirmed by an INDEPENDENT
DIRECTORY/FULL reader (un-fakeable). Known-tracked: the veneer image still
signal-11s AFTER the write commits (pre-existing under every link variant; in
the RMS-veneer cleanup path, not exercised by the clean N=7 round-trip) — filed
as a follow-up under vms-fd1; the veneer gate proves the ODS-2 landing
independent of that post-commit crash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Sep 10, 2026
…egression from exact-match

The prior commit's exact base+addend match keyed evax_wredir_apply on the
reference address directly, so it matched ONLY when a reference landed exactly
on an overridden weak's placed base — and DROPPED interior descriptor-field
references (base+8/+16, the linkage/PDSC quads mallocng's group-setup relies
on). That left a split allocator and re-crashed decc$free's get_meta at
DECC$SHR+0x1e354 (NULL group-meta), regressing the crtl_rms N=7 activation gate
AND still failing crtl_rms3 (vms-032's prior "#3 exact-match dropped an
interior-ref redirect" — the identical crash, ruled a linker over-narrowing,
NOT a mallocng bug).

Fix: resolve TOWN — the symbol whose value/code descriptor span contains
base+addend (greatest defined offset <= addend in to_section) — and if TOWN is
an overridden weak, remap onto the strong def PRESERVING the intra-descriptor
offset (addend - town_off): S += (strong_town_addr - town_addr). This forwards
the whole weak-descriptor extent (self-bind entry AND field refs) to mallocng,
while a distinct sole-def sibling (decc$_calloc64/__libc_calloc at a nonzero
offset) is its own TOWN, not an overridden weak, and is left untouched. Retires
both the section-base heuristic and the hardcoded vms-b14 __malloc_allzerop
exception with no per-name special case.

Host EVAX ctests green (run_evax_link/shareable/ximport/gvalfold/vmsrel/read).
Alpha proof: all three heap gates (crtl_rms stdio rung-3 + crtl_rms3 file-op +
crtl_rms N=7) on the rail. Dissolves vms-032 (same root).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013mZSxmiHPcUBuMNiVT5jXw
baron-3dl added a commit that referenced this pull request Sep 11, 2026
Real VAX<->VAX captures show real VMS prepends a Phase IV intra-Ethernet PADDING
field (0x81 = 0x80 padding-present | length 1) before the routing-flags byte on
UNICAST routed data frames; the length prefix counts it (specimen #3: 0x0033 =
51 = 1 pad + 21 rhdr + 29 NSP). OVMX built its long-data header WITHOUT the pad,
so (a) OVMX could not PARSE real VMS's padded unicast NSP frames at all -- a real
receive-side incompatibility -- and (b) OVMX's own unicast frames were not
byte-faithful to the VAX wire.

Fix (clean-room -- the wire format is the uncopyrightable DNA Phase IV routing
pad, the C is OVMX's own):
- dnet_engine_build_data_frame prepends the 0x81 pad after the data-link length
  prefix, before RFLG, and counts it in the LE length prefix. UNICAST only.
- dnet_engine_parse_data_frame strips an optional leading pad field (a byte with
  the 0x80 bit set = (byte & 0x7f) pad bytes) before reading RFLG, tolerating a
  padless legacy frame. REQUIRED so OVMX can parse real VMS's padded frames and
  so OVMX<->OVMX unicast NSP (FAL/DAP COPY, CTERM) still round-trips now that the
  sender pads.
- MULTICAST HELLO / router-hello control frames stay padless (separate build
  path, matching the vms-3be / vms-df5 specimens) -- not touched.

Grounded byte-for-byte on the vms-3be specimen #3 already in test_dnet_nsp.c
(frame prefix 0x0033, pad 0x81). New test_dnet_engine checks: unicast frame
carries 0x81 + RFLG follows; length prefix includes the pad; build(+pad) ->
parse(strip) round-trips the NSP PDU + DSTID/SRCID byte-identical; multicast
HELLO stays padless and is still rejected by parse_data_frame.

Tests: ctest -R vmsdecnet 11/11 green (incl dap/FAL, cterm, nsp, link, fuzz).
DECNETD --self/--nsp/--set-host/--set-host-src-codes/--fal selftests all PASS.

LAB RE-VALIDATION (vms-a70 direction A, isolated vaxlab-3, OVMX 1.42 -> real VMS
VAX1 1.1): the pad is NECESSARY-BUT-NOT-SUFFICIENT for the a70-A goal. With this
binary OVMX's CI routing header is now BYTE-IDENTICAL to the accepted real oracle
(81 2e 00 00 <DSTID> 00 00 <SRCID> 00 00 00 00) and NSP CI header identical, yet
real VAX1 STILL silently discards the Connect Initiate (writes_recv=0, no
conn-confirm, zero error counters). The pad hypothesis is REFUTED as the sole
blocker -- the same pattern the connect-data hypothesis hit earlier. a70-A
conn-confirm remains OPEN; full evidence + ranked next-direction in the lab
record a70-A-PADFIX-RESULT.txt (prime suspect: the initial NSP link-service PDU
both accepted real CIs emit and OVMX does not). never-crash-a-peer: PASS.

This change lands as a faithfulness + receive-side correctness fix, NOT as the
a70-A conn-confirm fix. Stacks on / supersedes #1133 (carries the base UIC fix
until it merges standalone).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4csyFSMUsS8k1D2MgMxk2
baron-3dl added a commit that referenced this pull request Sep 11, 2026
…81) (#1134)

Real VAX<->VAX captures show real VMS prepends a Phase IV intra-Ethernet PADDING
field (0x81 = 0x80 padding-present | length 1) before the routing-flags byte on
UNICAST routed data frames; the length prefix counts it (specimen #3: 0x0033 =
51 = 1 pad + 21 rhdr + 29 NSP). OVMX built its long-data header WITHOUT the pad,
so (a) OVMX could not PARSE real VMS's padded unicast NSP frames at all -- a real
receive-side incompatibility -- and (b) OVMX's own unicast frames were not
byte-faithful to the VAX wire.

Fix (clean-room -- the wire format is the uncopyrightable DNA Phase IV routing
pad, the C is OVMX's own):
- dnet_engine_build_data_frame prepends the 0x81 pad after the data-link length
  prefix, before RFLG, and counts it in the LE length prefix. UNICAST only.
- dnet_engine_parse_data_frame strips an optional leading pad field (a byte with
  the 0x80 bit set = (byte & 0x7f) pad bytes) before reading RFLG, tolerating a
  padless legacy frame. REQUIRED so OVMX can parse real VMS's padded frames and
  so OVMX<->OVMX unicast NSP (FAL/DAP COPY, CTERM) still round-trips now that the
  sender pads.
- MULTICAST HELLO / router-hello control frames stay padless (separate build
  path, matching the vms-3be / vms-df5 specimens) -- not touched.

Grounded byte-for-byte on the vms-3be specimen #3 already in test_dnet_nsp.c
(frame prefix 0x0033, pad 0x81). New test_dnet_engine checks: unicast frame
carries 0x81 + RFLG follows; length prefix includes the pad; build(+pad) ->
parse(strip) round-trips the NSP PDU + DSTID/SRCID byte-identical; multicast
HELLO stays padless and is still rejected by parse_data_frame.

Tests: ctest -R vmsdecnet 11/11 green (incl dap/FAL, cterm, nsp, link, fuzz).
DECNETD --self/--nsp/--set-host/--set-host-src-codes/--fal selftests all PASS.

LAB RE-VALIDATION (vms-a70 direction A, isolated vaxlab-3, OVMX 1.42 -> real VMS
VAX1 1.1): the pad is NECESSARY-BUT-NOT-SUFFICIENT for the a70-A goal. With this
binary OVMX's CI routing header is now BYTE-IDENTICAL to the accepted real oracle
(81 2e 00 00 <DSTID> 00 00 <SRCID> 00 00 00 00) and NSP CI header identical, yet
real VAX1 STILL silently discards the Connect Initiate (writes_recv=0, no
conn-confirm, zero error counters). The pad hypothesis is REFUTED as the sole
blocker -- the same pattern the connect-data hypothesis hit earlier. a70-A
conn-confirm remains OPEN; full evidence + ranked next-direction in the lab
record a70-A-PADFIX-RESULT.txt (prime suspect: the initial NSP link-service PDU
both accepted real CIs emit and OVMX does not). never-crash-a-peer: PASS.

This change lands as a faithfulness + receive-side correctness fix, NOT as the
a70-A conn-confirm fix. Stacks on / supersedes #1133 (carries the base UIC fix
until it merges standalone).


Claude-Session: https://claude.ai/code/session_01T4csyFSMUsS8k1D2MgMxk2

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Sep 11, 2026
…ates-connection (37→36)

kernel-core/vms_scs.c gets a real injected negative control (§1 37->36).

Defect scs-cdt-snapshot-fabricates-connection: vms_scs_cdt_snapshot()'s
`cl->scs == NULL` guard returns SS__NORMAL instead of SS__NOSUCHDEV (range-scoped,
the single return at :750), so every CDL index answers SS__NORMAL — a placeholder
connection reported live; memset(out,0) precedes it so rows read all-zero, i.e.
local_conid==0 ("not bound yet") projected as real. INV-6 fabrication. suites_red:
test_kmod_cluster_conn_diag; require_fail "row CDT, index far past any CDL:
SS$_NOSUCHDEV, not a crash" + knock_on "every projected CDT row carries a real
minted Local Con.ID, never 0 ..." (both anchored).

Static-proven (host): selftest injects + idempotent-teeth; §1 drops vms_scs.c
(37->36) + clears test_kmod_cluster_conn_diag; dash -n clean. Per-defect QEMU
falsification rides the batched local rail run + the CI negctl shard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMDFjHCqxDuXgbyxNU572h
baron-3dl added a commit that referenced this pull request Sep 12, 2026
…ates-connection (37→36)

kernel-core/vms_scs.c gets a real injected negative control (§1 37->36).

Defect scs-cdt-snapshot-fabricates-connection: vms_scs_cdt_snapshot()'s
`cl->scs == NULL` guard returns SS__NORMAL instead of SS__NOSUCHDEV (range-scoped,
the single return at :750), so every CDL index answers SS__NORMAL — a placeholder
connection reported live; memset(out,0) precedes it so rows read all-zero, i.e.
local_conid==0 ("not bound yet") projected as real. INV-6 fabrication. suites_red:
test_kmod_cluster_conn_diag; require_fail "row CDT, index far past any CDL:
SS$_NOSUCHDEV, not a crash" + knock_on "every projected CDT row carries a real
minted Local Con.ID, never 0 ..." (both anchored).

Static-proven (host): selftest injects + idempotent-teeth; §1 drops vms_scs.c
(37->36) + clears test_kmod_cluster_conn_diag; dash -n clean. Per-defect QEMU
falsification rides the batched local rail run + the CI negctl shard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMDFjHCqxDuXgbyxNU572h
baron-3dl added a commit that referenced this pull request Sep 12, 2026
…ates-connection (37→36)

kernel-core/vms_scs.c gets a real injected negative control (§1 37->36).

Defect scs-cdt-snapshot-fabricates-connection: vms_scs_cdt_snapshot()'s
`cl->scs == NULL` guard returns SS__NORMAL instead of SS__NOSUCHDEV (range-scoped,
the single return at :750), so every CDL index answers SS__NORMAL — a placeholder
connection reported live; memset(out,0) precedes it so rows read all-zero, i.e.
local_conid==0 ("not bound yet") projected as real. INV-6 fabrication. suites_red:
test_kmod_cluster_conn_diag; require_fail "row CDT, index far past any CDL:
SS$_NOSUCHDEV, not a crash" + knock_on "every projected CDT row carries a real
minted Local Con.ID, never 0 ..." (both anchored).

Static-proven (host): selftest injects + idempotent-teeth; §1 drops vms_scs.c
(37->36) + clears test_kmod_cluster_conn_diag; dash -n clean. Per-defect QEMU
falsification rides the batched local rail run + the CI negctl shard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMDFjHCqxDuXgbyxNU572h
baron-3dl added a commit that referenced this pull request Sep 13, 2026
The adhoc runtime proof (fixed rail, pristine 124 green) rejected 2 of the 6
re-anchors that static selftest had passed:
  - register-continue-identity-dropped: reddens test_kmod_exit, NOT its
    declared test_syssvc_identcont (the module/BGn refactor moved the identity-
    continuation path; suites_red/require_fail no longer match the reddening
    site).
  - bg-recv-length-zeroed: the re-anchor lands on a boot-fatal line (post
    core/rind split) -- injecting it CRASHES the guest before the suites run.
Both reverted to origin/main (they were dead anchors before, so zero
regression) and deferred to a follow-on for careful re-scoping/non-fatal
re-anchoring. This ships the 4 runtime-PROVEN re-anchors (exact-red on the
fixed rail): lock-deq-status-wrong (18), run-detached-not-detached (2),
bgsock-poll-always-ready (1), bgsock-getname-addr-zeroed (2). Dead-anchor
FAIL set 6 -> 2. Runtime-proof-over-static catch #3 this session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMDFjHCqxDuXgbyxNU572h
baron-3dl added a commit that referenced this pull request Sep 13, 2026
The adhoc runtime proof (fixed rail, pristine 124 green) rejected 2 of the 6
re-anchors that static selftest had passed:
  - register-continue-identity-dropped: reddens test_kmod_exit, NOT its
    declared test_syssvc_identcont (the module/BGn refactor moved the identity-
    continuation path; suites_red/require_fail no longer match the reddening
    site).
  - bg-recv-length-zeroed: the re-anchor lands on a boot-fatal line (post
    core/rind split) -- injecting it CRASHES the guest before the suites run.
Both reverted to origin/main (they were dead anchors before, so zero
regression) and deferred to a follow-on for careful re-scoping/non-fatal
re-anchoring. This ships the 4 runtime-PROVEN re-anchors (exact-red on the
fixed rail): lock-deq-status-wrong (18), run-detached-not-detached (2),
bgsock-poll-always-ready (1), bgsock-getname-addr-zeroed (2). Dead-anchor
FAIL set 6 -> 2. Runtime-proof-over-static catch #3 this session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMDFjHCqxDuXgbyxNU572h
baron-3dl added a commit that referenced this pull request Sep 13, 2026
…aduation) (#1215)

* vms-387: re-anchor 6 drifted negctl defects to current source (pre-graduation)

6 existing negctl defects had apply_edit sed anchors that no longer matched
main (source drifted → injection lands nothing → BROKEN FIXTURE in selftest),
leaving the coverage/selftest gate red. Re-anchored each to the current
source, SAME semantic mutation (per each defect's `why`), require_fail/
suites_red unchanged:
  lock-deq-status-wrong (vms_lock.c: args.status now via proxy_st)
  run-detached-not-detached (sys_process.c: detached term now `|| interactive`)
  register-continue-identity-dropped (vms_module.c: braceless-if → {} to dodge -Werror=empty-body)
  bg-recv-length-zeroed / bgsock-poll-always-ready / bgsock-getname-addr-zeroed
    (BGn core/rind split vms-9951 MOVED the files: targets kernel/vms_bg.c ->
     kernel-core/vms_bg.c and kernel/vms_bg_pollfd.c; corrected targets+prose,
     no semantics change; bgsock-getname scoped to its ioctl to avoid the now-
     shared exec_socket_getname() seam's 3 other callers)

selftest: all 6 drop off the "sed anchor no longer matches" FAIL list; 0
dead-anchors remain; bash -n clean; only facility_defects.sh changed. Bonus:
kernel/vms_bg_pollfd.c also drops off the "no negative control" list (side
effect of the corrected targets). Runtime redden proof via negctl-adhoc
(following, on the vms-926-fixed rail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMDFjHCqxDuXgbyxNU572h

* vms-387: revert 2 bad re-anchors (runtime-caught), ship the 4 proven

The adhoc runtime proof (fixed rail, pristine 124 green) rejected 2 of the 6
re-anchors that static selftest had passed:
  - register-continue-identity-dropped: reddens test_kmod_exit, NOT its
    declared test_syssvc_identcont (the module/BGn refactor moved the identity-
    continuation path; suites_red/require_fail no longer match the reddening
    site).
  - bg-recv-length-zeroed: the re-anchor lands on a boot-fatal line (post
    core/rind split) -- injecting it CRASHES the guest before the suites run.
Both reverted to origin/main (they were dead anchors before, so zero
regression) and deferred to a follow-on for careful re-scoping/non-fatal
re-anchoring. This ships the 4 runtime-PROVEN re-anchors (exact-red on the
fixed rail): lock-deq-status-wrong (18), run-detached-not-detached (2),
bgsock-poll-always-ready (1), bgsock-getname-addr-zeroed (2). Dead-anchor
FAIL set 6 -> 2. Runtime-proof-over-static catch #3 this session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMDFjHCqxDuXgbyxNU572h

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants