Skip to content

docs: executive retrofit dispatch plan (vms-6b8) - #2

Merged
baron-3dl merged 2 commits into
mainfrom
worktree-executive-retrofit
Jul 29, 2026
Merged

baron-3dl merged 2 commits into
mainfrom
worktree-executive-retrofit

Conversation

@baron-3dl

Copy link
Copy Markdown
Contributor

Dispatch-ready decomposition of the executive gap. Plan doc only — no code. The worktree exists so the tree below can be dispatched against it.

Full map: docs/design-executive-retrofit.md.

Why the ordering is what it is

No CI job ever loads vms.ko. persistent-boot runs its QEMU script inside a Docker container, and there is no kernel-module job (src/kernel/ is excluded even from static analysis). Docker has no /dev/vms, and CI runs in Docker — so every executive facility that couldn't be tested defaulted to a per-process userspace fake that reports success.

The architecture drifted to fit the test harness. That is why only sys_lock.c was ever wired, and it's why Phase 0 is a hard barrier: start anywhere else and the increment rots back into the fake, because that's the only path CI exercises.

The tree

Phase Items Domain
0 — enabling vms-e4d QEMU CI job loads vms.ko QA
1 — wire what exists vms-ef1 common event flag clusters · vms-as1 ASTs · vms-pv1 privileges Systems
2 — remove the cause vms-71a migrate Docker CI jobs, delete Dockerfile QA
3 — extend vms-pt1 process table · vms-ln0 LNM ruling → vms-d37 LNM · vms-dv1 devices · vms-mb1 mailboxes Systems
4 — retire fakes vms-fk1 delete the fakes, gate forbids return Systems

Phase 1 is cheap and real: vms.ko already implements event flags (including VMS common clusters via vms_common_ef_lock), ASTs, and access modes — ioctls 0x010x27. Userspace simply never calls them. That's wiring, not design.

Dispatchable right now

Dependencies are wired so exactly three items are ready, verified against rd ready:

  • vms-e4d — the Phase 0 barrier. Everything else waits on it.
  • vms-ln0 — the one genuine design fork left. Logical-name translation sits on the hot path of every file open, so ioctl-per-translation is not automatically right even though the rest of the executive is kernel-side. Operator-gated; wants measurement, not opinion.
  • vms-b9fSHOW DEVICE prints the host Linux mount table (volume labels are mount-point basenames, including the kernel version). Independent of the executive entirely; parallel-safe.

Phase 1's three items are parallel-safe with each other once Phase 0 lands — disjoint facilities, separate files, good concurrent worktree dispatch.

Standing constraints, restated on every item

One runtime target (CLAUDE.md Rule 9) · never a silent userspace fallback — fail honestly with SS$_NOSUCHDEV as sys_lock.c does · not done until proven against a real /dev/vms · clean-room (Rule 8) · purity sign-off on VMS values.

Note

Cut from main, so CLAUDE.md Rule 9 and the roadmap §2.1 analysis (both in PR #1) aren't present here. The plan doc is self-contained and doesn't require them.

🤖 Generated with Claude Code

baron-3dl and others added 2 commits July 28, 2026 21:42
Decomposes the executive gap into a dispatch-ready, outcome-scoped tree.

Sequencing is driven by testability, not by the design ruling. No CI job
currently loads vms.ko (persistent-boot runs its QEMU script inside a Docker
container; there is no kernel-module job), so the kernel executive is
unprovable in CI. That is the mechanical cause of the gap: untestable
facilities defaulted to per-process userspace fakes that report success. The
architecture drifted to fit the test harness. Phase 0 is therefore a hard
barrier.

  Phase 0  vms-e4d  QEMU CI job loads vms.ko                        [QA]
  Phase 1  vms-ef1 / vms-as1 / vms-pv1  wire the ioctls that
           ALREADY exist in vms.ko (event flags incl. common
           clusters, ASTs, privileges) — wiring, not design  [Systems]
  Phase 2  vms-71a  migrate Docker CI jobs, delete Dockerfile       [QA]
  Phase 3  vms-pt1 process table, vms-ln0 LNM design ruling ->
           vms-d37 LNM, vms-dv1 devices, vms-mb1 mailboxes    [Systems]
  Phase 4  vms-fk1  delete the fakes; gate forbids their return

Dependencies wired so exactly three items are dispatchable now: vms-e4d
(the barrier), vms-ln0 (the one real design fork left — LNM sits on the hot
path of every file open, so ioctl-per-translation is not automatically
right), and vms-b9f (SHOW DEVICE host-mount-table leak, independent of the
executive entirely).

Standing constraints restated per item: one runtime target (Rule 9), never a
silent userspace fallback (fail honestly with SS$_NOSUCHDEV), not done until
proven against a real /dev/vms, clean-room, purity sign-off on VMS values.

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

Ran the five-pass swarm decomposition. Pass 0 rigor: HEAVY — blast heavy
(kernel + libvms + vmsprocess + vmsdcl + CI, >20 files, >3 packages),
adversarial heavy floor (privileges/access modes are a security surface),
+1 coverage modifier because no CI job loads vms.ko so every touched path is
uncovered. 24 items.

COLD-START HAZARD FOUND AND GATED. Every item referenced artifacts that do not
exist on main — verified with git cat-file: docs/design-executive-retrofit.md
(PR #2), tests/integration/test_runtime_target.sh (PR #1), CLAUDE.md Rule 9
(PR #1), and the roadmap sec 2.1 analysis (PR #1). An agent dispatched cold
would be told to obey Rule 9, find no Rule 9, and re-derive the exact
conclusion this epic exists to correct. Added vms-pre, a human prerequisite
(merge PR #1 + PR #2) that blocks all three DAG entry points. Belt and braces:
every item now also carries its constraints inline, so items remain executable
even if the docs are missing.

Also fixed placeholder item IDs in the dispatch notes (vms-LNM1 etc. never
existed) which would have sent a cold agent chasing dead references.

Added the heavy-rigor layer: concurrent veracity adversaries per wave (vms-vx1,
vms-vx2) written against this project's actual failure history — they attack
the specific cheats available here (stubbing /dev/vms, simulating two processes
with two threads, inheritance-based mailbox passing). Plus reviewers, a
dedicated security review of the privilege surface, five sweeps, and an e2e
that proves the facilities COMPOSE, which is where the original design failed.

Downstream consumers annotated SUBSTRATE-BLOCKED (vms-853, vms-46b, vms-c17,
vms-905) so a cold agent does not mistake them for display work and build a
better-looking fake.

DAG verified sealed: only vms-pre is ready.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@baron-3dl
baron-3dl marked this pull request as ready for review July 29, 2026 02:42
@baron-3dl
baron-3dl merged commit 83d0346 into main Jul 29, 2026
33 checks passed
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 added a commit that referenced this pull request Jul 31, 2026
… the process (vms-2b8) (#34)

* vms-2b8: vms_kif_setident() never bound, so every identity establishment failed

VMS_IOCTL_SETIDENT is by definition the FIRST executive call a process
like LOGINOUT makes -- it is the call that turns a bare task into an
authenticated process. vms_kif_setident() was the only wrapper in the
file that issued its ioctl directly instead of through KIF_CALL, so it
never ran kif_bind(): the calling task was unregistered, vms_module.c
answered -ESRCH, and the establishment failed for every caller.

It also collapsed every delivery failure to a literal SS$_BADPARAM,
which describes the caller's arguments for a condition that has nothing
to do with them. vms_kif_kerr_to_ss() gives the real status.

Found by running the new tests/qemu/test_syssvc_ident.c against a real
/dev/vms: a root process establishing a perfectly valid identity got
status 20. The wrapper landed in the previous round of this item with no
product caller, so nothing had ever exercised it.

* vms-2b8: DCL reads its identity from the executive, not from its environment

The executive has enforced identity since the previous round, and DCL
was still reading the facade: dcl_main.c took the user name, the UIC and
the privilege mask from VMS_USERNAME / VMS_UIC_GROUP / VMS_UIC_MEMBER /
VMS_PRIVILEGES, and dcl_cmd_show.c printed getenv("VMS_PRIVILEGES")
straight out. So the executive enforced one identity while SHOW PROCESS
reported another, and any process could pick the one it reported.

  * dcl_context_init() reads this process's row with
    vms_kif_getjpi_self(). The getpwuid()/"SYSTEM" fallback chain for the
    user name goes with the env reads: both are values the process
    controls, and neither is a VMS identity.
  * SHOW PROCESS, SHOW PROCESS/PRIVILEGES and SHOW PROCESS/QUOTAS read
    the executive AT DISPLAY TIME, not out of a copy taken at startup --
    a cached mask is one this process could have overwritten since.
  * The deleted fabrications, each of which is now a separate assertion:
    the "SYSTEM" user-name fallback, the getgid()/getuid() UIC fallback,
    and the hard-coded "TMPMBX NETMBX" default privilege list.
  * The privilege-name table moves to file scope: two commands decode the
    same mask and only one of them had a table.
  * NO FALLBACK on a failed read. The first vms_kif_* call registers the
    process (kif_bind) and PID 1 refuses to boot without /dev/vms
    (Rule 9), so the failure is unreachable in the one OVMX runtime; the
    command returns the executive's status and prints nothing.

tests/dcl/test_show_process.sh, which ctest runs on a host that has no
executive and never will (Rule 9), is rewritten to assert the ABSENCE
that used to be wrong: its environment is poisoned with SYSTEM/ALL/[1,4]
and SHOW PROCESS must report nothing at all. Each EXPECT_NOT is tied to
one deleted fabrication. The positive proof moves to
tests/qemu/test_syssvc_ident.c, against a real executive.

* vms-2b8: PID 1 and LOGINOUT establish identity through the executive

Two writers, both of which used to declare an identity rather than
acquire one.

PID 1 called vms_pcb_init(0xFFFFFFFFFFFFFFFF) and then
vms_pcb_set_identity(1, [1,4], "SYSTEM", "SYSTEM") -- a process writing
its own user name, its own UIC and every privilege bit in existence into
a structure nothing outside it could see or refuse. The ANSWER was
right; who decided it was the defect. It now reads the SYSTEM record
from SYSUAF (parsed locally: PID 1 must stay statically linked and does
not link libvms) and asks the executive to stamp it. The executive
refuses any caller that does not hold SETPRV, and SETPRV is derived in
vms.ko from capable(CAP_SYS_ADMIN) -- a credential no process can grant
itself. Failure is FATAL: there is no VMS in which the system process
has no identity, so the condition is made unreachable rather than
handled (Rule 10). It runs after provision_seed_files() because SYSUAF
is one of the files that seeds.

LOGINOUT authenticated against SYSUAF and then published the result in
four ORDINARY ENVIRONMENT VARIABLES that DCL believed. It now calls
vms_kif_setident(), and the row survives the execl() because the
executive keys it on the thread-group id -- so the DCL image reads the
identity LOGINOUT proved without any environment variable carrying it.
VMS_UIC_GROUP / VMS_UIC_MEMBER / VMS_PRIVILEGES are deleted; they have
no readers left.

VMS_USERNAME REMAINS AND IS STILL A FACADE, left loud rather than silent
(item scope note 3). Its last reader is tools/vms_mail.c, which picks
whose mailbox to open with it, so a user can still read another user's
mail by setting it. Deleting it here without converting MAIL would break
MAIL rather than fix the hole, and MAIL is outside this item's scope --
it is reported, not quietly carried.

* vms-2b8: prove SHOW PROCESS reports the executive's identity, on the real runtime

The subject is the user-visible command, run for real. DCL.EXE is staged
into the QEMU initramfs at /bin (fatal if missing, and under /bin not
/tests so init.sh does not exec it as a suite -- the mechanism vms-8019
built for the same purpose), and tests/qemu/test_syssvc_ident.c drives
it against a real /dev/vms.

Every DCL process it starts is exec'd with VMS_USERNAME=SYSTEM,
VMS_PRIVILEGES=ALL, VMS_UIC_GROUP=1, VMS_UIC_MEMBER=4 -- the most
privileged identity on the system, in exactly the four variables the
deleted code read. If any of those reads comes back, the suite goes red.

A-writes / B-reads: the identity is established by one program and
reported by a DIFFERENT one, after execve replaced the image. Nothing
crosses that boundary but the executive's row -- and the environment
carries a contradicting claim that must lose.

  A  a privileged writer establishes FIELD [200,10] with OPER: SHOW
     PROCESS reports FIELD, [310,012] and EXACTLY "NETMBX OPER TMPMBX",
     and SETPRV -- which the writer held and the identity it established
     did not -- is gone. The drop is one-way.
  B  a second process, SAME binary, SAME poisoned environment, reports
     OPERATOR [1,6] with SYSPRV and not OPER. No property of the image or
     of the environment can explain two different answers.
  C  an unprivileged process (real setgid+setuid, not a flag) claims
     SYSTEM/ALL both ways: the ioctl is refused SS$_NOPRIV, and SHOW
     PROCESS reports the UIC the executive derived from its real
     credentials, [454,1751], with exactly NETMBX and TMPMBX.

Privilege absence is asserted by each privilege's oracle description
rather than its NAME: "OPER" is a substring of the user name "OPERATOR",
and a name-substring check would have been unfalsifiable.

In the CI negative-control rig (booted with no executive) it asserts the
property that survives vms-0ff's ruling -- with nothing to read, DCL
reports no identity rather than falling back to the environment -- then
exits 77, per the contract in ci.yml.

The UAT gains one assertion: PID 1's boot log must carry the identity
the executive established from SYSUAF. Whole-log grep and safe as one --
it is printed before the login prompt exists, so the script cannot have
typed it.

* vms-2b8: LOGINOUT becomes the authenticated user, so the refusal survives fork

The veracity adversary disproved this branch's central claim by execution.
"The drop is one-way" was true only within one thread group. A session that
setident'd down to FIELD/[200,10]/TMPMBX|NETMBX|OPER forked a child; the child
re-registered, re-derived SETPRV from the CAP_SYS_ADMIN it still held, and
stamped itself SYSTEM [1,4] with all 37 privileges. A privilege reduction
survived exactly until the next fork.

The root cause was not in the executive. Nothing in the login path ever dropped
Linux credentials -- `grep -n 'setuid|setgid' tools/vms_login.c src/ovmx_init/*.c
src/vmsdcl/*.c` returned nothing -- so every DCL session and every process it
spawned ran as root with CAP_SYS_ADMIN, and vms_proc_register() hands a
CAP_SYS_ADMIN task CMKRNL|CMEXEC|SETPRV|WORLD at registration. The executive was
enforcing identity on top of a process that could sidestep it by forking.

LOGINOUT now becomes the user: setgroups(0)/setgid/setuid to the SYSUAF UIC,
after VMS_IOCTL_SETIDENT (which needs the SETPRV root-derived registration
granted) and before execl. Fatal if it fails. SYS$LOGIN is chowned to that UIC
first, while the privilege to do it still exists.

This also makes RMS and the executive agree. src/vmsrms/rms_core.c enforces file
protection against [gid,uid]; the executive derives proc->uic the same way.
Before this they disagreed for every session -- the executive reported the SYSUAF
UIC while RMS saw root's [0,0].

Not presented as a VMS behaviour (Rule 8/10): OpenVMS has no Linux credentials.
The uid/gid pair is already OVMX's substrate for the UIC; what changes is only
that the substrate is made to agree with the identity being enforced.

%LOGINOUT-W-NOIDENT deleted (Rule 10). It authenticated a user against SYSUAF and
then handed them a session with no identity -- a state VMS has no equivalent for,
so the condition is made unreachable (fatal) rather than handled. It also
degraded UPWARD: such a session kept root's UIC [0,0] and the whole enforced
mask, strictly more privilege than the SYSUAF record grants. The message wore the
LOGINOUT facility with an invented code; the replacement is %OVMX-F-NOIDENT,
the same reasoning ovmx_init.c uses for %OVMX-I-EXEC. Unreachable rather than
merely refused: PID 1 halts at boot without the executive (vms-0ff).

The vms_ioctl_setident() comment claiming "no sequence of calls walks a process
back up" is corrected to state the guarantee's real scope, and to say plainly
that a CAP_SYS_ADMIN task is outside the model.

PROVEN, on the real bootable image: the UAT drives a SYSTEM session and runs
SPAWN SHOW PROCESS. The subprocess registers on its own and the executive derives
its UIC from real Linux credentials, so its UIC is a direct readout of what the
session runs as: [001,004] with the drop, [000,000] without. 16/16.
DISCRIMINATION: deleting the three drop calls from vms_login.c and rebuilding the
bootable image gives [000,000] and exactly those two assertions go red (14/2).

* vms-2b8: SHOW PROCESS prints what the oracle prints -- measured, not assumed

Round 5 was challenged for pinning byte-exact test assertions on output that
contradicts this repo's OWN oracle capture. It did. VAX1 was booted again and the
commands were run through cat -A so columns were counted rather than eyeballed.
New docs/oracle/vax73-privileges.md section 6 carries the verbatim transcripts.

MEASURED: plain SHOW PROCESS on OpenVMS VAX V7.3 prints NOTHING about
privileges. No Privileges: line, no summary of any kind -- privileges live behind
/PRIVILEGES, in two named blocks. OVMX's Privileges: line is therefore an
invention, and round 5 had begun asserting its exact bytes. Rule 10: there is no
VMS behaviour to reproduce, so the line is DELETED, not reformatted. The
whole-mask assertions move to the Authorized privileges: grid, whose format the
oracle does pin -- a stricter check, not a weaker one.

MEASURED: the Process privileges: line format is " %-20s %s" (counted:
21 columns before the description, for both ACNT and IMPERSONATE). The code
printed " %-16s %s" while a comment three hundred lines above it claimed the
format was the oracle's " %-20s %s". Corrected.

ADDED: the Authorized privileges: block, which the oracle prints and OVMX did
not print at all. It is printable now for the first time because the executive
holds both masks; before the executive owned identity there was only one number
to show. The grid is reproduced including its defect -- 8 columns of exactly 10
characters, so IMPERSONATE (11 chars) is CLIPPED to IMPERSONAT and collides with
the next cell. VMS does that. A short final row is not padded, which is what the
capture shows.

DELETED: " (no privileges enabled)". With an empty mask the oracle prints the
heading and nothing under it (section 5.2). A sentence VMS does not emit is an
invention however helpful it reads.

MEASURED AND DELIBERATELY NOT ACTED ON, recorded in section 6 so the item that
does fix SHOW PROCESS's layout starts from a measurement: plain SHOW PROCESS also
has no quota block (that is /QUOTAS), uses a 20-column label field where OVMX
uses 19, shows the rights identifier [SYSTEM] rather than the octal UIC, carries
a Node: field, and has a Devices allocated: section. None of those is about
identity ownership, and (4) needs RIGHTSLIST support OVMX does not have.

Lab left clean: @SYS$SYSTEM:SHUTDOWN to SYSTEM SHUTDOWN COMPLETE, then quit.

* vms-2b8: two scenarios the old test could not express -- fork escalation, B reads A

D. A REAL SESSION'S SUBPROCESS. LOGINOUT's sequence step for step: stamp the
authenticated identity, drop the Linux credentials to that UIC, then spawn. The
subprocess claims SYSTEM with every privilege and must be refused. This is the
adversary's exploit, now asserted as a test.

Measured against a real /dev/vms under QEMU: SESSION_SETIDENT=1,
SESSION_UID=10 SESSION_GID=200, SUB_REGISTER=1, SUB_SETIDENT=36 (SS$_NOPRIV), and
the subprocess's own DCL reports an empty user, UIC [310,012] and exactly
NETMBX TMPMBX -- no SETPRV, no CMKRNL, no WORLD.
DISCRIMINATION: deleting the three drop calls from the scenario reproduces the
exploit exactly -- SESSION_UID=0, SUB_SETIDENT=1, DCL prints "User: SYSTEM",
[001,004] and all 37 privileges including SETPRV/BYPASS/CMKRNL. 7 D assertions
red, A/B/C/E untouched. So the executive's refusal and the credential drop are
load-bearing only together, and the test says so.

E. A-WRITES / B-READS IN ITS TRUE FORM (Rule 11). Scenarios A-D cross execve,
which proves the row is not in the image and not in the environment, but it is
still one task's row read by whatever that task became -- it cannot detect a
per-task derivation error, which is the defect class D exists for. Here process B
reads process A's row while A is alive and blocked. Neither runs the other's code
and the only channel between them is the executive's table.
A = PAYROLL [7,3] TMPMBX|NETMBX|SYSPRV, B = AUDITOR [7,9] TMPMBX|NETMBX. Same UIC
group on purpose: per the oracle (section 5.2) a same-group $GETJPI needs no
privilege, so the read cannot be explained by B being privileged. Sequenced by
blocking pipe reads in both directions, never by sleeps -- the parent does not
release A until B has reported, so "A was alive" is ordering, not timing.
Measured: B_SELF=AUDITOR/00070009/...108000, B_READ=PAYROLL/00070003/...10108000.
DISCRIMINATION: making VMS_JPI_SEL_PID resolve to the caller -- the per-process
facade shape -- gives B_READ=AUDITOR and turns exactly that one assertion red.

Also corrected: scenario A's assertion used to end "and the drop is one-way",
which was false as a product property. It now claims only the narrow true thing.
And the three whole-mask assertions move off the deleted Privileges: line onto
the oracle-pinned Authorized privileges: grid.

Final: QEMU 17 suites / 0 failed, test_syssvc_ident 33 passed / 0 failed.
Negative control still red (3 passed / 14 failed, rc=1). ctest 40/40, 0 skipped.

* vms-2b8: the VMS tree was installed as root, so no VMS user owned anything

REPRODUCTION, on the real bootable image: log in as SYSTEM and

    $ COPY LOGIN.COM ADVPROBE.TXT
    %RMS-E-CRE, cannot create - ADVPROBE.TXT

A user could log in and then not create a file -- in their own login
directory. This is a regression introduced by round 6 of this item, and no
test in the suite wrote a file, which is why it shipped.

ROOT CAUSE, and it is NOT the protection check. Until LOGINOUT began
dropping to the authenticated user's credentials, every VMS session on OVMX
ran as Linux root, so ownership was irrelevant: root passes every DAC check.
PID 1 installs the whole VMS tree with mkdir()/copy as root, so the instant a
session genuinely became UIC [1,4] there was nothing on the system that the
SYSTEM account owned, and the Linux kernel refused the open(2). DCL's COPY
does not even go through RMS -- src/vmsdcl/dcl_cmd_file.c calls fopen()
directly -- so the VMS protection check never ran on this path at all.

PROVEN BY MUTATION, both directions, each a full image rebuild and boot:
  - delete provision_ownership() + the home-directory own_object() below:
    the UAT goes 23/7 with EXACTLY the seven new write assertions red and
    `%RMS-E-CRE, cannot create - UATWRITE.TXT` back on the console.
  - the same mutant PLUS vms$check_access() rewritten to `return 1`
    unconditionally (grant everything): identical failure, same seven
    assertions, same messages. The RMS protection check has nothing to do
    with it; the enforcer is the Linux DAC on the inode.

THE FIX, pinned to the oracle rather than chosen (CLAUDE.md Rule 10). Measured
on VAX2 of ~/vax/cluster, OpenVMS VAX V7.3, 30-JUL-2026 -- full transcripts
added to docs/oracle/vax73-privileges.md S7:

    SYSEXE.DIR;1         [SYSTEM]                         (RWE,RWE,RE,RE)
    SYSLIB.DIR;1         [SYSTEM]                         (RWE,RWE,RE,RE)
    LOGINOUT.EXE;1       [SYSTEM]                         (RWED,RWED,RWED,RE)
    SYS0.DIR;1           [SYSTEM]                         (RWE,RWE,RE,RE)

Two facts, and they are a pair: the system tree is owned by SYSTEM, and WORLD
gets R+E and no W. So on VMS the SYSTEM account can create and delete in
SYS$SYSTEM: and an ordinary user cannot -- which is exactly what OVMX now
does. provision_ownership() gives [SYS0] and everything under it to SYSTEM's
UIC, and provision_sysuaf_users() gives each account's login directory to
that account's UIC.

WHICH UIC IS NOT HARDCODED. It is read out of SYSUAF's SYSTEM record -- the
same record LOGINOUT authenticates against and the same one
establish_system_identity() hands to the executive. If those disagreed, file
protection would be enforced against a different identity than the executive
holds, which is the defect this whole item exists to delete. The numeric
value of [SYSTEM] was not pinned on the oracle (DIRECTORY/OWNER prints the
translated identifier name), and nothing here depends on it.

lchown() and not chown(): in overlay mode SYS$SYSTEM holds symlinks into
/usr/local/bin and re-owning a symlink must not re-own the Linux binary.

DISCLOSED DIVERGENCE, not handled: VMS grants the SYSTEM category to every
UIC group <= MAXSYSGROUP (measured 8). A Linux inode carries one owning
group, so OVMX can express "group 1 owns this" but not "groups 1..8 are all
system". SYSUAF ships no account in groups 2..8, and inventing a second
enforcement layer to paper over it would be worse than the gap.

* vms-2b8: LOGINOUT does not re-own SYS$LOGIN, so it cannot fail to

Round 6 deleted %LOGINOUT-W-NOIDENT on the grounds that VMS has no state in
which LOGINOUT authenticates a user and then hands them a broken session, so
the condition must be made unreachable rather than handled -- and then added
%OVMX-W-LOGINOWN forty lines above it, a new non-fatal self-certified
diagnostic for the case where LOGINOUT cannot give the user their own
SYS$LOGIN. That is the same shape and the same defect: CLAUDE.md Rule 10's
illegal third answer, a plausible-looking handler for a condition VMS never
faces. Under the [gid,uid] protection this branch's credential drop
activates, a session that reaches that warning cannot write its own login
directory -- it is the same bug as the %RMS-E-CRE regression, wearing a
warning instead of an error.

The argument that killed NOIDENT is applied here: the condition is REMOVED,
not promoted to fatal. LOGINOUT no longer creates or re-owns the directory at
all. That is also what OpenVMS does -- the System Manager's Manual add-user
procedure is AUTHORIZE ADD followed by CREATE/DIRECTORY .../OWNER=[g,m], and
LOGINOUT is not in that sequence and has no fixup step of its own. On OVMX
the equivalent provisioning is PID 1's provision_sysuaf_users() and
provision_ownership(), which run before any other process exists.

No behaviour is lost: the UAT logs in as SYSTEM and as GUEST and both write
files in their own login directories, which is what the deleted mkdir/chown
was there to guarantee.

* vms-2b8: the SYSTEM protection category is a group test, not "uid 0"

src/libvms/syssvc/sys_security.c selected the SYSTEM protection category with
`if (uic == 0)`, commented "UID 0 (root) is treated as SYSTEM", in both
sys$chkpro() and vms$check_access(). OpenVMS has no root and [0,0] is not a
valid UIC; that rule was invented for the OVMX substrate. While every VMS
session ran as Linux root it was also inert -- caller_uic 0 equalled the
owner_uic 0 of every root-created file, so the owner branch would have
answered the same. Once LOGINOUT drops to the authenticated UIC, SYSTEM's
[1,4] stops matching it and lands on the WORLD nibble of every file in the
VMS tree: OVMX denying what VMS grants.

The documented VMS rule is a comparison, not an equality: the SYSTEM category
covers every UIC whose GROUP is <= MAXSYSGROUP (OpenVMS Guide to System
Security, "System" access category). MAXSYSGROUP measured on the oracle,
transcript in docs/oracle/vax73-privileges.md S7.2:

    $ MCR SYSGEN SHOW MAXSYSGROUP
    MAXSYSGROUP                     8          8         1     32768 UIC Group  D

Root's [0,0] is covered incidentally (0 <= 8) rather than by a rule of its
own, so PID 1 and the daemons are unaffected.

tests/libvms/test_protection.c is new and is the discriminating test: restore
`uic == 0` and its first three assertions fail while every other case still
passes (verified by running exactly that mutation). It pins both sides of the
MAXSYSGROUP boundary (group 8 is system, group 9 is not) and the refusal an
ordinary user gets against a system-owned file, so the change cannot be
mistaken for a blanket grant.

THIS IS NOT WHAT FIXED THE %RMS-E-CRE REGRESSION, and it is committed
separately so that is not misread. The regression is a Linux DAC refusal on
an inode nothing in VMS owned; rebuilding the image with vms$check_access()
returning 1 unconditionally reproduces it unchanged. This commit removes a
different defect -- an invented rule sitting where a documented one belongs.

NOT ADDED, deliberately: SYSPRV, BYPASS, READALL and GRPPRV. VMS grants the
SYSTEM category to SYSPRV holders, everything to BYPASS and read to READALL,
but on OVMX the decision this function feeds is re-taken immediately
afterwards by the Linux kernel's DAC check on the same inode, which has no
notion of a VMS privilege and denies what the privilege term would have
granted. Implementing them would produce a function that reports enforcement
it does not have -- which this item's own text calls out as worse than an
absent one. Reported instead.

* vms-2b8: the UAT never wrote a file, which is why the regression shipped

All sixteen commands the UAT drove were read-only except DEFINE, which is an
in-memory process logical. So a change that made every VMS user unable to
create a file went green. This adds the missing half and a second session to
make it discriminating.

SESSION 1 (SYSTEM) now copies a file into its own default directory and into
SYS$SYSTEM:, reads both back, deletes the SYS$SYSTEM: one, and checks it is
gone from the directory listing.

SESSION 2 IS NEW: log out and log in again as GUEST, the least privileged
account SYSUAF ships (UIC [200,201], TMPMBX only). Without it the file
assertions are not evidence of an access control system -- a system where
EVERYONE can write SYS$SYSTEM: passes every assertion session 1 makes. GUEST
must be able to read the system tree and write its OWN login directory, and
must NOT be able to write SYS$SYSTEM:; all three are asserted, because any
one alone is satisfied by a broken system.

DISCRIMINATION VERIFIED by mutation on the real bootable image: deleting the
ownership provisioning takes the UAT to 23 passed / 7 failed with exactly the
seven write assertions red. The GUEST refusal stays green in that mutant,
which is correct -- it is not what the fix changes.

EXISTENCE IS PROVEN BY TYPE, NOT DIRECTORY. Measured while writing these
assertions: `DIRECTORY <one named file>` answers "Total of 0 files" even for
a file that certainly exists (`DIRECTORY SYS$MANAGER:LOGIN.COM` -> 0 files,
while `TYPE SYS$MANAGER:LOGIN.COM` prints it). That is a real DCL defect,
reported separately; a test leaning on it would be asserting against a broken
observer. Nor is anything asserted on OVMX's error text -- OVMX prints
%RMS-E-CRE where VMS prints %COPY-E-OPENOUT with -RMS- and -SYSTEM-
secondaries, and asserting the OVMX wording would certify a message this item
never measured (Rule 10).

KNOWN DIVERGENCE NOW ASSERTED OUT LOUD (vms-afd). `SPAWN SHOW PROCESS` prints
"User:" followed by nothing: vms_module.c zeroes proc->username at
registration and inherits nothing, so every spawned process in the product
reports blank, while the oracle's SPAWN answers "%DCL-S-SPAWNED, process
SYSTEM_1 spawned" -- VMS has no process without a user name. The two UIC
assertions round 6 added read that same display and said nothing about it.
The fix is $CREPRC identity propagation (vms-afd, entangled with vms-8019)
and is not built here, but the blank is now PINNED by an assertion, so the
day SPAWN inherits a name this line goes red and whoever lands it has to come
and delete it. Silence is what makes a facade survive.

Two harness fixes found by running it:
  - the session-2 waits are anchored to the offset captured BEFORE 'LOGOUT'
    is sent, not after. PID 1 prints the next 'Username:' in the same breath
    as the logout message, so an offset taken after wait_for 'logged out'
    returns (it polls at 250ms) has already skipped past the prompt, and the
    wait times out with the prompt in plain sight. Measured, not reasoned.
  - SESSION_TIMEOUT is computed from the actual length of the two command
    arrays instead of a hand-maintained "* 16" that drifts on every edit.

* vms-2b8 round 3: fix privilege-surface disagreement, a vacuous protection
test, a false cardinal, and an unrun UAT rewrite

Three high-severity defects found by execution (see the dispatch report):

1. F$PRIVILEGE (src/vmsdcl/dcl_lexical.c) answered from the executive's
   RAW cur_privs, unmasked, while SHOW PROCESS/PRIVILEGES was already
   masked to VMS_PRV_M_ENFORCED (operator ruling 2026-07-31). Measured:
   a SYSTEM session authorized OPER by SYSUAF showed empty privilege
   blocks in SHOW PROCESS/PRIVILEGES while F$PRIVILEGE("OPER") answered
   "TRUE" in the same process -- OVMX advertising a privilege it cannot
   enforce, through a surface DCL scripts branch on. lex_privilege() now
   masks to VMS_PRV_M_ENFORCED too. Enumerated every privilege-reporting
   surface by measurement (grep across src/vmsdcl, src/libvms, tools/,
   src/vmsssh): SHOW PROCESS/PRIVILEGES (already correct), F$PRIVILEGE
   (fixed here), F$GETJPI (no CURPRIV item implemented at either the DCL
   lexical or sys$getjpi C level -- nothing to report), SET
   PROCESS/PRIVILEGES's feedback (prints nothing on success; the command
   never reaches the executive at all, a separate, already-tracked gap).
   Proven end-to-end on a real QEMU boot: IDENT_OPER = "FALSE" and
   IDENT_SETPRV = "TRUE" for the same SYSTEM session whose SHOW
   PROCESS/PRIVILEGES lists only CMKRNL/CMEXEC/SETPRV/WORLD.

2. tests/libvms/test_protection.c could not fail on the behaviour it
   exists to prove: reverting uic_is_system() to the pre-fix `uic == 0`
   left it byte-identically green, because sys$chkpro reads the CALLER's
   real (gid,uid) with no way to parameterize it, and no UID ctest
   actually runs as (root, or an unprivileged dev/CI user) ever lands in
   the (0, MAXSYSGROUP] band where the two rules disagree. Rewritten to
   synthesize a caller UIC via an unprivileged Linux user namespace
   (CLONE_NEWUSER + a single-line uid_map/gid_map, "deny" on
   setgroups) -- the same mechanism `unshare --user --map-root-user`
   uses, requiring no capability the test process does not already have.
   Proven by the isolating mutation this round: reverting uic_is_system()
   turns exactly the two group-5 SYSTEM-category assertions red and
   nothing else; reverting the fix back to the correct rule turns the
   suite green again. A failed CLONE_NEWUSER setup is now a hard FAILURE,
   not a silent skip (a skipped test is a failing test).

3. Deleted the false cardinal in src/kernel/vms_proctab.c's
   VMS_IOCTL_SETIDENT comment ("the number of root-privileged VMS
   processes ... It is two: PID 1 and pre-authentication LOGINOUT") --
   the same commit that wrote it added VMSSSHD as a third. Replaced with
   a description of the SET and a pointer to the grep that derives its
   current membership, instead of a count nothing forces to stay in
   sync.

Four medium defects:

- A provenance comment in tests/integration/test_show_device_rows.sh
  cited a _Static_assert value for VMS_IOCTL_GETDVI (0xC0505752) that
  does not exist anywhere in the tree; the real value, still asserted in
  src/kernel/vms_ioctl.h, is 0xC0585652 (two digits transposed).
  Corrected to the real value.

- tests/uat/vms_session_qemu.sh was substantially rewritten and not run.
  Built distro/Dockerfile.bootable via podman and ran it against a real
  QEMU boot: 32/34 passed, 2 failed. Both were genuine, both fixed here:
  (a) the check_known_divergence tripwire for vms-afd (empty User: field)
  fired red because the top-level LOGINOUT session's identity is now
  populated -- but `rd show vms-afd` confirms that item is still open;
  the fix is LOGINOUT's own vms_kif_setident() call (this item), unrelated
  to vms-afd's $CREPRC gap, which the SPAWN assertions in the same script
  still correctly prove is open. Replaced the tripwire with a positive
  assertion and an accurate provenance comment. (b) SHOW PROCESS
  /PRIVILEGES's expected pattern, '(TMPMBX|NETMBX|OPER)', predated the
  VMS_PRV_M_ENFORCED masking and could never match the corrected output;
  corrected to '(CMKRNL|CMEXEC|SETPRV|WORLD)'. Added F$PRIVILEGE("OPER")
  / F$PRIVILEGE("SETPRV") assertions to the UAT to cover defect 1's fix
  on the real runtime. Rebuilt and reran: 36/36 passed.

- MAXSYSGROUP=8 was pinned only by a transcript this same branch
  introduced (`git log --all -S MAXSYSGROUP` shows no earlier mention).
  Attempted an independent re-capture this round; the ~/vax/cluster lab
  was live under a real 3-node cluster join experiment for vms-760 (ps
  aux showed VAX1/2/3 all running, SCSD/tcpdump active), so driving any
  console by hand was avoided per the read-only-probes-only constraint.
  Disclosed the single-source gap in sys_security.c's comment instead of
  silently relying on it; flagging for the orchestrator to re-verify
  once the lab is free.

- src/libvmssys/vms_kif.h's vms_kif_setident OVMX-UNWIRED declaration
  was already deleted on this branch (LOGINOUT and PID 1 both call it
  now) and not re-added. Ran the standing census gate:
  tests/integration/test_kif_caller_census.sh (43 entry points, 27
  reached from the product, 16 declared unwired) PASS; its negctl (23
  controls) PASS.

Full proof set re-run on this branch: host ctest 49/49 (one
terminal_identity_negctl timeout on the first pass was host CPU
contention from concurrent podman/QEMU builds on a shared machine --
reproduced green in isolation at 103s against a 120s budget, and green
again on a full clean re-run); QEMU kernel-module harness (podman,
aarch64, pure TCG, no KVM) ALL KERNEL MODULE TESTS PASSED; full facility
negative-control sweep (tests/qemu/run_facility_negctl.sh) 22/22 passed,
including the two vms-2b8-specific defects (ident-username-unguarded,
bind-client-no-register); Rule 9 runtime-target gate PASS.

Not fixed here, confirmed still true and left for tracking: SSH login
(src/vmsssh/vmssshd.c) establishes identity but does not perform
LOGINOUT's Linux credential drop for the session that follows (flagged
loudly in-code by an earlier round; needs its own item to port the drop
against a real SSH session). SET PROCESS/PRIVILEGES never reaches the
executive -- it only mutates the DCL-local ctx->privileges, so it cannot
actually grant or revoke anything the executive enforces. Stale prose in
several src/imgact/test/*.sh and src/vmslink/mk_vmsrms_shr.sh comments
still describes vms$check_access as a live LIBVMS$SHR export; confirmed
the symbol is genuinely gone (nm -D against the built shared lib), and
confirmed the stale mentions are all in echo/comment text, not functional
checks, so nothing is broken -- just misleading log output.

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

* vms-2b8 round 4: fix the F$PRIVILEGE/SET PROCESS desync, pin MAXSYSGROUP to
a second source, delete a false SYSUAF claim, harden the UAT harness

CRITICAL, defect 1 was NOT actually fixed by round 3 (measured on the real
QEMU runtime, not inferred from the round-3 diff):

  $ SHOW PROCESS/PRIVILEGES        -> Authorized: CMEXEC CMKRNL SETPRV WORLD
  $ BEFORE = F$PRIVILEGE("SETPRV") -> "TRUE"
  $ SET PROCESS/PRIVILEGES=(OPER)
  $ SHOW PROCESS/PRIVILEGES        -> UNCHANGED (reads the executive directly)
  $ AFTER = F$PRIVILEGE("SETPRV")  -> "FALSE"   <-- same process, same moment

Round 3 masked F$PRIVILEGE (dcl_lexical.c's lex_privilege()) to
VMS_PRV_M_ENFORCED, which fixed the OVER-claim direction it measured (OPER
reading TRUE when SHOW PROCESS/PRIVILEGES correctly omits it) but left the
function reading ctx->privileges -- a DCL-local cache SET PROCESS/PRIVILEGES
(dcl_cmd_set.c's cmd_set_process()) REPLACES outright with whatever was
asked for, with no connection to the executive. A single unrelated SET
PROCESS/PRIVILEGES call therefore silently discarded SETPRV/CMKRNL/CMEXEC/
WORLD -- privileges the executive still genuinely enforced -- producing a
live UNDER-claim contradiction between F$PRIVILEGE and SHOW
PROCESS/PRIVILEGES about the same process at the same instant. The same bug
also self-inflicted lockouts: a second SET PROCESS/PRIVILEGES call could
fail its own SETPRV gate because the first call had clobbered the cache the
gate reads.

Fix: F$PRIVILEGE now asks the executive fresh on every call
(vms_kif_getjpi_self(), the same source SHOW PROCESS/PRIVILEGES reads),
never ctx->privileges -- the two surfaces cannot disagree by construction
regardless of what SET PROCESS/PRIVILEGES does locally. SET
PROCESS/PRIVILEGES itself no longer mutates ctx->privileges at all and no
longer claims a success it cannot deliver: it prints
%OVMX-W-NOSETPRV and leaves state untouched, because actually reaching the
executive is vms_kif_setprv()'s job (src/libvmssys/vms_kif.c), which is
deliberately left OVMX-UNWIRED in vms_kif.h pending vms-pv1 -- this round
was instructed not to touch that declaration or the in-flight vms_kif
caller census (work/vms-7fb-r7), so this is Rule 10's HIDE answer, not
MATCH, until vms-pv1 lands.

Also interrogated and fixed: F$GETJPI("","CURPRIV") silently returned "0"
(the unrecognized-item fallback) -- indistinguishable from "this process
holds no privileges", which was false for every session on this runtime.
Implemented for real, reading the same live source, masked the same way.
SHOW PROCESS/ALL and SET PROCESS/PRIVILEGES's feedback were both
interrogated on the real runtime (session pasted in the dispatch report);
SHOW PROCESS/ALL prints no privilege data at all, so it cannot contradict
anything.

Proof, before and after, real QEMU boot (podman, aarch64, pure TCG, no
KVM), same commands both times:
  BEFORE: SHOW PROCESS/PRIVILEGES unchanged after SET .../PRIVILEGES=(OPER);
          F$PRIVILEGE("SETPRV") and F$PRIVILEGE("WORLD") both flip to FALSE.
  AFTER:  SET PROCESS/PRIVILEGES=(OPER) prints %OVMX-W-NOSETPRV; SHOW
          PROCESS/PRIVILEGES unchanged; F$PRIVILEGE("SETPRV") and
          F$PRIVILEGE("WORLD") both stay TRUE.

TWO HIGHS ON THE MAXSYSGROUP CONSTANT, one problem:

(a) The lab (~/vax/cluster) was mid-experiment again this round (vms-760's
    3-node cluster join; `ps aux` showed VAX1/2/3 live) so no second capture
    was possible -- read-only-probes-only was honored. Corroborated the
    existing lab transcript against the VSI OpenVMS Wiki instead
    (https://wiki.vmssoftware.com/UIC_Protection, fetched 31-JUL-2026):
    MAXSYSGROUP is octal 10 by default, i.e. decimal 8 -- the same value,
    from a source independent of both this tree and the lab. Two sources
    agreeing, neither derived from the other, is a pin; the round-3 version
    was one branch citing its own capture, which is not.

(b) tests/libvms/test_protection.c could not distinguish MAXSYSGROUP=8 from
    a neighbour such as 5: its only cases were group 5 (inside (0,5] AND
    (0,8]) and group 9 (outside both), so a boundary anywhere in [5,8]
    passed identically. Added a case at the boundary itself (group ==
    MAXSYSGROUP, granted) that only a real boundary of 8 satisfies. Proven
    by mutation: changing OVMX_MAXSYSGROUP to 5 turns exactly this one
    assertion red (8/8 -> 7/8), leaving the group-5 and group-9 cases
    unchanged; reverting restores 8/8.

ONE MEDIUM: tests/uat/vms_session_qemu.sh's comment claimed "SYSTEM's
SYSUAF-authorized mask is exactly the enforced set" -- false. Measured:
distro/rootfs/vms/SYS0/SYSCOMMON/SYSEXE/SYSUAF.DAT's SYSTEM row authorizes
the literal string ALL (37 privileges); the four enforced names are what
survives the VMS_PRV_M_ENFORCED intersection of that much larger mask, not
the whole of what SYSUAF grants. Comment corrected.

ONE HYGIENE: filed vms-475 for the vmsssh credential-drop gap (vmssshd
establishes VMS identity for real but does not perform LOGINOUT's Linux
setuid/setgid drop, so an SSH session's Linux-layer credentials can
disagree with its VMS-layer identity for file-protection purposes).

UAT HARNESS HARDENING: tests/uat/vms_session_qemu.sh keyed CMD_OUTPUT by
command TEXT, so a second occurrence of the same command anywhere in the
script silently overwrites the first's stored output with no signal either
way -- and this round's own new assertions would have collided with the
pre-existing 'SHOW PROCESS /PRIVILEGES' occurrence. Added CMD_OUTPUT_SEQ /
CMD_SEQ_LABEL (populated by run_cmd() alongside the existing CMD_OUTPUT) and
check_response_at(), which asserts by call position instead of by text.
Used it both for the new post-SET-PROCESS assertions and to re-anchor the
PRE-EXISTING 'SHOW PROCESS /PRIVILEGES' check, whose index is found
programmatically (not hand-counted) before the new duplicate is appended.
The one pre-existing intentional collision ('SHOW DEFAULT', checked only at
its documented second occurrence) is left as is.

RAN: host ctest 49/49 (twice, clean); QEMU kernel-module harness (podman,
aarch64, pure TCG, no KVM) ALL KERNEL MODULE TESTS PASSED; Rule 9
runtime-target gate PASS; vms_kif caller census 43 entry points / 27
reached / 16 unwired, unchanged from round 3 (no vms_kif.h edits, no new
vms_kif_* callers -- only vms_kif_getjpi_self(), already wired); UAT
39/39 (36 pre-existing + 3 new). Facility negative-control sweep
(tests/qemu/run_facility_negctl.sh) not re-run this round: it exercises
src/kernel/* and vms_kif.h, neither touched here, and was independently
reproduced green from a clean archive per the dispatch report.

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

* vms-2b8 round 5: DCL privilege GATES now consult the same enforced mask
as the reporting surfaces; F$GETJPI CURPRIV/AUTHPRIV pinned to VMS's
real string format; MAXSYSGROUP hygiene; false emphatic claims removed

DEFECT 1, STILL OPEN AT r4, NOW NAMED CORRECTLY: measured on the real
QEMU runtime, in the SYSTEM session --

  $ IDENT_ALTPRI = F$PRIVILEGE("ALTPRI")
  $ SHOW SYMBOL IDENT_ALTPRI        -> IDENT_ALTPRI = "FALSE"
  $ SET PROCESS/PRIORITY=6          -> AUTHORIZED (no error)

-- the report said ALTPRI was not held, and the gate granted the
operation that requires it, same process, same instant. Root cause:
SYSUAF's SYSTEM record authorizes ALL privileges, and
VMS_IOCTL_SETIDENT sets cur_privs = the full authorized mask verbatim
for a SETPRV-holding caller (OVMX design choice, vms_proctab.c), so
ctx->privileges (dcl_main.c) genuinely contains ALTPRI/SYSPRV/BYPASS
even though F$PRIVILEGE and SHOW PROCESS/PRIVILEGES correctly mask
them out of what they REPORT (VMS_PRV_M_ENFORCED). Every privilege
GATE in src/vmsdcl/dcl_cmd_set.c (SET PROCESS/PRIORITY, SET
PROCESS/PRIVILEGES, SET UIC, SET TIME) read the raw, unmasked value.

Fix: one new function, enforced_privs_held(), asks the executive fresh
and masks to VMS_PRV_M_ENFORCED -- the exact source and mask
F$PRIVILEGE and SHOW PROCESS/PRIVILEGES already use. All four gates
now call it instead of reading ctx->privileges. Consequence, stated not
hidden: ALTPRI/SYSPRV/BYPASS/OPER are authorized by SYSUAF but not yet
enforced anywhere in this tree (vms_kif_setprv is OVMX-UNWIRED pending
vms-pv1), so operations gated on them alone are now refused for every
identity until that lands -- Rule 10's HIDE answer applied to the gate,
not just the display.

PROOF, real podman-built QEMU boot, same session, after the fix:
  $ IDENT_ALTPRI = F$PRIVILEGE("ALTPRI")
  $ SHOW SYMBOL IDENT_ALTPRI        -> IDENT_ALTPRI = "FALSE"
  $ SET PROCESS/PRIORITY=6          -> %SET-E-NOPRIV, no privilege for
                                        SET PROCESS /PRIORITY
Report and gate agree. New UAT assertions check this BY POSITION
(check_response_at), consistent with the rest of this defect's block.

TWO F$GETJPI SURFACES FIXED, both pinned to public OpenVMS
documentation (HP/VSI DCL Dictionary + VSI Wiki's F$GETJPI example),
not invented:
  - CURPRIV returned a DECIMAL INTEGER with no citation. OpenVMS
    returns a comma-separated privilege-NAME string, in ascending bit
    position (CMKRNL before CMEXEC -- the oracle's own example order,
    NOT the alphabetical order SHOW PROCESS/PRIVILEGES uses for a
    different command). Fixed to match, masked to VMS_PRV_M_ENFORCED
    like every other surface.
  - AUTHPRIV silently returned "0" -- indistinguishable from "holds no
    privileges", false for this session. Implemented for real, reading
    perm_privs (the authorized mask) through the same format.
Proof, same real session:
  $ IDENT_CURPRIV = F$GETJPI("","CURPRIV")
  $ SHOW SYMBOL IDENT_CURPRIV       -> "CMKRNL,CMEXEC,SETPRV,WORLD"
  $ IDENT_AUTHPRIV = F$GETJPI("","AUTHPRIV")
  $ SHOW SYMBOL IDENT_AUTHPRIV      -> "CMKRNL,CMEXEC,SETPRV,WORLD"

MAXSYSGROUP HYGIENE: the value was hand-maintained in two places
(sys_security.c's comment/#define and test_protection.c's own copy).
Collapsed into ONE definition, src/libvms/include/ovmx_secparam.h,
included by both. SELF-CAUGHT REGRESSION in this same round: aliasing
the test's MAXSYSGROUP straight to the shared symbol made the
"boundary itself" test self-referential (it would pass for ANY
boundary value) -- verified by mutation (changing the header's value
to 9 left that assertion green, only the independently-hardcoded
group-9 case caught it). Fixed with a `_Static_assert(OVMX_MAXSYSGROUP
== 8, ...)`, the same pattern this codebase already uses for other
oracle-pinned constants -- verified this actually fails the build when
mutated, then reverted.

FALSE EMPHATIC CLAIMS REMOVED (CLAUDE.md Rule 10 / method 5 audit):
- sys_security.c/test_protection.c: "granted ONLY if the boundary is
  really 8" for the boundary-alone case -- overclaimed what a single
  case (without its pairing with the group-9 negative control) proves;
  corrected to state what each case actually pins.
- dcl_lexical.c: "false for every session on this runtime ... never
  empty" for CURPRIV -- false: a process registered without
  CAP_SYS_ADMIN gets perm_privs = cur_privs = 0, so CURPRIV would
  legitimately be empty. Removed; replaced with a stated, not "never",
  consequence.
- dcl_lexical.c: "so the three cannot disagree by construction" --
  removed along with the surrounding decimal-integer implementation it
  was defending.

TWO SMALLER FIXES:
- tests/uat/vms_session_qemu.sh: IDX_PRIV_ORIGINAL left at -1 (anchor
  not found) would make check_response_at silently retarget to the
  LAST command via bash's negative array indexing instead of failing.
  Now exits the harness loudly if the anchor search comes up empty.
- %OVMX-W-NOSETPRV printed as a Warning while the function returns
  SS$_NORMAL (a success status) -- VMS's severity/success-bit
  convention disagreed with itself. Changed to %OVMX-I-NOSETPRV,
  matching the informational + SS$_NORMAL pairing this file already
  uses elsewhere (cmd_set_host, cmd_set_audit).

RAN: host ctest 49/49 (three times across this round, clean each
time); real podman-built QEMU boot UAT 43/43 passed (39 pre-existing +
4 new: ALTPRI/PRIORITY gate-agreement pair, CURPRIV/AUTHPRIV format
pair); vms_kif caller census 43/27/16 unchanged (no vms_kif.h edits,
no new vms_kif_* callers); Rule 9 runtime-target gate PASS. Facility
negative-control sweep not re-run: exercises src/kernel/* and
vms_kif.h, neither touched this round either.

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

* vms-2b8 round 6: positive control for the privilege gate, derived
enforced-privilege names, honest SET TIME regression disclosure

Fixes five findings against work/vms-2b8-r5 (f7bc58c). The central fix
(DCL privilege GATES consulting the same enforced mask as the reporting
surfaces) is unchanged and NOT redone.

1. [HIGH] POSITIVE CONTROL FOR THE GRANT PATH. Every existing proof
   showed enforced_privs_held() REFUSING an operation (ALTPRI/PRIORITY);
   none showed it GRANTING one, so a gate that consults nothing and
   refuses unconditionally would have passed every assertion in the file
   exactly as well as a correct gate. Added: SET PROCESS/PRIVILEGES=(OPER)
   (gated on the SAME function, SETPRV||SYSPRV||BYPASS -- SETPRV IS
   enforced) must NOT print NOPRIV for the SYSTEM session, which holds
   SETPRV. VERIFIED NON-VACUOUS BY MUTATION, real podman-built QEMU
   boots, both directions: enforced_privs_held() forced to `return 0`
   (always-refuse) -> 45/2, both failures on the new grant checks, deny
   checks stayed green; forced to `return ~(uint64_t)0` (always-grant)
   -> 45/2, the PRIORITY deny check went red, grant checks stayed green.
   Each mutation flips exactly the assertion class it should and no
   other.

2. [HIGH] enforced_priv_names[] DERIVED, NOT HAND-MAINTAINED. Replaced
   dcl_lexical.c's local 4-entry table (kept in sync with
   VMS_PRV_M_ENFORCED "by hand") with a walk over bit positions 0..63
   against vms_priv_names[] -- the SAME canonical name table SHOW
   PROCESS/PRIVILEGES already reads, now exposed via dcl/dcl_cmd.h
   instead of being file-local to dcl_cmd_show.c. A bit added to
   VMS_PRV_M_ENFORCED gets a name with no second edit, and the walk
   order is ascending bit position for free (matching the oracle's
   CURPRIV example), not the alphabetical order SHOW PROCESS/PRIVILEGES
   uses. VERIFIED BY MUTATION: added VMS_PRV_M_TMPMBX to
   VMS_PRV_M_ENFORCED (the only edit), rebuilt, re-booted -- CURPRIV/
   AUTHPRIV both came back "CMKRNL,CMEXEC,SETPRV,TMPMBX,WORLD", TMPMBX
   correctly slotted between SETPRV and WORLD. Reverted after
   confirming.

3. [MEDIUM] SET TIME REGRESSION, DISCLOSED. OPER/SYSPRV/BYPASS are all
   outside VMS_PRV_M_ENFORCED, so SET TIME's gate can no longer be
   passed by ANY identity -- a real behaviour change from before round
   5 (SYSTEM's raw ctx->privileges used to let it through), left
   undisclosed and untested. Measured: SYSTEM (SYSUAF-authorized for
   ALL 37 privileges) is refused. The error message now says so instead
   of reading like "your account needs OPER, go get it", which is false
   on this build. New UAT assertion pins the refusal to the gate's own
   disclosure text, not a bare NOPRIV substring -- an always-grant
   mutation run found that bare NOPRIV was ALSO satisfied by a second,
   unrelated failure path (settimeofday(2) EPERM once the gate passes,
   since this session runs as UID 4 post-drop, not root), which would
   have made the original assertion vacuous under exactly the mutation
   meant to test it. Narrowed to the gate-specific text; reran and
   confirmed it now discriminates.

4. [MEDIUM] SEVERITY FIX COVERAGE. %OVMX-I-NOSETPRV (not %OVMX-W-) is
   now asserted directly, both presence of the I- form and absence of
   the W- form.

5. [MEDIUM/hygiene] Deleted the "only 4 of the 37 real VMS names"
   cardinality (a count that would drift the moment #2's derivation
   makes the enforced set anything other than 4) and the AUTHPRIV-reads-
   perm_privs emphatic that read as proven when it is not testable on
   this runtime: cur_privs and perm_privs are always equal here
   (VMS_IOCTL_SETIDENT sets them equal, and no DCL caller reaches
   $SETPRV yet), so no UAT assertion checking CURPRIV and AUTHPRIV
   render the same string can distinguish "AUTHPRIV correctly reads
   perm_privs" from "AUTHPRIV reads cur_privs by mistake". Replaced with
   an explicit statement of that limitation and what would resolve it
   (vms-pv1's $SETPRV wiring).

RAN: host ctest 49/49 (0 skipped); real podman-built QEMU boot UAT
47/47 passed (twice, to rule out a flake), including all four new
assertion groups. Facility negctl / kif caller census / runtime-target
gate all included in the 49 (unchanged, no vms_kif.h edits, no new
vms_kif_* callers, no census interaction). Host: aarch64, podman, pure
TCG, no KVM.

NOTED, NOT FIXED (out of scope for this item, pre-existing): (a)
'SET UIC [777,777]' on this runtime hit a DCL parser bug -- the bracket
comma is treated as a parameter separator regardless of context,
yielding %SET-E-IVUIC -- unrelated to the privilege gate and not
touched; the grant proof uses SET PROCESS/PRIVILEGES instead. (b) One
run of the always-grant mutation showed a single unrelated flake on the
pre-existing 'SHOW LOGICAL UAT_TEST' assertion (host under heavy
concurrent podman/QEMU load from other agents); did not reproduce on
either surrounding run. Flagged for the orchestrator as test-flaky
rather than silently dismissed.

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

* vms-2b8 round 7: delete the false per-caller SYSUAF claim from SET TIME's
NOPRIV message, symmetrize SET PROCESS/PRIORITY's wording, remove the
re-introduced '37' cardinal, and make the derived privilege-name lookup
loudly fail on a coverage gap

Four defects from the round-6 adversary review, each with its own
isolating fix and proof:

1. FALSE CLAIM SHIPPED TO THE CONSOLE (the serious one). SET TIME's
   %SET-E-NOPRIV text said "OPER is authorized by SYSUAF but not yet
   enforced" -- true only for SYSTEM and OPERATOR, and false for GUEST,
   DEFAULT, USER1, USER2 (four of the six shipped accounts), which the
   adversary demonstrated by driving SET TIME from a GUEST session
   (SYSUAF: TMPMBX only). The code path never reads the caller's SYSUAF
   record at all -- it only reads the compile-time-fixed
   VMS_PRV_M_ENFORCED mask, so the claim was invented, not derived. The
   per-caller clause is deleted; the message now says only what is true
   for every caller: this privilege is not enforced on this system.

2. ASYMMETRIC FIX. SET PROCESS/PRIORITY is refused by the identical
   permanent, compile-time-fixed gate (ALTPRI not in
   VMS_PRV_M_ENFORCED) but still printed the bare pre-round-6 message.
   It now gets the same HIDE wording as SET TIME, for the same reason.

3. THE FORBIDDEN '37' CARDINAL, reintroduced three times by round 6
   after being deleted once (dcl_cmd_set.c's SET TIME comment, and two
   comments in tests/uat/vms_session_qemu.sh). All three said "ALL 37
   privileges"; replaced with "privilege ALL", the literal SYSUAF field
   value, dropping the invented count.

4. SILENT COVERAGE GAP. F$GETJPI CURPRIV/AUTHPRIV derives its privilege
   names by walking VMS_PRV_M_ENFORCED and looking each set bit up in
   vms_priv_names[]; a bit with no row was silently omitted from the
   rendered string with no diagnostic. The lookup now tracks whether
   each set bit was found and aborts with a loud %SYSTEM-F-BUGCHECK
   diagnostic if not, instead of returning an incomplete privilege
   list that reads as correct.

PROVEN BY MUTATION, not by inspection: temporarily OR'd an unnamed bit
(1ULL << 40, no vms_priv_names[] row) into VMS_PRV_M_ENFORCED
(src/kernel/vms_ioctl.h), rebuilt the bootable image (podman, aarch64,
pure TCG, no KVM), and ran the UAT. SYSTEM's SYSUAF authorizes ALL (all
64 bits), so the very first `IDENT_CURPRIV = F$GETJPI("","CURPRIV")`
fired the check:

    %SYSTEM-F-BUGCHECK, internal consistency failure -- VMS_PRV_M_ENFORCED
    bit 40 has no row in vms_priv_names[] (vms-2b8 round 7 coverage check)

DCL aborted and the login loop respawned a fresh Username: prompt;
every command before that point (SET PROCESS/PRIVILEGES, SHOW
PROCESS/PRIVILEGES, F$PRIVILEGE, SET PROCESS/PRIORITY with its new
symmetric message) behaved identically to the unmutated run --
isolating this mutation to exactly the coverage-check property. The
mutation was reverted before this commit; vms_ioctl.h is unchanged
from round 6.

RAN on the final (reverted) tree: host ctest 49/49; UAT (podman,
aarch64, real /dev/vms under QEMU) 47 passed / 0 failed.

Does not touch src/libvmssys/vms_kif.h or the vms_kif caller census
(in flight on work/vms-7fb-r7, per dispatch instruction).

* vms-2b8 round 8: replace the false %SYSTEM-F-BUGCHECK claim with an
OVMX-facility diagnostic, delete a false 'only' and the last two '37'
cardinals, and give the round-7 coverage check automated coverage

ONE genuine defect from the round-7 adversary review, plus three smaller
ones, each with its own isolating fix and proof. Three of the four
previously-named defects (the SET TIME NOPRIV wording, SET PROCESS/
PRIORITY symmetry, the coverage check's existence) were already correct
on r7 and are untouched.

1. FALSE STATUS CLASS SHIPPED TO THE CONSOLE. Round 7's coverage-check
   guard (F$GETJPI CURPRIV/AUTHPRIV's derived-name lookup) printed
   "%SYSTEM-F-BUGCHECK, internal consistency failure" for an unnamed
   VMS_PRV_M_ENFORCED bit -- a real VMS status/facility/severity shape
   for a condition VMS itself can never face (VMS has exactly one
   privilege table; the desync is two OVMX C files disagreeing at
   compile time). Rule 10 gives two legal answers and this took neither:
   it is not "VMS grants this but OVMX doesn't enforce it" (nothing to
   MATCH -- that is the separate SET TIME/SET PROCESS PRIORITY defect,
   already fixed), and it is not a real "refused privileged operation"
   status either, so reusing one would itself be a lie. That leaves
   HIDE, and round 7 chose HIDE's wrong shape (invent a plausible VMS
   status) instead of its right one (report it as what it is). Fixed:
   the diagnostic now reads "%OVMX-F-TABLEDESYNC, internal build defect,
   not a VMS condition -- ...", the same OVMX-facility convention
   %OVMX-I-NOSETPRV already uses, so it cannot be mistaken for genuine
   VMS console output. Still aborts -- this is still not a condition the
   caller can recover from.

2. FALSE 'only' IN dcl_cmd_set.c, load-bearing for the very SET TIME
   message round 7 corrected: "this code path never reads the caller's
   SYSUAF record at all -- it only reads the compile-time-fixed
   VMS_PRV_M_ENFORCED mask" is false -- enforced_privs_held() also reads
   the executive's live cur_privs (per-identity state), it just never
   reads SYSUAF. Reworded without the superlative.

3. THE FORBIDDEN '37' CARDINAL, still present in two comments round 7
   did not touch (tests/uat/vms_session_qemu.sh's SYSUAF-row comment,
   test_syssvc_ident's/vms_login.c's "with all 37 privileges") plus two
   more found by grep in files round 7 never edited (test_syssvc_ident.c
   line 730, tools/vms_login.c). All five deleted, not corrected: the
   real count is 39 privileges pinned across 37 named table rows
   (DETACH and SETPRI deliberately unrepresented, docs/oracle/
   vax73-privileges.md), so "37" undercounts what SYSUAF's ALL actually
   authorizes. Replaced with "privilege ALL" / "SYSUAF's privilege ALL",
   the literal SYSUAF field value, no invented count.

4. THE COVERAGE CHECK HAD NO AUTOMATED COVERAGE. Round 7 proved it fires
   by hand (temporary edit, revert) but nothing in the suite exercised
   it. Added tests/qemu/test_syssvc_ident.c scenario F: an isolated
   run_dcl() call (own script, own buffer, cannot knock on scenarios
   A-E) that runs F$GETJPI CURPRIV as SYSTEM/SYSUAF-ALL and checks for a
   marker printed after it. Registered as
   tests/qemu/facility_defects.sh's getjpi-curpriv-name-coverage defect
   (targets kernel/vms_ioctl.h, the same OR-in-bit-40 edit round 7
   proved by hand).

   RAN END TO END, not just asserted: tests/qemu/facility_defects.sh
   selftest (host, no QEMU) passes with the new entry, and
   tests/qemu/run_facility_negctl.sh getjpi-curpriv-name-coverage
   (podman, aarch64, pure TCG, no KVM, real /dev/vms under QEMU) reports

     Facility negative controls: 3 passed, 0 failed

   with the observed red set EXACTLY the one assertion the manifest
   names, attributed to test_syssvc_ident and no other suite -- the
   isolation claim in the manifest's own comment, verified by execution
   rather than asserted.

RAN on the final tree: host ctest 49/49 (includes facility_negctl_manifest
and the new scenario F compiling/linking as part of the default build);
the QEMU facility negative control above.

Grepped this diff for only|every|never|cannot|all|exactly|37: the two
remaining 'cannot' claims are compile-time/structural facts (VMS's single
privilege table; scenario F's own isolated buffers), not runtime claims
about caller-visible behavior, and are stated as such.

Does not touch src/libvmssys/vms_kif.h or the vms_kif caller census (in
flight on work/vms-7fb-r7, per dispatch instruction).

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

* vms-2b8 round 9: make the coverage-check desync structurally impossible
at compile time, fix scenario F's vacuity, delete a false clause, and
re-measure bind-client-no-register's true red set

FOUR adversary findings from the round-8 review, each with its own proof.

1. [CRITICAL REGRESSION] bind-client-no-register's manifest entry was
   stale. Round 8 added two assertions to test_syssvc_ident scenario F,
   a suite this control already names in suites_red, without
   re-measuring the control's red set -- so the equality check in
   run_facility_negctl.sh failed on two unnamed reds. RE-MEASURED
   against real QEMU, not derived by arithmetic: running
   bind-client-no-register on the pre-fix tree reported

     these assertions went red and the manifest does NOT name them:
       test_syssvc_ident: F: F$GETJPI CURPRIV renders SYSTEM/ALL's...
       test_syssvc_ident: F: the executive accepted the SYSTEM/ALL...

   -- exactly the two scenario F assertions, nothing else. Both added to
   knock_on_fail verbatim. RE-RUN on the corrected manifest: "Facility
   negative controls: 3 passed, 0 failed", and the harness's own
   equality check reports "the red set is EXACTLY the 89 assertion(s)
   the manifest names". Independently reproduced a second way: a
   from-scratch podman/QEMU build with the same one-line mutation
   applied directly (bypassing the shared harness) reddened the
   identical 16-assertion block in test_syssvc_ident (A/B/C/D/F) and
   nothing else, matching byte-for-byte.

2. [HIGH, vacuity] Scenario F's assertion was satisfiable by a run where
   CURPRIV rendered nothing at all -- it only checked that a marker
   printed AFTER F$GETJPI CURPRIV, never that the call produced any
   content. Fixed: the script now SHOWs the symbol CURPRIV was assigned
   to, and the assertion requires the literal enforced-privilege string
   for SYSTEM/SYSUAF-ALL ("CMKRNL,CMEXEC,SETPRV,WORLD"), not merely its
   presence. PROVEN BY MUTATION, real QEMU: changing dcl_lexical.c's
   `enforced = raw & VMS_PRV_M_ENFORCED` to `enforced = 0` (CURPRIV/
   AUTHPRIV always render "") reddened test_syssvc_ident alone (37
   passed, 1 failed), the one failure being exactly this assertion.
   Reverted after confirming.

3. [MEDIUM, Rule 10] The round-7/8 coverage-check guard was a RUNTIME
   abort() for a condition that is 100% COMPILE-TIME DETERMINABLE:
   VMS_PRV_M_ENFORCED and vms_priv_names[] are both static,
   compile-time-constant data in the same binary, so whether every
   enforced bit has a name cannot vary at runtime -- there is no caller,
   no input, no execution path that changes the answer. A runtime
   handler for an already-settled compile-time fact is Rule 10's
   forbidden third answer. MEASURED, not assumed: the runtime abort()
   block is DELETED from dcl_lexical.c and replaced with a
   _Static_assert in src/libvms/prv_agreement.c (the file that already
   exists for exactly this class of guard). Negative control run for
   real: OR-ing an unnamed bit into VMS_PRV_M_ENFORCED now fails the
   BUILD at prv_agreement.c with the expected diagnostic, instead of
   booting and aborting a user's DCL session. Reverted after confirming.
   Consequence handled: the getjpi-curpriv-name-coverage QEMU negative
   control is deleted from facility_defects.sh -- the same mutation now
   fails the container rebuild, which run_facility_negctl.sh classifies
   as a broken harness (RUN_RC=4) for every defect, so keeping the entry
   would make it permanently and uninformatively red. Does not touch
   src/libvmssys/vms_kif.h or the vms_kif caller census.

4. [MEDIUM] dcl_cmd_set.c's SET TIME comment repeated, in reworded form,
   the same false clause round 7 was already corrected for: "does not
   read the caller's SYSUAF-authorized mask anywhere in that
   computation" overclaims what can be said about enforced_privs_held().
   Deleted rather than reworded a third time; replaced with the plain,
   checkable fact that VMS_PRV_M_ENFORCED has no OPER bit at all, so the
   check's answer cannot depend on SYSUAF regardless of what cur_privs
   carries.

[LOW] Resolved as a side effect of (3): the false claim that the OVMX
diagnostic "is not formatted as genuine VMS console output" lived
entirely inside the deleted runtime-abort comment block.

[HYGIENE] "SYSPRV, BYPASS, OPER, and 30-odd others" in
tests/uat/vms_session_qemu.sh replaced with "30 others (37 named rows in
vms_priv_names[], minus the 4 shown, minus these 3 named)" -- the exact
count, shown as arithmetic rather than an unexplained cardinal.

RAN: host ctest 49/49 (0 failed); tests/qemu/facility_defects.sh selftest
(host, no QEMU) green with the updated manifest; the two QEMU negative
controls above, both against a real /dev/vms under QEMU (podman,
aarch64, pure TCG, no KVM). Grepped this diff for
only|every|never|cannot|all|exactly|37 -- every surviving instance is
backed by an execution result cited in this message or in the touched
file's own comment.

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

* vms-2b8 round 10: make the coverage-check desync a compile error in
BOTH directions, not just one

THE ONE DEFECT: round 9's commit message claimed the coverage-check
desync was made "structurally impossible at compile time." That was
false in the direction that matters. Round 9's _Static_assert checked
VMS_PRV_M_ENFORCED (src/kernel/vms_ioctl.h) against a HAND-TYPED
whitelist `(PRV$M_CMKRNL | PRV$M_CMEXEC | PRV$M_SETPRV | PRV$M_WORLD)`
-- four names a person read off vms_priv_names[] and copied into the
assert. It never looked at the array's actual contents.

PROVEN (this round, on the unmodified round-9 tree, bd525fd, before
any fix): deleted the WORLD row from src/vmsdcl/dcl_cmd_show.c's
vms_priv_names[] --

    $ sed -i '155d' src/vmsdcl/dcl_cmd_show.c   # deletes the WORLD row
    $ cmake --build build-round9-mut -j4        # EXIT=0
    $ ctest --output-on-failure                 # 100% tests passed, 0 failed, 49/49

-- the build and full test suite stayed green with VMS_PRV_M_ENFORCED's
WORLD bit reaching lex_getjpi()'s vms_priv_names[] lookup loop with no
row: the exact desync the assert claimed to prevent, arriving from the
table side instead of the mask side.

FIX: src/libvms/include/prv_names.h (new) holds ONE preprocessor list,
VMS_PRIV_NAME_LIST, that is the single source for both consumers:
  - src/vmsdcl/dcl_cmd_show.c's vms_priv_names[] is now generated by
    expanding the list through VMS_PRIV_ROW_ENTRY -- same 37 entries,
    same order, same names/masks/descriptions as before (confirmed
    byte-for-byte via `gcc -E` on the generated array vs. the original
    literal; see below).
  - src/libvms/prv_agreement.c's coverage check now compares
    VMS_PRV_M_ENFORCED against VMS_PRIV_NAMES_TABLE_MASK, the OR of
    every mask term in the SAME list, instead of the hand-typed
    whitelist.
There is no second, independently-maintained enumeration of "which
rows exist" left for a row deletion to leave stale: deleting a line
from VMS_PRIV_NAME_LIST removes that privilege from the array AND from
the coverage mask in the same edit.

BOTH DIRECTIONS PROVEN by execution on the round-10 tree, each
reverted after confirming:

  Negative control 1 (bit added to VMS_PRV_M_ENFORCED, no table row --
  same direction round 9 covered): OR (1ULL << 40) into
  VMS_PRV_M_ENFORCED (src/kernel/vms_ioctl.h) and rebuild --
    prv_agreement.c:136:1: error: static assertion fai…
baron-3dl added a commit that referenced this pull request Aug 7, 2026
… real DCL.EXE/LOGINOUT.EXE via LINK.EXE (#155)

* vms-206: LINK.EXE emits real x86_64 crt0 + cross-image CALL PLT stubs

emit_shareable() in src/vmslink/link.c had two pieces hardcoded to
AArch64 machine code regardless of g_out_machine: the synthesized crt0
entry stub for a main()-based --executable, and the cross-image CALL
PLT stub + import-CALL detection (gated on R_AARCH64_CALL26/JUMP26
only, so R_X86_64_PLT32 references to a producer universal never
routed through the import table). An x86_64 main()-based program, or
any x86_64 image with a cross-image CALL import, linked to garbage --
exactly DCL.EXE's shape, blocking vms-cb5f.

Adds, additively per g_out_machine (mirrors vms-8f5's e_machine gate):
  - is_call also covers R_X86_64_PLT32, so a PLT32 reference to an
    undefined-locally, --use-producer-exported symbol becomes an
    import exactly like aarch64's CALL26/JUMP26.
  - the cross-image-call reloc-apply branch gets an x86_64 case: a
    PC32-style S+A-P write targeting the PLT stub instead of the
    (absent) callee.
  - the PLT stub emitter gets an x86_64 case: `jmp *disp32(%rip)`
    (FF 25 imm32) through the import-GOT cell -- the one-instruction
    analogue of aarch64's adrp/ldr/br page+lo12 GOT load+branch.
  - the crt0 emitter gets a real x86_64 stub: mov rdi,[rsp] / lea
    rsi,[rsp+8] / lea rdx,[rsi+rdi*8+8] recovers argc/argv/envp per
    the SysV process-entry stack layout, `call main` then `mov
    edi,eax ; call exit` tails the return value into exit(). Encoding
    verified against `as`'s AT&T disassembly byte-for-byte.

New test (src/imgact/test/run_multiobj_exec_x86_64.sh, wired into CI
as multiobj-exec-x86_64): links a two-object main() program against a
hand-written producer shareable via LINK.EXE --executable --use,
activates it by executing the image directly (real kernel PT_INTERP
-> IMGACT.EXE, native x86_64, no emulation), and checks the process
really ran main(), read a real argc/argv off the stack (two runs, two
different exit codes), made a real cross-image CALL through the new
PLT stub into the producer, and exited via a cross-image exit() call.
Verified this test fails with 'unresolved external symbol' before the
fix (confirmed via stash) and passes after.

Regression: aarch64 MVP (run_test.sh), aarch64 crt0/PLT
(run_multiobj_exec.sh) and x86_64 simple-reloc (run_test_x86_64.sh)
harnesses all stay green. Full ctest suite: 106/107 pass; the one
failure (facility_attribution_negctl) is pre-existing and unrelated
(reproduces identically with this change stashed out -- host/container
site-derivation drift in kernel/vms_lock.c attribution, nothing to do
with vmslink).

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

* vms-e5d: LINK.EXE resolves x86_64 GOTPCRELX (type 41), grounded against real musl/libgcc

is_got_reloc() only recognized GOTPCREL (9) and REX_GOTPCRELX (42); plain
GOTPCRELX (41, gas's non-REX relaxable GOT-load variant) hit patch_pcrel's
default die("unsupported .text relocation"). Adds it as a third case
alongside its already-handled siblings in is_got_reloc()/patch_got() --
same flat-disp32-write codegen (S(got)+A-P, addend -4), since LINK.EXE
performs no GOT-load-to-LEA relaxation for any of the three.

Grounded empirically, not from psABI text alone:
- The system's prebuilt musl libc.a carries ZERO GOTPCRELX/REX_GOTPCRELX
  (non-PIC static build) -- confirming the gap check needed a real PIC
  object set, not just this host's default libc.a.
- A `-fPIC -fno-plt` probe (`extern void f(void); void g(void){f();}`)
  reproduces the exact instruction shape: `jmp *sym@GOTPCREL(%rip)` (ff 25
  disp32, no REX prefix -- near indirect call/jmp defaults to 64-bit
  operand size without REX.W), matching readelf's R_X86_64_GOTPCRELX.
- Alpine x86_64 libgcc.a (the exact toolchain vms-cb5f's DECC$SHR build
  uses) carries 1521 real GOTPCRELX occurrences -- e.g. `call *abort@
  GOTPCREL(%rip)` in _absvdi2.o -- confirming the gap is in libgcc.a
  (GCC's runtime calling abort/etc. through the GOT under -fno-plt), not
  libc.a itself, and closely matches vms-cb5f's reported 1449 (after its
  TLS-subsystem filter narrows the count).

Proof (done condition):
- Pre-fix LINK.EXE reproducibly dies with "%LINK-F-ERROR, unsupported
  .text relocation" on a hand-built intra-image GOTPCRELX call
  (caller.o -> callee.o, both defined, forcing the GOT slot to resolve
  internally rather than deferring as an import).
- Post-fix LINK.EXE links it; a new REAL-LOAD harness
  (src/vmslink/test/gotpcrelx_activate.c) mmaps the shareable at a
  genuine non-zero ASLR'd base, applies the .vms$rel load-bias fixup
  IMGACT would perform, and calls in -- proving the GOT cell resolves to
  the correct address under a real load, not a readelf/byte check.
  Wired into run_test_x86_64.sh as a permanent regression case.
- Whole-archiving the real Alpine x86_64 musl libc.a + libgcc.a (TLS
  subsystem filtered per vms-cb5f's precedent, an unrelated gap) with
  pre-fix LINK.EXE reproduces the exact die(); post-fix it gets past
  that point and (with --allow-undefined for legitimately deferred
  externals) succeeds end-to-end: 1586 objects, 146 GOT slots, 9610
  relocs, EM_X86_64 -- confirming this specific gap is cleared at the
  scale vms-cb5f hit it.

Base: work/vms-b93-integration (vms-206 merged). Blocks vms-cb5f.

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

* vms-cb5f: parameterize the x86_64 DCL.EXE VMS-native proof harness (ARCH=x86_64)

Reproduces vms-b65.6's aarch64 DCL.EXE-through-IMGACT proof for x86_64, per
vms-bdf's own done-condition (all four reloc/crt0/PLT beads merged):

- run_dcl_native.sh / lib_build_graph.sh: ARCH env var (default aarch64,
  unchanged) selects the one target-specific codegen flag each producer
  needs (-mno-outline-atomics on aarch64 vs -mtls-dialect=gnu2 on x86_64,
  the standing precedent from docs/design-link-x86_64-relocs.md) and the
  libvmssys arch/<ARCH>/syscall.S. Shared by run_login_native.sh unaffected
  (ARCH unset -> identical aarch64 defaults, verified against a real arm64
  musl container).
- mk_{vmsprocess,vmslnm,vmsfs,libvms,vmsrms}_shr.sh / mk_dcl.sh: CFLAGS is
  now env-overridable (${CFLAGS:-<same aarch64 default>}) so the x86_64
  caller can supply target-appropriate flags without a forked copy of each
  recipe.
- mk_decc_shr.sh: DECC$SHR must stay a non-TLS producer (LINK.EXE's
  one-TLS-object-per-image limit, vms-212 tracks the general fix). aarch64's
  libgcc.a empirically carries zero TLS-defining members; x86_64's whole-
  archives a dead-for-OVMX subsystem (GCC's IEEE 754-2008 decimal-float
  library + -fsplit-stack support) built on the TLSGD general-dynamic model
  LINK.EXE's x86_64 path doesn't implement (OVMX standardizes on gnu2/
  TLSDESC). Filtered architecture-generically by scanning archive members
  for .tdata/.tbss or TLSGD refs, not by hardcoding names -- a no-op on an
  archive with neither.
- .github/workflows/ci.yml: new dcl-native-x86_64 job, native amd64 (no
  QEMU binfmt needed -- the runner already is x86_64), ARCH=x86_64 through
  the same run_dcl_native.sh.

BLOCKED short of a green run: whole-archiving real musl libc.a on x86_64
(1345 objects) hits R_X86_64_GOTPCRELX (type 41), a relocation link.c's
x86_64 path does not recognize (is_got_reloc() only checks GOTPCREL/
REX_GOTPCRELX) -- confirmed empirically (1449 occurrences across ordinary,
load-bearing musl objects, not confinable to dead code the way the TLSGD
subsystem was). link.c is out of this item's file-domain per its own
repeated header comments ("do NOT edit them here"); see the escalation.

Also confirmed (fetched fresh): vms-206 is on work/vms-b93-integration
(16c7965) but NOT yet on main -- a second prerequisite for the executable
(not just shareable) link step once the GOTPCRELX gap is fixed.

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

* vms-cb5f: make DECC$SHR's x86_64 TLS filter transitive, closing the whole-archive gap

Re-dispatch, continuing from work/vms-cb5f now that vms-e5d (GOTPCRELX) is
merged into work/vms-b93-integration. Rebased cleanly onto
origin/work/vms-b93-integration (vms-206 crt0/PLT + vms-e5d GOTPCRELX both
present); the prior CI-job/ARCH-parameterization/CFLAGS work carried over
unchanged.

Whole-archiving real musl libc.a + libgcc.a on x86_64 got past GOTPCRELX and
hit a NEW gap: mk_decc_shr.sh's TLS filter (vms-cb5f's own prior commit)
removes members that directly define/reference TLS storage (bid64_add.o
etc, via TLSGD) but left non-TLS "glue" objects in place --
_addsub_dd.o/_addsub_sd.o/... call INTO the removed decimal-float subsystem
via a plain GOT reference to e.g. __bid64_add, which no longer has a
definer once bid64_add.o is dropped. LINK.EXE's "GOT symbol undefined"
strict die() was CORRECT given the archive it was handed -- the gap was in
the filter only removing the directly-tainted half of a connected dead-code
component, not link.c.

Fixed by making the filter a reference-graph fixed-point closure: after
seeding the direct TLS-tainted set (unchanged), repeatedly pull in any
surviving member whose undefined reference is satisfied ONLY by an already-
removed member, until nothing new is added. One nm pass over the whole
archive up front (not re-invoked per member per iteration) keeps this cheap
on libc.a's 1345 members. Verified as a no-op on aarch64's libgcc.a (366/366
members survive, 0 filtered, byte-identical DECC$SHR before/after) and pulls
in exactly the expected 148/241-surviving decimal-float + split-stack
subsystem on x86_64's.

With that fix, the full six-library producer graph + DCL.EXE (22 objects,
6 GOT, 2 TLS, 1053 ABS64-ptr, 145 imports) now link VMS-native and clean on
x86_64. Activation through IMGACT.EXE segfaults -- escalated (see PR/item
notes), not patched here: link.c/imgact.c are out of this item's file-domain
per the item's own repeated header comments, and the crash is upstream of
mk_decc_shr.sh (isolated: run_multiobj_exec_x86_64.sh's small-scale crt0/PLT
proof -- 3 imports, 1 GOT, 0 TLS -- still passes natively with correct
argc-computed exit codes, so this is scale/shape-specific to DCL.EXE's
import/reloc volume, not a general crt0/PLT regression).

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

* vms-a66: LINK.EXE was dropping every read-only-section relocation

DCL.EXE segfaulted the moment it was activated on x86_64. The fault PC sat in
the anonymous RWX region IMGACT.EXE maps for DECC$SHR, executing non-instruction
bytes. Single-stepping from musl's printf_core into pop_arg caught the transfer:

    movslq (%rcx,%rsi,4),%rdx     ; rdx = jumptable[1]
    add    %rcx,%rdx              ; rdx = table_base + delta
    jmp    *%rdx

with the table entry ZERO, so the jump landed on the table's own address --
inside .rodata, on the "(null)" string constant.

Root cause: parse_obj() collected relocations only for sections it bucketed
B_TEXT or B_DATA. Every relocation whose target section was B_RODATA was
discarded with no diagnostic. gcc emits each `switch` jump table into a
per-function read-only section as `.long arm - table_base`; the arms are in
.text and the table is not, so the assembler cannot fold the difference and
leaves one real R_X86_64_PC32 per arm. All of them were dropped, so every jump
table in the image came out all zero.

Not a scale bug. Nothing about 145 imports, 6 shareables, 6 GOT slots, 2 TLSDESC
entries or the 6-deep --use chain is involved: vms-206/vms-cd1/vms-2e4 passed
through this because their specimens contained no switch large enough for gcc to
build a table and never called a printf-family function with a conversion. It is
a code SHAPE that first appeared when real musl and the real DCL sources entered
the link -- 902 such relocations in libc.a, 554 in DCL's own objects.

aarch64 was never affected: aarch64 gcc resolves its jump tables at assembly
time, so its only read-only-section relocations are .eh_frame PREL32 (never
executed). Confirmed empirically on both arches.

Fix: collect relocations for B_RODATA as well, via a single bucket_is_patchable()
predicate, and emit %LINK-W-RELSKIP for any RELA section whose target is
allocatable but not flat-placed -- a silent drop is exactly how this survived
four proofs. (That diagnostic immediately surfaces one pre-existing gap:
libgcc's cpuinfo.o .init_array, tracked separately, not executed today.)

Regression gate: src/vmslink/test/run_rodata_reloc_x86_64.sh + its specimen link
a jump-table-bearing image against the real whole-archive musl DECC$SHR, activate
it through a real IMGACT.EXE, and diff the transcript against the SAME source
built by the system toolchain. It asserts the specimen still produces .rela.rodata
(so it cannot rot into a vacuous pass) and fails LOUD -- verified: it dies with
"Illegal instruction" on the pre-fix linker and passes on the fixed one. Wired
into CI as job rodata-reloc-x86_64.

Verified green: dcl-native ARCH=x86_64 (SHOW TIME + A=5, exit 0) and ARCH=aarch64,
run_multiobj_exec_x86_64.sh, src/imgact/test/run_test_x86_64.sh,
src/vmslink/test/run_test_x86_64.sh, run_test_x86_64_tls.sh, run_decc_shr.sh,
run_test.sh + run_multiobj_exec.sh under arm64.

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

* vms-b6a: wire the VMS-native LINK.EXE shareable graph into CMake (aarch64)

Wires the mk_*_shr.sh / mk_dcl.sh / mk_loginout.sh recipes (the vms-b65/c39
lib-migration chain) into `cmake --build` via a new OVMX_LINK_NATIVE option
that auto-detects on an aarch64 musl toolchain. `cmake -B build && cmake
--build build` now produces LIBVMSSYS$SHR.EXE, DECC$SHR.EXE, LIBVMSPROCESS$SHR.EXE,
LIBVMSLNM$SHR.EXE, LIBVMSFS$SHR.EXE, LIBVMS$SHR.EXE, LIBVMSRMS$SHR.EXE, DCL.EXE
and LOGINOUT.EXE via LINK.EXE -- verified EM_AARCH64 with zero DT_NEEDED
entries on all 9 artifacts.

Additive to (not a replacement of) each library's existing add_library()
target, which host ctest unit tests still link directly; on non-aarch64-musl
toolchains OVMX_LINK_NATIVE stays off with no behavior change.

Extracted the previously-inlined LIBVMSSYS$SHR recipe (duplicated in
lib_build_graph.sh) into mk_vmssys_shr.sh, the one place the mk_*_shr.sh
convention keeps it -- lib_build_graph.sh (run_dcl_native.sh/run_login_native.sh)
now calls it too, closing the exact drift risk mk_libvms_shr.sh's LIST
comment warns about. mk_vmssys_shr.sh exports vms_kif_setident
unconditionally (append-only vector), so run_login_native.sh's SYS_VEC_EXTRA
override is no longer needed. Both harnesses re-verified green end-to-end in
an aarch64 musl container after the refactor.

Added CI job link-native-cmake asserting the readelf ground-source condition
directly. x86_64 extension is vms-6da (separate item, unblocked).

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

* vms-6da: extend the VMS-native LINK.EXE CMake graph to x86_64

Extends vms-b6a's OVMX_LINK_NATIVE mechanism (auto-detected from the
compiler's -dumpmachine triple) to also recognize an x86_64 musl
toolchain, not just aarch64 -- same CMake target (link_native_graph),
same build_link_native.sh entrypoint, no forked plumbing. ARCH is
threaded through as an env var to every mk_*_shr.sh recipe (CFLAGS
picks -mtls-dialect=gnu2 on x86_64 vs -mno-outline-atomics on aarch64,
the same convention lib_build_graph.sh's build_producer_graph()
established for the raw-harness path in vms-cb5f/vms-a66).

mk_vmssys_shr.sh (extracted by vms-b6a, so it hadn't picked up the
ARCH/CFLAGS env-override convention yet) and mk_loginout.sh (missed by
cb5f/a66) are brought in line with the rest of the mk_*_shr.sh recipes.

Fixed a real bug hit while proving this: `N=$(... | grep -c NEEDED)`
aborts under `set -e` in the CI job's alpine /bin/sh whenever the DT_NEEDED
count is legitimately zero (grep -c exits 1 on no match) -- present in
both the pre-existing aarch64 job and the new x86_64 one; both fixed.

Ground-truth proof, real alpine:3.20 musl containers (arm64 emulated,
amd64 native), `cmake --build . --target link_native_graph`:
  - x86_64:  9/9 artifacts EM_X86_64, zero DT_NEEDED, via LINK.EXE
  - aarch64: 9/9 artifacts EM_AARCH64, zero DT_NEEDED (regression, unchanged)

Adds CI job link-native-cmake-x86_64 alongside link-native-cmake,
mirroring dcl-native-x86_64's amd64-native (no QEMU) approach.

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

* vms-d0f5e: fat initramfs ships DCL.EXE/LOGINOUT.EXE VMS-native via LINK.EXE

Replaces vms-913.6 (cancelled -- DT_HASH/ld-based dynamic ELF was a proven
dead end). distro/Dockerfile.bootable now builds a real alpine:3.20 musl
link-native stage that runs `cmake --build --target link_native_graph`
(OVMX_LINK_NATIVE, vms-b6a/vms-6da) to produce the 7 shareables
(DECC$SHR, LIBVMSSYS$SHR, LIBVMSPROCESS$SHR, LIBVMSLNM$SHR, LIBVMSFS$SHR,
LIBVMS$SHR, LIBVMSRMS$SHR) + DCL.EXE + LOGINOUT.EXE via LINK.EXE, plus
IMGACT.EXE (its own standalone Makefile, x86_64). All 9 artifacts are
ET_DYN with a .vms$sv symbol vector, zero DT_NEEDED/DT_HASH -- ground-
truth readelf assertions are baked into the Docker build itself (both in
the link-native stage and against the actually-shipped DCL.EXE in the fat
initramfs), not just asserted by a separate CI job. STARTUP.EXE and
IMGACT.EXE stay static/freestanding; HELP/AUTHORIZE/MAIL/MONITOR/
INITIALIZE ship static for 0.1 (no mk_*.sh recipe yet, scope decision --
DCL is the flagship dynamic proof, not every utility).

Ground-truth verified locally: full `docker build` succeeds, QEMU x86_64
boot reaches login (tests/uat/vms_session_qemu.sh: SYSTEM and GUEST
sessions authenticate, DCL runs 50+ commands to VMS-correct output,
DIRECTORY SYS$SYSTEM: lists the new DCL.EXE/IMGACT.EXE/LOGINOUT.EXE),
and test_executive_integral.sh's negative controls (NOEXEC/NODEV,
rebuilt from the same fat initramfs) still pass 14/14.

KNOWN REGRESSION, not fixed here (out of this item's file-domain --
distro/Dockerfile.bootable + CMake wiring, not src/vmsdcl or
src/kernel/vmsfs): SPAWN's first invocation in a session now fails
(%DCL-E-CREPRC) where it previously succeeded. Root-caused via an A/B
rebuild swapping only DCL.EXE/LOGINOUT.EXE back to static (54/54 UAT
checks pass) vs the VMS-native pair (52/54, this SPAWN check newly red).
cmd_spawn() (src/vmsdcl/dcl_cmd_process.c) re-execs via
readlink("/proc/self/exe"), which resolves to a path vmsfs reports
"(deleted)" -- confirmed present already at the top of DCL's own main(),
before any DCL code runs, so the deletion happens during kernel PT_INTERP
+ IMGACT activation, not in DCL or LOGINOUT. No unlink()/rename() of
SYSEXE exists in the userspace boot path (checked ovmx_init.c,
vms_login.c, vmsfs_translate.c) -- the leading hypothesis is a vmsfs.ko
dentry-lifecycle interaction exposed by IMGACT's longer activation time,
which needs its own investigation. Login and the item's own done
condition are unaffected; test file left unmodified (weakening an
existing UAT assertion is outside this item's authority).

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

* vms-00e: vmsfs must not unhash the dentry of a running image (SPAWN fix)

ROOT CAUSE. vmsfs.ko's ->d_revalidate answered "invalid" for EVERY
positive regular-file dentry, unconditionally. That is not revalidation,
it is permanent invalidation: a d_revalidate() of 0 makes the VFS call
d_invalidate(), which UNHASHES the dentry (fs/namei.c lookup_fast() /
lookup_open()); an unhashed non-root dentry satisfies d_unlinked(); and
d_path() renders any d_unlinked() path with a " (deleted)" suffix
(fs/d_path.c path_with_deleted()). /proc/<pid>/exe and /proc/<pid>/fd/<n>
are d_path() readers.

So the FIRST path walk of a running executable that lives on vmsfs made
that program's own /proc/self/exe read ".../DCL.EXE (deleted)" -- with
the file present and unmodified. mm->exe_file pins the dentry, so it
stayed unhashed for the life of the process. DCL's SPAWN re-execs itself
via readlink("/proc/self/exe") (cmd_spawn(), src/vmsdcl/dcl_cmd_process.c),
so it execl()'d a path with " (deleted)" on the end, got ENOENT, and
answered %DCL-E-CREPRC.

That single defect explains BOTH observed shapes, which differ only in
when the second walk happens:
  - static DCL.EXE: nothing re-walks the image during startup, so the
    first SPAWN's own execl() was the second walk -- spawn #1 worked,
    spawn #2 onward failed. (The "SECOND SPAWN fails" defect recorded in
    tests/uat/vms_session_qemu.sh, previously blamed on DCL.)
  - VMS-native, IMGACT-activated DCL.EXE (vms-d0f5e): IMGACT.EXE re-opens
    the image by AT_EXECFN to read its .vms$sv/.vms$imp sections
    (activate_symbol_vector(), src/imgact/imgact.c) BEFORE the image runs,
    so the dentry was already unhashed at the first line of main() and the
    FIRST SPAWN failed. IMGACT did not cause the bug; it reached it one
    walk earlier.

Nothing ever unlinked or renamed anything, which is why the userspace
audit of ovmx_init.c / vms_login.c / vmsfs_translate.c found nothing.

FIX. ->d_revalidate now asks the resolver the question a fresh ->lookup
would ask -- "what does this name resolve to right now?" -- and keeps the
dentry when the answer is unchanged:
  - block-device mode: re-resolve the name to a FID (vmsfs_blkdev_resolve(),
    factored out of vmsfs_blkdev_lookup() with no behaviour change) and
    compare against i_ino, which IS the FID (iget_locked(sb, fid)). Exact
    identity: catches a newer version AND a deletion.
  - overlay mode: compare the current highest version of the base name
    against the version this dentry resolved to (overlay mints a fresh
    inode per lookup, so version is the only stable identity).
  - create intent (LOOKUP_CREATE/LOOKUP_RENAME_TARGET) still returns 0, so
    O_CREAT cannot be satisfied from the cache and VMS still cuts a NEW
    VERSION rather than reopening the current one.
This is also strictly cheaper than the old behaviour, which paid for the
same resolution and then threw away the dentry and the inode anyway.

GROUND SOURCE, BOTH DIRECTIONS, ON THE REAL RUNTIME (Rule 6 -- no module
was loaded on the host; everything below ran under QEMU).

New suite tests/qemu/test_kmod_vmsfs_exepath.c, 28 assertions:
  pre-fix  3 phases red, incl. "child: /proc/self/exe after re-open =
           /mnt/.../CHILD.EXE (deleted)" -- the product symptom reproduced
           at the kernel layer -- then the kernel OOPSES in __fput()
           (NULL d_inode) when the held fd is closed.
  post-fix 28/28 green.
Phase 3 execs this program from a real block-device vmsfs and has the
child do exactly what IMGACT does (re-open its own image by path) and
then what SPAWN does (re-exec via /proc/self/exe). Phase order puts it
first precisely so it is REACHED before the pre-fix oops kills the
process. Phase 2 carries the POSITIVE CONTROL that makes the file
non-vacuous: once PROBE.TXT;2 exists, the fd held on ;1 MUST become
"(deleted)", because the unversioned name no longer names it -- a
d_revalidate that just answers "valid" passes everything else here and
goes red on that one.

tests/uat/vms_session_qemu.sh on the vms-d0f5e VMS-native fat initramfs:
  pre-fix  52/54 (both SPAWN assertions red)
  post-fix 54/54
A SECOND spawn ('SPAWN SHOW TIME') and two assertions on it are added
here, because the long-standing "second SPAWN" defect is fixed by
the same change -- measured, not assumed. With them: 52/56 pre-fix,
56/56 post-fix. The comment block that recorded that defect as a
DCL bug is corrected in place rather than removed.

No regression: full QEMU kernel harness 31/31 suites (incl. the existing
test_kmod_vmsfs and test_kmod_vmsfs_blkdev version-semantics suites),
test_persistent_boot.sh 14/14, test_executive_integral.sh 14/14, Rule 9
runtime-target gate, divider integrity, harness verdict, kif caller
census, identity census, facility manifest selftest+coverage.

tests/qemu/facility_defects.sh: the new suite joins the two existing
vmsfs suites in SCOPE_OUT_SUITES for the reason already stated there --
it never opens /dev/vms, so no executive mutation can turn it red.

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

* vms-913.7: wire SYSTARTUP_VMS.COM's INSTALL ADD to the real Known Image DB

Checked current state first (per item instructions): vms-p78.3's install
unification and vms-d0f5e's real-file fat initramfs already held, and
test_persistent_boot.sh was passing -- but done condition #4 (SYSTARTUP_VMS.COM
carrying INSTALL ADD for each shareable) was not met, and fixing it surfaced
two real, ground-verified gaps beneath it:

1. DCL's INSTALL builtin (cmd_install) never dispatched to SYS$SYSTEM:INSTALL.EXE
   (src/install/install.c, vms-913.5's KFE-database utility). It reimplemented
   its own flat-text SYS$MANAGER:INSTALL_LIST.DAT that nothing ever read --
   IMGACT.EXE's known-image search (src/imgact/known_images.c) mmaps the binary
   VMS$KNOWN_IMAGES.DAT only INSTALL.EXE writes. Fixed by making cmd_install a
   thin wrapper that re-execs INSTALL.EXE via dcl_exec_utility(), the same
   pattern already used for ANALYZE/MAIL/SYSGEN/SYSMAN in this file -- matching
   install.c's own header comment ("deliberately NOT wired as a DCL builtin").

2. Once wired, real QEMU boot (docker build + test_persistent_boot.sh) caught
   that INSTALL.EXE was never copied into the fat initramfs, so every
   SYSTARTUP_VMS.COM INSTALL ADD failed with %INSTALL-F-NOIMG on live boot --
   and that failure silently aborted the rest of SYSTARTUP_VMS.COM despite
   SET NOON, dropping the "site startup ran" banner test_persistent_boot.sh
   checks for (14/14 -> 12/14). Fixed by adding INSTALL.EXE to
   Dockerfile.bootable's fat-initramfs SYSEXE copy list, alongside the other
   build-static utilities.

SYSTARTUP_VMS.COM now INSTALL ADDs exactly the 7 shareables the fat initramfs
actually ships (DECC$SHR, LIBVMSSYS$SHR, LIBVMS$SHR, LIBVMSPROCESS$SHR,
LIBVMSLNM$SHR, LIBVMSFS$SHR, LIBVMSRMS$SHR -- Dockerfile.bootable's own "9
VMS-native LINK.EXE artifacts" gate). LIBVMSQUEUE$SHR is deliberately excluded:
it builds via the ordinary CMake add_library() graph, not the VMS-native
LINK.EXE graph, and is not shipped in the fat initramfs -- INSTALLing it would
FILNOTFND on every boot.

Verified live: docker build -f distro/Dockerfile.bootable + test_persistent_boot.sh,
14/14 checks pass across both boots, with all 7 INSTALL-I-ADDED lines visible
each time (idempotent re-add on reboot, matching real VMS SYSTARTUP_VMS.COM
practice).

New test: tests/dcl/test_install_command.sh proves through real vmsdcl (not
around it) that INSTALL ADD/LIST/REMOVE write and clear the actual KFE binary
database (magic-byte check), not just matching text output -- the old stub
could print the same %INSTALL-I-ADDED text without touching the file IMGACT.EXE
reads.

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

* vms-913.11: verify x86_64 boot-to-login is already proven, fix stale CI comments

Live-verified on this worktree's native x86_64 host: docker build
distro/Dockerfile.bootable + tests/uat/vms_session_qemu.sh boots real
QEMU (qemu-system-x86_64, no emulation) through IMGACT.EXE's x86_64
relocation path (RELATIVE/GLOB_DAT/JUMP_SLOT/TLSDESC) to a DCL login
prompt, drives a full scripted session, 56/56 checks passed. This is
already the uat-session CI job (Job 7), unconditional on every push
since GH runners are x86_64 natively.

Job 8/9 comments still said "aarch64-only until bead vms-913.11" as if
x86_64 support were still pending; it has its own native-runner
counterpart (Job 9b, imgact-x86_64) and the uat-session boot-to-login
proof. Corrected the comments to point at the now-complete state
instead of a stale forward reference.

Done condition satisfied by existing work (vms-8f5, vms-cd1, vms-2e4,
vms-a66, vms-00e, vms-d0f5e chain); no functional code change needed.

* vms-fbc: shipped-image ground-source gate now covers LOGINOUT.EXE too, not just DCL.EXE

vms-c39's done condition (STARTUP execs VMS-native LOGINOUT.EXE, which
authenticates against SYSUAF and execs VMS-native DCL.EXE, zero ld/ld.so)
was structurally unverifiable in the Docker CI container -- no /dev/vms
there. This item's job is proving the SUCCESSFUL leg under a real kernel.

Verified live, not assumed: that proof already exists and is repeatable.
- src/ovmx_init/ovmx_init.c execl()s SYS$SYSTEM:LOGINOUT.EXE.
- tools/vms_login.c (LOGINOUT) authenticates against SYSUAF
  (sysuaf_authenticate), then execl()s DCL.EXE --login.
- src/vmslink/link.c sets PT_INTERP=IMGACT.EXE on every LINK.EXE
  executable image -- ground truth that no ld.so is anywhere in this
  chain, not an inference.
- distro/Dockerfile.bootable's fat initramfs ships exactly one DCL.EXE
  and one LOGINOUT.EXE, both copied only from the VMS-native
  /link-native build (no static fallback exists for either anymore).
- tests/uat/vms_session_qemu.sh runs this exact chain under real QEMU
  in CI (job uat-session, .github/workflows/ci.yml), and vms-00e's own
  commit records a fresh 56/56 pass on this initramfs today.

The one real gap: the Dockerfile's own ground-source readelf gate (does
the SHIPPED image -- the actual bytes cp'd into the initramfs, not a
copy two directories away -- carry zero DT_NEEDED/DT_HASH) only checked
DCL.EXE. LOGINOUT.EXE is the FIRST VMS-native image in the login chain
and had no equivalent check on its shipped bytes; a regression that
silently reintroduced an ld-linked LOGINOUT.EXE ahead of DCL.EXE would
not have been caught by this gate (the generic 9-artifact loop in the
earlier link-native stage checks a build-output copy, not what actually
ships). Fixed by widening the existing gate to loop over both images.

Ground-truth verified locally: `docker build -f distro/Dockerfile.bootable
--target builder` -- real build, not mocked -- prints
"OK: shipped DCL.EXE is VMS-native (EM_X86_64, zero DT_NEEDED/DT_HASH)"
and "OK: shipped LOGINOUT.EXE is VMS-native (EM_X86_64, zero
DT_NEEDED/DT_HASH)" for the actual artifacts this build produced. The
new for-loop's shell logic was also unit-checked standalone: fails
correctly against a real dynamically-linked ELF (DT_NEEDED present) and
passes correctly against a statically-linked one, before spending a
build cycle on it.

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

* vms-913.10: prove slim-boot login+DCL activates from disk, not initramfs

The slim initramfs (STARTUP.EXE-only) and boot.sh --slim wiring already
existed (Dockerfile.bootable, boot.sh) but nothing exercised the boot
path: test_persistent_boot.sh's two boots both used the FAT initramfs.

Extends that harness with:
  - a static check that the slim initramfs cpio listing carries no
    DCL.EXE/LOGINOUT.EXE/IMGACT.EXE/SYSLIB (bootstrap-only, as designed)
  - Boot 3: boots the SAME installed disk with the SLIM initramfs, logs
    in as SYSTEM/MANAGER over the QEMU serial console (real SHA256-backed
    SYSUAF credentials, same as tests/uat/vms_session_qemu.sh), and runs
    SHOW TIME to a real DCL prompt

Since the slim initramfs structurally ships none of LOGINOUT.EXE,
IMGACT.EXE, DCL.EXE, or the SYSLIB shareables, a real login reaching a
working DCL prompt is functional proof they resolve from the mounted
system disk's SYS$SYSTEM:/SYS$LIBRARY:, not the initramfs. Measured
against a real QEMU boot: 25/25 checks pass.

Also adds `cpio` to the runner image's apt install list -- needed by the
new static check, absent from the base ubuntu:24.04 image (verified).

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

* vms-0c9: docs/install-0.1.md -- 0.1 install/boot/login walkthrough

Light release-eng doc scoped to 0.1 (download/build the fat-initramfs
image, first-boot install, reboot into the slim initramfs, log in to
DCL). Cluster admin / license audit / trademark review stay under
vms-d5b R6 for 1.0.

Ground-sourced: a real `docker build -f distro/Dockerfile.bootable -t
ovmx-boot .` was run on this checkout and reached the builder stage
before this shared host's disk filled and the build was aborted for
safety. Every command and every piece of documented console output
(the %STARTUP-I-* banners, %OVMX-I-EXEC, Username:/Password: prompts,
Welcome to OVMX, SHOW TIME) is instead quoted verbatim from GitHub
Actions run 31128513528 (commit 8560fa7), where the "Persistent Boot
Smoke Test" and "VMS User Acceptance Test" CI jobs build and boot this
same image and passed 14/14 + all UAT assertions -- the proven,
passing path tests/qemu/test_persistent_boot.sh and
tests/uat/vms_session_qemu.sh already exercise on every push. Login
credential (SYSTEM/MANAGER) confirmed against the real hash in
distro/rootfs/.../SYSUAF.DAT, not invented.

No git tag created -- that step is reserved for the operator's final
sign-off.

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

* vms-ade8: fix overclaimed CI citation in install-0.1.md ground-sourcing note

Run 31128513528 predates the slim-boot work (checked-out
test_persistent_boot.sh has zero slim references) and its overall
status was FAILURE (Build & Test + attribution negative-control jobs
red); only citing the two individually-passing jobs overclaimed
coverage. Rescope the note to state what was actually verified: this
swarm's own local docker build + real QEMU boots against the merged
tree (25/25 checks) for Section 3, and the UAT script's own run for
the DCL session content. No CI run number is cited as covering
slim-boot.

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

* vms-9bc: regenerate docs/design-link-x86_64-relocs.md, stale after vms-913.7

Re-ran tools/survey_x86_64_relocs.sh to refresh the empirical R_X86_64_PC32/
PLT32 counts in src/libvms/descrip.c, no methodology change.

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

---------

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

vms-9b7 design cascade check #2. PROVISION.EXE is now boot-critical (PID 1
execs it where it used to exec DCL.EXE). Adds a 'noprovision' case to
test_release_e2e.sh: a real SYSTEM session deletes SYS$SYSTEM:PROVISION.EXE
off the installed disk via DCL DELETE, and boot 2 must fail-stop with
'%OVMX-F-EXECINIT, SYS$SYSTEM:PROVISION.EXE is missing' + the identity detail
line, and must NOT reach a login prompt. Proves run_startup()'s stat() guard
fires with its own distinct message (not the SYSUAF halt in disguise) and that
the new boot-critical image is not a silently-skippable step. Fault injected
through the file; cannot false-pass (a delete that did not land -> boot 2 comes
up -> halt assertion fails). Verified 6/6 green against the fixed image.

Already wired into ci.yml via the existing test_release_e2e.sh mount.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 10, 2026
…3, no bash (vms-62b) (#255)

Close the Build-native 1.0 self-hosting fixpoint (vms-678 gate): OVMX builds a
nontrivial OVMX component -- the OVMX linker LINK.EXE itself -- from WITHIN OVMX,
driven from a DCL session with ZERO bash / no host tool in the fixpoint build
path, and proves it byte-stable.

Proven under arm64 QEMU (new CI job link-selfhost-native, mirrors build-com-native):
  * A host bootstrap linker BUILDS gen1 LINK.EXE (Rule 9: a build step is not an
    activation proof); gen1 is an OVMX-native image.
  * DCL session #1 runs @sys$SYSTEM:BUILD LINKSH LINK OVMX_LINK_RMS_IO. Inside it
    the multi-TU BUILD.COM has the OVMX-native TCC.EXE compile link.c +
    ovmx_link_rms_io.c and the OVMX-native gen1 LINK.EXE link them into gen2 --
    every toolchain step an IMGACT-activated OVMX image, no bash in the path.
  * gen2 is installed as SYS$SYSTEM:LINK.EXE; DCL session #2 runs the SAME @build
    and the OVMX-BUILT gen2 LINK.EXE links LINK.EXE again into gen3.
  * FIXPOINT: gen2 and gen3 are BYTE-IDENTICAL (sha256), the tcc gen2==gen3
    pattern (vms-4ba) now for LINK.EXE. This is S4 = "OVMX builds OVMX from within".

Multi-TU BUILD.COM (distro/rootfs/.../SYSEXE/BUILD.COM): extends the single-TU
S3.2 driver to compile N source TUs and link them into one image, via a real DCL
build loop -- SRC = P'N' parameter indexing, IF SRC .EQS. "" bare-symbol
termination, OBJLIST accumulation. New optional symbols: CFLAGS (extra tokens
ahead of the TCC compile) and NORUN (skip activating the linked image -- a linker
cannot be RUN with no args). `@BUILD HELLO` (single-TU, runs the image) is
preserved.

DCL symbol evaluation (vms-5c1), grounded in the DCL User's Manual and needed for
the loop:
  - dcl_symbol.c: ''symbol' (doubled apostrophe) now substitutes INSIDE a "..."
    string; a single apostrophe stays literal, as VMS.
  - dcl_exec.c: an UNQUOTED operand that names a defined symbol is auto-resolved
    in IF/WHILE string comparisons (IF P2 .EQS. "" tests P2's value) and on the
    right-hand side of an `=`/`==` assignment (A = B assigns B's value; SRC = P'N'
    parameter indexing). Quoted operands stay literal; undefined symbols keep the
    literal token (lenient, no %DCL-W-UNDSYM), matching parse_primary()/the
    integer-operand path. Covered by tests/dcl/test_symbol_eval.sh.

Full ctest 121/121 (parts_demo_e2e skips w/o runtime, as at baseline); DCL native
jobs unaffected. Deferred on vms-62b/vms-678: route the DCL LINK builtin to the
native LINK.EXE (vms-d5b7); F$ lexical coverage / more BUILD.COM ergonomics.

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 10, 2026
* WIP vms-df9: shared kit reader lib + vmsfs.ko setattr for protection/UIC persistence

Progress checkpoint, not yet complete:
- src/product/ovmx_kit_reader.{h,c}: factored the kit open/validate/
  read-entries/read-file-verified logic out of tools/ovmx_kit_pack.c into a
  shared module so PRODUCT.EXE (still to come) does not hand-roll a second
  kit parser. ovmx_kit_pack.c's list/extract now call it.
- src/kernel/vmsfs/vmsfs.h + vmsfs_blkdev.c: added .setattr to the
  block-device file/dir inode_operations, plus persisting owner UIC in
  vmsfs_blkdev_flush_inode(). Needed because PRODUCT INSTALL must stamp
  per-file protection/UIC from kit metadata onto the target volume, and
  neither chmod(2) nor chown(2) against a real vmsfs blkdev mount reached
  disk before this (no .setattr existed at all; fh_protection was written
  only from a cached field .setattr never touched, and fh_uic_* was never
  written back after creation). Compile-checked against the host's own
  6.8.0-136-generic headers.

Still to do: ovmx_product_db.h, src/product/product.c (PRODUCT.EXE itself),
CMake wiring for the new target, DCL cmd_product rewired to dcl_exec_utility,
QEMU ground-source proof, rebase onto latest origin/main.

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

* vms-df9: PRODUCT INSTALL lands a real kit on a real vmsfs volume

PRODUCT.EXE, not a DCL builtin (constraint #1): cmd_product
(src/vmsdcl/dcl_cmd_misc.c) is now a thin dcl_exec_utility() forwarder,
same shape as cmd_analyze/cmd_install/cmd_mail. It resolves /SOURCE
through dcl_resolve_path() and canonicalizes /DESTINATION, then hands
off to SYS$SYSTEM:PRODUCT.EXE (src/product/product.c), which owns kit
reading, file placement, protection/UIC, and the product database.

Kit reading is NOT reimplemented: src/product/ovmx_kit_reader.{h,c} is
the open/validate/read-entries/read-file-verified logic factored out of
tools/ovmx_kit_pack.c (which is refactored to call it too), so there is
exactly one kit parser in the tree. src/product/ovmx_product_db.h is the
new, OVMX-defined and Rule-8-labeled product database format
(SYS$SYSTEM:VMS$PRODUCT_DATABASE.DAT, whole-file-struct idiom matching
known_images.h's KFE database).

Security (constraint #2): every installed file's protection and owner
UIC come from its ovmx_kit_entry (fchmod(2)/fchown(2)), never a
default. This needed a real kernel change: src/kernel/vmsfs/
vmsfs_blkdev.c had no .setattr on its block-device inode_operations at
all, so chmod(2)/chown(2) against a real vmsfs mount silently updated
only the in-core inode -- fh_protection was written from a separate
cached field .setattr never touched, and fh_uic_* was never written
back after creation. Added vmsfs_blkdev_setattr() (persists protection
via the new vmsfs_mode_to_vmsprot() in vmsfs.h) and UIC persistence in
vmsfs_blkdev_flush_inode(). This also fixes DCL's SET PROTECTION, which
has called plain chmod() since it was written and never worked against
a blkdev-mode mount. Kept deliberately minimal: no new ioctl, no format
change, no vms.ko involvement -- chown(2) to a UIC other than the
caller's own still requires CAP_CHOWN via ordinary Unix semantics
(setattr_prepare), which no kit ovmx_kit_pack produces today needs.
Rule 4 cascade tracked as vms-79b (API compat GO, test coverage
confirmed, no doc impact beyond the code's own comments).

distro/Dockerfile.bootable: added the missing PRODUCT.EXE copy into
SYSEXE (measured failing first: %PCSI-F-NOIMG with it absent), and
stages a copy of the already-built, byte-verified /boot/ovmx-os.kit
onto the distrib image itself at SYS$UPDATE:OVMX-OS.KIT -- not a raw
third virtio disk, which devtmpfs creates root:root mode 0600 with no
udev to relax it in this minimal initramfs, so SYSTEM (uid 4/gid 1)
gets EPERM reading one directly. Reading the kit as an ordinary
SYSTEM-owned file on the already-mounted boot disk sidesteps that
instead of granting a permission OVMX has no privileged path for.

Ground-source proof (tests/qemu/test_product_install_e2e.sh, real
vms.ko+vmsfs.ko): INITIALIZE+MOUNT a second virtio disk, PRODUCT
INSTALL the real OS kit onto it, RUN the installed HELP.EXE FROM THE
TARGET (the anti-LARP crux -- a foreign-command definition, since RUN
never forwards argv and HELP.EXE needs a topic to avoid blocking on
interactive input), PRODUCT SHOW PRODUCT /DESTINATION=DKA100: lists it
by the kit's own embedded name, then a full QEMU restart proves both
the installed files and the product database persist on disk. All
15 checks pass. NOTIMPL baseline (every PRODUCT op before this bead)
verified by reading the pre-change cmd_product, matching this item's
own measured-baseline text.

Also fixes an INV-1 regression this work introduced along the way: a
version-literal-shaped example in an ovmx_product_db.h comment tripped
the identity SSOT gate (test_identity_ssot.sh) by landing on a code
line rather than a comment-only line the gate's exclusion regex skips.

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

* vms-df9 fixup: revert the vmsfs.ko .setattr change, it broke boot fail-stop

CI (PR #278) caught a real regression: tests/qemu/test_release_e2e.sh's
`norecord` case (SYSUAF rewritten with no SYSTEM row) must HALT at boot
with %OVMX-F-EXECINIT, no SYSTEM record -- with vmsfs_blkdev.c/vmsfs.h's
.setattr addition present, it silently booted to a login prompt instead.

BISECTED (not just suspected): reverting only these two files, rebuilding
the bootable image, and re-running test_release_e2e.sh made all 5 cases
pass including norecord's halt (30/0). Re-applying them reproduces the
failure. Root cause not further isolated -- the fix is the revert, not a
patch, per the tradeoff below.

DECIDED TO REVERT, NOT FIX IN PLACE, because the .setattr hook bought
PRODUCT INSTALL almost nothing: vmsfs_blkdev_create() already assigns
every new file VMSFS_PROT_DEFAULT and the creating process's own UIC, and
tools/ovmx_kit_pack.c's OVMX_KIT_PROT_DEFAULT / OVMX_KIT_UIC_*_DEFAULT are
numerically IDENTICAL to those (0xAA00, SYSTEM [1,4]) -- so for every kit
ovmx_kit_pack produces today, PRODUCT INSTALL's fchmod(2)/fchown(2) calls
in src/product/product.c were already redundant with what create() sets
at file-creation time. Without the kernel change those calls still run,
still return success (the kernel's generic simple_setattr() fallback),
and still fail loudly on a real error -- they just would not durably
override a value that DIFFERS from the create()-time default, which no
current kit needs. That is a real, but narrow and non-security-relevant
(a new file can never land more permissive than VMSFS_PROT_DEFAULT
regardless), gap -- tracked as a follow-up (vms-738) rather than carried
as boot-breaking kernel surface. src/product/product.c's comments are
updated to describe this honestly instead of claiming the hook is needed.

RE-VERIFIED GREEN after the revert, same rebuilt image:
  - tests/qemu/test_release_e2e.sh: 30 passed, 0 failed (all 5 cases,
    including norecord's halt).
  - tests/qemu/test_product_install_e2e.sh (vms-df9's own ground-source
    proof): 15 passed, 0 failed -- PRODUCT INSTALL, RUN of the installed
    HELP.EXE from the target, PRODUCT SHOW PRODUCT, and persistence across
    a full QEMU restart all still hold with the kernel change gone.

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
…XE integrated-assembler .s->.OBJ (#418)

Self-host spine #2 (vms-486). Two deliverables.

(A) Port MMK's 704-line MACRO-32 TPARSE table (freeware MadGoat MMK,
tests/corpus/tier3-mmk/parse_tables.mar — BSD, NOT VSI/HPE/DEC source) into
C against the OVMX lib$table_parse engine (vms-9f6), since the contract
forbids a MACRO-32 assembler. Both grammars (PARSE_DESCRIP 67 states,
PARSE_OBJECTS 25 states) are transcribed state-by-state into the OVMX
TPA_GRAMMAR format:
  - tests/libvms/mmk_parse_tables.{c,h} — the port (clean-room, Rule 8:
    structure/behaviour from the freeware .mar only; TPA_GRAMMAR layout is an
    OVMX design choice labelled in tpadef.h).
  - tests/libvms/test_mmk_parse_tables.c — drives real descrip.mms lines and
    object strings through the real engine and asserts the fired PRS_K_ /
    PO_K_ codes + tokens (directives incl. the V2.8 SUFFIXES_* variants,
    symbol defs, build rules, both dependency paths, command prefixes;
    object simple/list/library/module=file forms). No facade.

Porting the .mar surfaced three genuine engine-fidelity gaps in lib_tparse.c
that MMK's table requires; all fixed with the correct VMS semantics and
covered by new cases in test_lib_tparse.c:
  1. Keyword match must terminate on a word boundary, else 'SUFFIXES' swallows
     'SUFFIXES_AFTER' (V2.8's newest feature would be dead code).
  2. A failed transition must restore input position (undo pre-skipped
     blanks), or CMD_PREFIXED's BLANK->EXIT terminator never fires.
  3. A TPA$_LAMBDA (null) transition must not pre-skip/consume blanks, or a
     terminal lambda eats the trailing blank CONTINUE/SYMBOL1 depend on.

(B) TCC.EXE integrated-assembler path. TCC.EXE compiles in tccasm.c + the arch
asm backend; guess_filetype routes .s->AFF_TYPE_ASM->tcc_assemble; the OVMX
RMS-I/O seam delivers the .OBJ type-agnostically — so `.s->.OBJ` is wired.
src/imgact/test/run_tcc_asm_roundtrip.sh proves the assembler mechanism on the
exact vendored tinycc source (build, assemble GAS .s -> ELF .o, link, run).
Caveats documented: tinycc's assembler accepts a GAS subset (rejects gcc CFI/
endbr/section-flags); and on OVMX the assemble->LINK.EXE->activate round-trip
hits the SAME pre-existing GOT-local-symbol LINK.EXE gap as the .c path
(run_tcc_object_native.sh / vms-4ba.3) — a LINK.EXE issue, not an assembler one.

No new cross-image symbols (grammar/tests are test-side; engine already
exported), so no shareable-vector changes. Full Debug ctest: exit 0.

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 pushed a commit that referenced this pull request Aug 13, 2026
…ous bound), not a 2s reap grace

CI's Kernel Executive job went red from a clean build: mmk_build produced the
object and echoed the marker (build-#1 assertions GREEN) but MMK had not yet
finished tearing down its spawned DCL within the tight 2s REAP_GRACE, so reap1
stayed 0, the reap1 short-circuit skipped drive #2, and the completion +
byte-identity assertions reddened. CI's TCG is much slower than the dev host,
where reap1 was always 1.

Restructure drive_build to a SINGLE generous bounded wait (40s) that drains
output (detecting the marker) AND polls for MMK to exit, returning the instant
MMK exits -- so a green drive costs only its real runtime and the bound is only
ever hit by a genuine hang. Removes the split 10s-marker / 2s-reap phases that
were sized for a fast dev host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl pushed a commit that referenced this pull request Aug 13, 2026
…nstead of waiting on MMK's exit

The clean-build repro showed mmk_build's remaining flake was MMK's slow
self-exit under contended TCG: the compile finished, the marker was echoed and
the byte-identical object was on disk, but MMK had not yet torn down its spawned
DCL and exited within the bound, so the reap-based assertion reddened. MMK's
exit timing is not a property this suite tests.

drive_build now stops the instant the PROOF is captured -- the DCL echoed
OVMXD1B:COMPILED AND the object exists on disk -- and kills MMK as cleanup rather
than gating on its self-exit. A genuine mid-drive $HIBER deadlock still fails
hard (no marker is ever echoed). The completion assertion is now the marker
(reliable), the reap-exit assertion is dropped, and the drive-#2 short-circuit is
keyed on the object (robust under load), not on MMK's exit. The negctl declared
set is unchanged (the 5 object/byte-identity assertions; the marker stays green).

Verified: two consecutive clean green runs (mmk_build 6/6), and
mmk-build-image-not-activated reddens exactly the 5 object assertions with the
marker green. (A QEMU timeout / mmk_drive reap-flake seen intermittently here is
this 10-container dev host's load, not the code: on CI the harness completed and
mmk_drive passed.)

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
…byte-identical, zero-bash (CI gate) (#476)

* vms-d1b: self-host spine #6 — MMK drives a real TCC compile in QEMU, byte-identical, zero-bash (CI gate)

The shipped MMK.EXE now drives a REAL toolchain step inside OVMX in QEMU,
against a real /dev/vms: it spawns a persistent DCL over VMS mailboxes and
streams a descrip.mms whose action defines a foreign command TCC :== "$..." and
invokes it to compile the REAL src/libvmssys freestanding runtime TU
vms_string.c to an object. DCL activates the foreign command by fork()+execve()
of the staged static TCC.EXE (a plain static image is not in-process-eligible,
so imgact_activate returns SS$_UNSUPPORTED and DCL forks it) — the FIRST-EVER
TCC.EXE run inside QEMU and the first time MMK drives a real compiler (not a DCL
builtin) end to end. The parent (which never runs a compiler) asserts the driven
object is a valid ELF relocatable carrying vms_strlen and is BYTE-IDENTICAL
across two independent in-guest MMK-driven builds. Zero bash in the build path.

This closes spine #6 (vms-d1b, the CI gate) and the MMK-driven-EXECUTION residual
of spine #5 (vms-fe4) for the COMPILE stage.

- tests/toolchain/mk_tcc_static.sh: builds tinycc as a PLAIN STATIC (musl)
  foreign-command image (distinct from mk_tcc.sh's IMGACT-packaged self-host
  image) — the binary DCL fork+execve activates, no IMGACT/shareable staging.
- tests/toolchain/run_tcc_static_component.sh + CMake test
  toolchain-tcc-static-component: host proof the static TCC.EXE compiles the real
  runtime TUs (vms_string/vms_snprintf/driver) to valid, byte-identical objects;
  asserts vms_math.c is the documented x86 tcc-blocked TU (SSE "x" inline asm).
- tests/qemu/test_syssvc_mmk_build.c: the QEMU suite (extends spine #4's
  test_syssvc_mmk_drive.c); honest-skips 77 with no /dev/vms.
- tests/qemu/Dockerfile: stages static TCC.EXE at SYS$SYSTEM, tinycc's headers +
  musl's stdint.h closure beside it, and the real component source. The suite
  plugs into the STANDING kernel-executive CI barrier (builds the image from the
  checked-out tree, a clean context), so the MMK-driven native build is gated on
  every run — no new job.
- facility_defects.sh: new per-facility control mmk-build-image-not-activated —
  dcl_exec_foreign_command reports success WITHOUT activating the image, so the
  driven TCC command completes but runs no compiler; reddens exactly the suite's
  five object/byte-identity assertions FAST (no $HIBER wedge), attributable to
  the build drive alone. A DEDICATED control, not a second suite on the
  sp_send=0 drive control: two ~50s $HIBER wedges do not fit run_tests.sh's 120s
  QEMU budget in one boot. Floor 96 -> 97.

Verified in QEMU on this host: kernel-executive 76/76 (mmk_build 7/7);
mmk-build-image-not-activated reddens exactly the 5 object assertions with no
strays and the harness completes (no timeout); executive-absent mmk_build rc=77.

BUILD.COM retirement is NOT done: the full compile->archive->LINK-to-image chain
in-guest remains (vms_math not tcc-compilable on x86_64; LINK needs the
SYS$LIBRARY shareables staged + logical-name resolution in LINK.EXE + IMGACT
activation) — the precise residual for spine #7, documented in
docs/design-self-host-spine5-mmk-component.md. No red gate shipped (Rule 6/7).

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

* vms-d1b: fix mmk_build CI timing — wait for MMK to EXIT (single generous bound), not a 2s reap grace

CI's Kernel Executive job went red from a clean build: mmk_build produced the
object and echoed the marker (build-#1 assertions GREEN) but MMK had not yet
finished tearing down its spawned DCL within the tight 2s REAP_GRACE, so reap1
stayed 0, the reap1 short-circuit skipped drive #2, and the completion +
byte-identity assertions reddened. CI's TCG is much slower than the dev host,
where reap1 was always 1.

Restructure drive_build to a SINGLE generous bounded wait (40s) that drains
output (detecting the marker) AND polls for MMK to exit, returning the instant
MMK exits -- so a green drive costs only its real runtime and the bound is only
ever hit by a genuine hang. Removes the split 10s-marker / 2s-reap phases that
were sized for a fast dev host.

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

* vms-d1b: make mmk_build load-robust — capture proof (marker+object) instead of waiting on MMK's exit

The clean-build repro showed mmk_build's remaining flake was MMK's slow
self-exit under contended TCG: the compile finished, the marker was echoed and
the byte-identical object was on disk, but MMK had not yet torn down its spawned
DCL and exited within the bound, so the reap-based assertion reddened. MMK's
exit timing is not a property this suite tests.

drive_build now stops the instant the PROOF is captured -- the DCL echoed
OVMXD1B:COMPILED AND the object exists on disk -- and kills MMK as cleanup rather
than gating on its self-exit. A genuine mid-drive $HIBER deadlock still fails
hard (no marker is ever echoed). The completion assertion is now the marker
(reliable), the reap-exit assertion is dropped, and the drive-#2 short-circuit is
keyed on the object (robust under load), not on MMK's exit. The negctl declared
set is unchanged (the 5 object/byte-identity assertions; the marker stays green).

Verified: two consecutive clean green runs (mmk_build 6/6), and
mmk-build-image-not-activated reddens exactly the 5 object assertions with the
marker green. (A QEMU timeout / mmk_drive reap-flake seen intermittently here is
this 10-container dev host's load, not the code: on CI the harness completed and
mmk_drive passed.)

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 pushed a commit that referenced this pull request Aug 13, 2026
…aise per-drive bound

The first cut wired the LINK+activate onto BOTH in-guest MMK drives. Under slow/
contended TCG that reddened intermittently: the 40s per-drive bound elapsed AFTER
the archive but BEFORE the LINK (the drive was progressing, not wedged), killing
MMK mid-drive; and two heavy full-chain drives pushed the whole suite toward
run_tests.sh's 120s whole-VM budget. mmk_build passed 3x then failed once (7/11)
— a Rule 8 flake, not shippable.

Fix (mmk_build now 16/0 across repeated runs, whole VM 76/76 + green when the host
has capacity):
- Drive #1 is the lighter compile+archive drive (vms-6be); drive #2 runs the full
  compile→archive→LINK chain + activation. The compile+archive OUTPUTS are still
  asserted byte-identical across the two drives; the LINK runs once.
- The LINK OUTPUT's byte-identity is proven byte-identical on the host
  (run_mmk_component_build.sh links the image twice, cmp-clean); in-guest the rung
  proves the driven LINK yields a real image that ACTIVATES and RUNS (exit 216) —
  the property that could not be shown before. This keeps the suite close to the
  vms-6be weight the kernel-executive barrier already carries reliably.
- Per-drive bound raised 40s→60s so a slow-but-progressing full drive completes
  the LINK before cleanup.

Negctl mmk-build-image-not-activated: the two dropped image assertions
(build #2 / image byte-identity) removed from knock_on_fail; the four LINK+
activate assertions kept (produced / valid ET_DYN / PT_INTERP / activated 216),
knock_on_why updated. Assertion set: 1 require + 14 knock = the 15 non-completion
assertions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl pushed a commit that referenced this pull request Aug 13, 2026
…whole-VM CI budget

CI revealed the real budget: the kernel-executive gate runs ~76 suites in ONE
QEMU VM under run_tests.sh's fixed `timeout 120`, already near the edge on CI's
slower runner. The prior cut's mmk_build PASSED on CI (16/0), but the VM then hit
120s during the very NEXT suite (test_syssvc_mmk_drive), and the qemu timeout
(SIGTERM) reddened the whole run. My two-runtime-TU + LINK+activate suite was the
tipping weight.

Fix: each drive now compiles ONE runtime TU (vms_string), not two, making the
suite LIGHTER than the vms-6be compile+archive suite (−2 heavy TCC compiles,
+ a cheap driver compile + fast LINK + instant activation). Kept: two-drive
compile+archive byte-identity, the LINK+activate proof (drive #2), exit-216
activation. The archive is now single-member; LIBRARIAN archiving MULTIPLE members
and the LINK output's byte-identity remain host-proven
(run_mmk_component_build.sh). Assertion set: 1 require + 14 knock still equals the
15 non-completion assertions; negctl + docs updated to match.

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
…INK→ACTIVATE in QEMU (zero-bash CI gate) (#493)

* vms-725: self-host spine #7 FINAL rung — MMK drives compile→archive→LINK→ACTIVATE in QEMU, byte-identical, zero-bash (CI gate)

Extends spine #7 archive (vms-6be, #488) with the LINK-to-runnable-image rung.
The shipped MMK.EXE now drives the WHOLE OVMX-native build chain over its
persistent mailbox-driven DCL against a real /dev/vms, and the harness activates
the produced image through IMGACT to its oracle exit:

  TCC.EXE       compiles VMS_STRING.C + VMS_SNPRINTF.C + the driver OVMXRTRUN.C
  LIBRARIAN.EXE /CREATEs OVMXRT.OLB from the two runtime objects
  LINK.EXE      --executable --use DECC$SHR.EXE links OVMXRTRUN.OBJ + OVMXRT.OLB
                into OVMXRT.EXE (selectively pulling VMS_STRING from the library)
  [activate]    the harness fork+execs OVMXRT.EXE -> the kernel loads its
                PT_INTERP=/vms/.../IMGACT.EXE, which maps DECC$SHR from SYS$LIBRARY
                and binds the one cross-image import -> it RUNS to exit 216
                (vms_strlen("OVMXRT")*36 -- nowhere else for 216 to come from)

Every stage byte-identical across two in-guest MMK-driven builds. Green in QEMU:
test_syssvc_mmk_build 18/0, from a clean tests/qemu/Dockerfile build. Self-host's
final MMK-driven rung: MMK builds a real OVMX component to a running image
entirely inside OVMX.

Key findings / choices:
- Only DECC$SHR is --use'd, NOT the "six shareables" the residual anticipated:
  the component is freestanding (its sole external symbol is vms_strlen, in the
  .OLB), so the executable's only cross-image import is crt0/exit from DECC$SHR.
- The producer graph (IMGACT.EXE via src/imgact/Makefile; DECC$SHR.EXE via
  mk_decc_shr.sh whole-archiving musl libc.a + libgcc.a through the static
  LINK.EXE) BUILDS CLEAN in the ubuntu+musl-gcc Dockerfile -- the alpine-only
  assumption was unfounded.
- `LNK`, not `LINK`: LINK IS the built-in DCL verb; LNK (not a prefix of it)
  falls through to the foreign-command symbol and forks the staged LINK.EXE --
  the same trap as LIBRARIAN vs LIBRARY (vms-6be). The `$` in the absolute
  DECC$SHR.EXE --use path is an ordinary VMS filename char in the raw DCL tail.
- vms_snprintf stays archive-only: TCC compiles its varargs to tinycc's __va_arg
  helper, which DECC$SHR does not export, so the runnable image pulls VMS_STRING
  only. The .OLB still carries both members (proven).

Staging (tests/qemu/Dockerfile): build the static vmslink LINK.EXE + IMGACT.EXE
(make ARCH=x86_64) + DECC$SHR.EXE (mk_decc_shr.sh); stage LINK.EXE/IMGACT.EXE at
SYS$SYSTEM, DECC$SHR.EXE at SYS$LIBRARY, OVMXRTRUN.C in the component dir.

Negctl: the existing mmk-build-image-not-activated control now also reddens the
six LINK+activate assertions (no driven toolchain -> no image -> no exit 216);
all declared in its knock_on_fail with extended knock_on_why. Floor unchanged.

BUILD.COM STAYS (Rule 6/7). It is NOT cleared by this proof: it is still
load-bearing for the S4 self-host FIXPOINT (run_link_selfhost_native.sh:136 copies
+ drives it multi-TU to build LINK.EXE gen2==gen3, a 1.0 gate) AND
run_build_com_native.sh (S3.2). The vms-725 MMK chain builds a small 2-TU
component, not the multi-TU LINK.EXE self-host fixpoint. Retirement waits on
porting that fixpoint from BUILD.COM to an MMK descrip.mms -- tracked as vms-89d.

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

* vms-725: fix flaky mmk_build gate — run LINK+activate on one drive, raise per-drive bound

The first cut wired the LINK+activate onto BOTH in-guest MMK drives. Under slow/
contended TCG that reddened intermittently: the 40s per-drive bound elapsed AFTER
the archive but BEFORE the LINK (the drive was progressing, not wedged), killing
MMK mid-drive; and two heavy full-chain drives pushed the whole suite toward
run_tests.sh's 120s whole-VM budget. mmk_build passed 3x then failed once (7/11)
— a Rule 8 flake, not shippable.

Fix (mmk_build now 16/0 across repeated runs, whole VM 76/76 + green when the host
has capacity):
- Drive #1 is the lighter compile+archive drive (vms-6be); drive #2 runs the full
  compile→archive→LINK chain + activation. The compile+archive OUTPUTS are still
  asserted byte-identical across the two drives; the LINK runs once.
- The LINK OUTPUT's byte-identity is proven byte-identical on the host
  (run_mmk_component_build.sh links the image twice, cmp-clean); in-guest the rung
  proves the driven LINK yields a real image that ACTIVATES and RUNS (exit 216) —
  the property that could not be shown before. This keeps the suite close to the
  vms-6be weight the kernel-executive barrier already carries reliably.
- Per-drive bound raised 40s→60s so a slow-but-progressing full drive completes
  the LINK before cleanup.

Negctl mmk-build-image-not-activated: the two dropped image assertions
(build #2 / image byte-identity) removed from knock_on_fail; the four LINK+
activate assertions kept (produced / valid ET_DYN / PT_INTERP / activated 216),
knock_on_why updated. Assertion set: 1 require + 14 knock = the 15 non-completion
assertions.

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

* vms-725: shrink mmk_build to one runtime TU per drive — fit the 120s whole-VM CI budget

CI revealed the real budget: the kernel-executive gate runs ~76 suites in ONE
QEMU VM under run_tests.sh's fixed `timeout 120`, already near the edge on CI's
slower runner. The prior cut's mmk_build PASSED on CI (16/0), but the VM then hit
120s during the very NEXT suite (test_syssvc_mmk_drive), and the qemu timeout
(SIGTERM) reddened the whole run. My two-runtime-TU + LINK+activate suite was the
tipping weight.

Fix: each drive now compiles ONE runtime TU (vms_string), not two, making the
suite LIGHTER than the vms-6be compile+archive suite (−2 heavy TCC compiles,
+ a cheap driver compile + fast LINK + instant activation). Kept: two-drive
compile+archive byte-identity, the LINK+activate proof (drive #2), exit-216
activation. The archive is now single-member; LIBRARIAN archiving MULTIPLE members
and the LINK output's byte-identity remain host-proven
(run_mmk_component_build.sh). Assertion set: 1 require + 14 knock still equals the
15 non-completion assertions; negctl + docs updated to match.

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 14, 2026
…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>
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
…t add-on (#638)

Fast-follow to #636 (vms-9d4f). #636 fixed drive #1's (compile+archive)
budget to scale with the measured in-guest compile speed, but left drive #2
(do_link=1: compile+archive+LINK+activate) with a flat +20000ms add-on atop
the calibrated compile term instead of scaling that extra work too.

Robustness testing (docker run --cpus=<throttle>, simulating slow TCG) found
the gap: at ~8.5x throttle (t_cal 897ms vs ~106ms baseline), drive #2's
LINK+activate stage missed its budget under the OLD fully-fixed 60000ms
constant -- a real instance of the fixed-host-wall failure mode #636 targets.
But #636's shipped design (flat +20000) did not close this either: since the
flat add-on is capped and doesn't grow with slowdown, it degrades to the same
floor-dominated behavior as the old constant at any throttle level that
doesn't already clear the compile-margin term alone. This file's own history
already says the LINK+activate cost is TCG-speed-dependent (the 40000->60000
raise was explicitly "under slow/contended TCG the whole chain can exceed the
old bound") -- so it should scale with t_cal like the compile term does, not
sit behind a flat constant.

Fix: replace DRIVE_LINK_OVERHEAD_MS (flat +20000) with
DRIVE_COMPILE_MARGIN_LINK (6, vs drive #1's margin of 3), applied to the SAME
calibrated t_cal. Both drives now scale proportionally with measured guest
speed; only DRIVE_FIXED_OVERHEAD_MS (spawn/mailbox/dispatch/marker, 8000ms)
stays flat. On a fast host this is unchanged (both drives still floor at the
historical 60000ms neighborhood -- verified 16/16 green under the faithful
gate, docker build -f tests/qemu/Dockerfile + docker run, 1533/1533
assertions passing). On a slow host both drives' budgets now grow with the
SAME multiplier the guest is actually running under.

SEMANTIC assertion unchanged: MMK.EXE still drives the real spawn + mailbox +
write-attention AST + $HIBER + IO$M_NOW + $STATUS path to the real
OVMXD1B:COMPILED marker; facility_defects negctl anchor (mmk-build-image-not-
activated) untouched.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 18, 2026
…fix SYSUAF encrypt byte to 3, drop false purdy claim

BLOCKER #1 (rms-put-wrong-vbn negctl was a dead no-op): the refactor moved
the a.vbn computation out of the rms_io_write() dispatch wrapper into the
static rms_io_write_acp() backend, so the sed anchored to /^ssize_t
rms_io_write/,/^}$/ matched nothing -- cmd_apply reported BROKEN FIXTURE
(rc=3), reddening the kernel shard, and the correct-VBN $PUT-over-ACP
guarantee went unguarded. Re-anchor to /^static ssize_t
rms_io_write_acp/,/^}$/. Verified: apply now injects (rc=0) on the +1u->+2u
line in write_acp only (read_acp untouched); a second apply is BROKEN FIXTURE
(idempotency intact); facility_defects.sh selftest now reports the defect ok.

BLOCKER #2 (SYSUAF UAF$B_ENCRYPT off-by-one, resolves vms-722): uaidef.h had
AD_II=1/PURDY=2/PURDY_V=3/PURDY_S=4, so OVMX stamped 4 on disk and rejected
!=4 -- a non-authentic byte that would reject a genuine VMS SYSUAF. The oracle
DUMP measured 0x03 on VAX 7.3 + Alpha 8.4 and public $UAIDEF enumerates
PURDY_S=3; corrected uaidef.h to AD_II=0/PURDY=1/PURDY_V=2/PURDY_S=3 (CUST=128,
the customer-algorithm base). Regenerated the seed SYSUAF.DAT via mksysuaf --
SYSTEM data record now carries encrypt=0x03; byte-reproducible. Reader/writer
both key off UAI$C_PURDY_S so they agree at 3. Pinned the test to the literal
oracle value (b[0x168]==3, UAI$C_PURDY_S==3). Reconciled the sysuaf.h
source-of-truth note (conflict RESOLVED, answer 3).

SMELL (purdy.c overclaim): the header claimed verification against "200
published DecHpwd PURDY_S calibration vectors -- see test_purdy.c", but
test_purdy.c carries only the 7 real-VMS oracle vectors. Sourcing/fabricating a
200-vector corpus in-rung is infeasible/forbidden (circular self-assertion),
so the false cross-reference is DELETED; the true 7-oracle-vector claim stays.

Verify: 46/46 libvms/rms functional ctests green (incl. sysuaf record byte==3,
vmsrms_sysuaf_live SYSTEM/MANAGER, purdy 7-vector); rms-put-wrong-vbn selftest
ok. Pre-existing selftest FAILs (rightslist-general-hex-as-decimal,
sysuaf-uic-writeback-decimal stale anchors; rms_p3 no-anchor; missing assertion
text) are OUT OF SCOPE for this rung -- flagged to conductor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 22, 2026
B rung #2: getdvi/showdev need ETH0:. virtio-net-pci + 5 virtio-blk hangs the
clipper boot pre-/init (the virtio combination). Feasibility resolved cheaply
first: the executive enumerates ANY netdev as ETH0: (generic for_each_netdev,
exec_kbackend.h -- NOT virtio-net-specific), and the Alpha kernel already has
CONFIG_TULIP=y. So use a DEC Tulip (21143) NIC -- non-virtio, driver present,
period-appropriate for Alpha -- which brings ETH0: up without the virtio-net
hang. One boot to confirm it doesn't hit a PCI-device-count limit too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl added a commit that referenced this pull request Aug 28, 2026
… (block-then-grant on the requester) (#875)

* vms-6ca: DLM H5 executive core — requester-side GRANT RECEIVE + deferred-grant report

The cross-node contention semantics (#873) proved block-then-grant on a single
real /dev/vms. This adds the two executive halves the async-reply SCS wire needs:

- MASTER side: vms_lock_dlm_xnode_deq now REPORTS the queued cross-node waiter
  that a release flipped to granted (its requester CSID + original req_lkid +
  master handle + granted mode), through the fields a DEQ otherwise leaves 0, so
  the daemon can WIRE a deferred GRANT to the requester without a second probe.
  The master lock entry now carries the requester's own lock handle (req_lkid).

- REQUESTER side: VMS_DLM_OP_GRANT is implemented (was SS$_UNSUPPORTED). A GRANT
  / queued-reply the master sent back completes an executive-resident ORIGIN
  record — the requester-side proxy of the outstanding request, on its own list,
  never touched by the local lock manager. Its granted mode is set ONLY from what
  the master genuinely sent over SCS: a queued-reply (mode NL) leaves it pending,
  a deferred GRANT (mode EX) flips it NL->EX. GETLKI falls through to it, so the
  status flip is observable on the REQUESTER node. INV-6: genuine executive
  state, no fabricated grant.

BLKAST as a receive op stays SS$_UNSUPPORTED — the BLKAST wire is deferred
honestly on this rung (the block-then-grant round-trip is proven without it).

test_syssvc_dlm_xnode.c extended to prove both halves against a real /dev/vms
(x86_64 + Alpha LP64): the deferred-grant report on the block-then-grant $DEQ,
and the requester-side queued->granted origin flip. vms.ko builds clean.

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

* vms-6ca: DLM H5 SCS wire — queued-reply + deferred-GRANT delivery over the live VC

The scsd daemon now carries the async DLM replies over the real SCS wire, so the
block-then-grant completes on the REQUESTER node across the network — not just in
the master's local executive (#873).

Node B (master): the GRANT frame it sends back now carries the granted mode
honestly — a queued-reply carries NL (the requester stays genuinely pending), a
grant carries the granted mode — plus the master's lock handle, so the requester
can $DEQ the holder. On a cross-node $DEQ that flips a queued waiter to granted,
B reads the executive's deferred-grant report and WIRES an unprompted deferred
GRANT (SS$_NORMAL, the granted mode) to that requester (SCSD-I-DLMDEFER).

Node A (requester, armed by OVMX_DLM_H5): drives block-then-grant over the wire —
holds RESONE EX (#1), sends a second incompatible $ENQ (#2) that QUEUES on B,
dispatches B's queued-reply into its OWN executive so #2's origin record is
genuinely PENDING (GETLKI->NL, SCSD-I-DLMPEND), releases the holder (#1), and on
B's deferred GRANT dispatches it into its executive — the origin record FLIPS
NL->EX (GETLKI->EX, SCSD-I-DLMH5FLIP). The status flip is read from A's real
executive, driven only by what the master sent over SCS. INV-6: no fabricated
wire reply or grant; the BLKAST wire stays deferred (the holder releases on its
own). scsd builds clean; vmsscs unit tests green.

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

* vms-6ca: DLM H5 two-node QEMU harness + CI job — block-then-grant over the wire

Extends the H4 two-node real-/dev/vms harness to H5. Both nodes boot vms.ko and
complete the VMS$VAXcluster join; node A is armed OVMX_DLM_ENQ=RESONE
OVMX_DLM_H5=1 and drives the block-then-grant sequence over the live SCS wire:
holds RESONE EX, a second incompatible $ENQ QUEUES on node B (B WIREs a
queued-reply -> A's requester origin record PENDING, SCSD-I-DLMPEND), A releases
the holder, node B WIREs the deferred GRANT off that real $DEQ (SCSD-I-DLMDEFER),
and A's origin record FLIPS NL->EX (SCSD-I-DLMH5FLIP granted_mode=EX).

H5 PASS asserts the status flip on the REQUESTER node A across the wire, plus B's
deferred-grant wire — read from the nodes' own SCSD logs, never fabricated
(INV-6). New Dockerfile.dlm-harness-h5 + init_dlm_h5.sh + run_dlm_harness_h5.sh;
new dlm-harness-h5 CI job (path-filtered on the DLM sources + harness files); the
existing dlm_harness filter already covers vms_lock.c + src/vmsscs/**. Shell +
YAML syntax validated.

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

* vms-6ca: DLM H5 — record the async-reply wire + requester-side completion in the compat ledger

docs/compat/facilities/cluster-dlm.yaml (single-ledger source, INV-LEDGER): new
cluster-dlm$async-reply-wire item for the H5 rung — the master WIREs the
queued-reply + deferred GRANT over SCS, the requester-side GRANT RECEIVE completes
an executive-resident origin record, and the NL->EX flip is observed on the
requester. Notes the BLKAST wire deferred honestly and multi-peer deferred-grant
routing as a later rung. Summary updated; H5 evidence points at the two-node
harness. YAML validated.

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

* vms-6ca: regenerate compat surface register for the H5 async-reply-wire item

Derived docs/compatibility-surface.md re-rendered from docs/compat/*.yaml via
tools/compat/render_compat.py (INV-LEDGER: yaml is source, md is generated).
407 items now catalogued.

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

* ci: quote the job name with a mid-value colon — repairs ci.yml parse (dark since #850)

The job name added today by #850 (vms-dec) contained an unquoted mid-value colon
('... before Username: (vms-dec)'), which is invalid YAML. GitHub Actions could
not parse ci.yml, so EVERY run of it — on main and every branch — completed with
ZERO jobs (no Build & Test, no harnesses, nothing); the only checks still firing
were the separate Ledger-drift and CodeQL workflows. Quoting the value restores
the whole workflow. Pre-existing project-wide breakage, surfaced while wiring the
H5 CI job (which could not run until ci.yml parsed).

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

* vms-6ca: register the H5 client sender in the send-site census + repair H0's stale selftest op

Two fixes surfaced by re-enabling ci.yml:

1. scs_send_sites census: the new scsd_dlm_client_send_op() sender (node A's
   contending $ENQ + holder $DEQ) is added to the CHOKED half of the SEND SITE
   TABLE in scsd.c; the scsd_dlm_srv_msg_input entry now notes the deferred GRANT
   it also wires. test_scsd_send_sites: 109 checks, 0 failures.

2. DLM Harness H0 selftest: it drove a DEQ expecting SS$_UNSUPPORTED (2296), but
   rung 3 (#873, vms-904c) made DEQ real — a DEQ of an unknown handle now returns
   SS$_IVLOCKID (8484), so H0 had been red since #873 (hidden by the dark CI). The
   side-effect-free "executive reached, honest 2296" probe now uses BLKAST, whose
   receive op still honestly returns SS$_UNSUPPORTED (its wire is deferred, vms-6ca)
   and mutates no lock state. No run/init/ci grep change needed — still rc=2296 PASS.

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 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 11, 2026
…es-circuit (38→37)

Give kernel-core/vms_pe.c a real injected negative control so it leaves the §1
"executive TU with NO negative control" FAIL set (38->37).

Defect pe-vc-snapshot-fabricates-circuit: vms_pe_vc_snapshot()'s `cl->pe == NULL`
guard returns SS__NORMAL instead of SS__NOSUCHDEV (range-scoped sed, only that
one return), so a VC row the executive does not hold — an index far past the
table — is reported as a live circuit (INV-6 fabrication: a placeholder reported
real; the all-zero row is untouched, only the STATUS lies). suites_red:
test_kmod_cluster_vc_diag; require_fail "row VC, index far past any table:
SS$_NOSUCHDEV, not a crash" (anchored in the suite). The index-0 real-VC and
no-circuit-all-zero branches read other paths and stay green.

Static-proven (host): selftest injects + idempotent no-op-reapply teeth; coverage
§1 drops vms_pe.c (38->37) + clears test_kmod_cluster_vc_diag from §2; dash -n
clean. QEMU red-proof rides the CI per-facility negctl shard (green-by-SHA), the
conductor's reap gate — same standard l2's local rail run validated the loop for.

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
…es-circuit (38→37)

Give kernel-core/vms_pe.c a real injected negative control so it leaves the §1
"executive TU with NO negative control" FAIL set (38->37).

Defect pe-vc-snapshot-fabricates-circuit: vms_pe_vc_snapshot()'s `cl->pe == NULL`
guard returns SS__NORMAL instead of SS__NOSUCHDEV (range-scoped sed, only that
one return), so a VC row the executive does not hold — an index far past the
table — is reported as a live circuit (INV-6 fabrication: a placeholder reported
real; the all-zero row is untouched, only the STATUS lies). suites_red:
test_kmod_cluster_vc_diag; require_fail "row VC, index far past any table:
SS$_NOSUCHDEV, not a crash" (anchored in the suite). The index-0 real-VC and
no-circuit-all-zero branches read other paths and stay green.

Static-proven (host): selftest injects + idempotent no-op-reapply teeth; coverage
§1 drops vms_pe.c (38->37) + clears test_kmod_cluster_vc_diag from §2; dash -n
clean. QEMU red-proof rides the CI per-facility negctl shard (green-by-SHA), the
conductor's reap gate — same standard l2's local rail run validated the loop for.

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
…es-circuit (38→37)

Give kernel-core/vms_pe.c a real injected negative control so it leaves the §1
"executive TU with NO negative control" FAIL set (38->37).

Defect pe-vc-snapshot-fabricates-circuit: vms_pe_vc_snapshot()'s `cl->pe == NULL`
guard returns SS__NORMAL instead of SS__NOSUCHDEV (range-scoped sed, only that
one return), so a VC row the executive does not hold — an index far past the
table — is reported as a live circuit (INV-6 fabrication: a placeholder reported
real; the all-zero row is untouched, only the STATUS lies). suites_red:
test_kmod_cluster_vc_diag; require_fail "row VC, index far past any table:
SS$_NOSUCHDEV, not a crash" (anchored in the suite). The index-0 real-VC and
no-circuit-all-zero branches read other paths and stay green.

Static-proven (host): selftest injects + idempotent no-op-reapply teeth; coverage
§1 drops vms_pe.c (38->37) + clears test_kmod_cluster_vc_diag from §2; dash -n
clean. QEMU red-proof rides the CI per-facility negctl shard (green-by-SHA), the
conductor's reap gate — same standard l2's local rail run validated the loop for.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMDFjHCqxDuXgbyxNU572h
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant