Skip to content

vms-8d4: SHOW CLUSTER reflects real SCS membership (INV-DCL facade-kill) - #422

Merged
baron-3dl merged 3 commits into
mainfrom
vms-8d4-show-cluster-real
Aug 13, 2026
Merged

baron-3dl merged 3 commits into
mainfrom
vms-8d4-show-cluster-real

Conversation

@baron-3dl

Copy link
Copy Markdown
Contributor

Problem

cmd_show_cluster() (src/vmsdcl/dcl_cmd_show.c) hardcoded %SYSTEM-I-NOTMEMBER unconditionally — so a genuinely-clustered node lied about being standalone, even though src/vmsscs/ implements the full SCS stack (datalink, NISCA VC, connection manager, MSCP serve, the vms-694 rejoin epic). The live membership lived only inside the SCSD process's in-memory peer table, unreachable by the separate DCL process. This is the top INV-DCL facade.

Fix — the real query path

Implements the local IPC that docs/design-cluster-node.md §3.1 already specifies ("SCSD exposes a local IPC ... to userspace consumers (SHOW CLUSTER, mount)"), in its simplest honest form: SCSD publishes its live member set to a well-known file; consumers read it. The file IS the daemon's real state (Rule 9 / INV-6) — not a per-process fake. When the daemon is down or this node has not joined, the file is absent and NOTMEMBER is the honest answer, exactly as a real non-member VMS node reports.

  • src/vmsscs/include/scs_membership.h — header-only (all static inline, so no shareable-image symbol-vector wiring; sidesteps the native-link cascade). Byte format is an OVMX invention, labeled as such (Rule 8). Path resolves via $OVMX_CLUSTER_STATE_PATH, else <$OVMX_SYSGEN_PATH>.members, else a default.
  • src/vmsscs/scsd.cscsd_publish_membership() writes the live view (~1 Hz off the main loop): local node plus every peer whose VMS$VAXcluster connection reached OPEN (vaxcluster_open_reached) or that received the coordinator's post-commit membership burst (membership_bursts). Cleared on departure/exit.
  • src/vmsdcl/dcl_cmd_show.c — reads the published view, renders the OpenVMS SHOW CLUSTER default report (NODE/SOFTWARE/STATUS banner, Rule 8 — modeled on public docs). Software version shown only for the local node (peer software is genuinely unknown, so not claimed).

Sibling audit

  • F\$GETSYI("CLUSTER_MEMBER") / CLUSTER_NODES (src/libvms/syssvc/sys_misc.c) — shared the facade (read the static SYSGEN VAXCLUSTER flag / hardcoded 1). Fixed in-scope: both now read the same live membership, consistent with SHOW CLUSTER.
  • SHOW CLUSTER/CONTINUOUS (SCACP live-updating TUI) — now shows the real snapshot rather than lying, but the continuous live-refresh screen is a larger piece of work. Filed as a follow-up, not a membership lie.

Tests (run foreground, all pass)

  • tests/vmsscs/test_scs_membership.c — publish/read/clear round-trip at the real IPC boundary (does not mock the function under test): absent → NOTMEMBER; 2-node set round-trips; unknown-name peer keyed on SCSSYSTEMID; clear → NOTMEMBER.
  • tests/integration/test_show_cluster_membership.shRUNS DCL against a membership file written in SCSD's own publish format (the real daemon↔DCL boundary): member set shown when present, NOTMEMBER when absent/empty, unknown-name peer shown by SCSSYSTEMID.
  • Regression: vmsscs_scsd_wire_unit and all 27 fast scs-labelled tests green; runtime_target_gate + negctl green.

Requires lab-2 to fully verify: an end-to-end proof that SCSD actually publishes the file on a real 2-node join (this PR proves the wiring and the honest standalone/member split without a live cluster).

🤖 Generated with Claude Code

alice and others added 3 commits August 13, 2026 01:59
cmd_show_cluster() hardcoded "%SYSTEM-I-NOTMEMBER" unconditionally, so a
genuinely-clustered node lied about being standalone even though src/vmsscs/
implements the full SCS stack (datalink, NISCA VC, connection manager, MSCP
serve, the vms-694 rejoin epic). The membership state lived only inside the
SCSD process's in-memory peer table with no way for the separate DCL process
to read it.

Implement the local IPC docs/design-cluster-node.md §3.1 already specifies
("SCSD exposes a local IPC ... to userspace consumers (SHOW CLUSTER, mount)"),
in its simplest honest form: SCSD publishes its live member set to a well-known
file, consumers read it. The file IS the daemon's real state (Rule 9 / INV-6),
not a per-process fake — when the daemon is down or this node has not joined,
the file is absent and NOTMEMBER is the honest answer, exactly as a real
non-member VMS node reports.

- src/vmsscs/include/scs_membership.h: header-only (all static inline, so no
  cross-image symbol vector wiring) publish/read/clear IPC. File byte format is
  an OVMX invention and labeled as such (Rule 8); resolves via
  $OVMX_CLUSTER_STATE_PATH, else <$OVMX_SYSGEN_PATH>.members, else a default.
- src/vmsscs/scsd.c: scsd_publish_membership() writes the live view (~1 Hz off
  the main loop) — local node plus every peer whose VMS$VAXcluster connection
  reached OPEN (vaxcluster_open_reached) or that received the coordinator's
  post-commit membership burst (membership_bursts). Cleared on departure/exit.
- src/vmsdcl/dcl_cmd_show.c: cmd_show_cluster() reads the published view and
  renders the OpenVMS SHOW CLUSTER default report (NODE/SOFTWARE/STATUS banner,
  Rule 8 — modeled on public docs); NOTMEMBER only when genuinely not a member.
  Software version shown only for the local node (peer software is unknown).
- src/libvms/syssvc/sys_misc.c: SIBLING facade-kill — F$GETSYI CLUSTER_MEMBER
  and CLUSTER_NODES now read the same live membership instead of the static
  SYSGEN VAXCLUSTER flag / a hardcoded 1, keeping them consistent with SHOW
  CLUSTER.

Tests (run foreground, pass):
- tests/vmsscs/test_scs_membership.c: publish/read/clear round-trip at the real
  IPC boundary (not mocking the function under test).
- tests/integration/test_show_cluster_membership.sh: RUNS DCL against a
  membership file in SCSD's own publish format — member set shown when present,
  NOTMEMBER when absent/empty, unknown-name peer keyed on SCSSYSTEMID.
Full fidelity against a live 2-node cluster requires lab-2 (SCSD actually
publishing on a real join); this proves the wiring and the honest split.

SHOW CLUSTER/CONTINUOUS (SCACP live-updating TUI) remains a snapshot — filed
as a follow-up, not a membership lie.

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

The native-link graph (link_native_graph, alpine-musl) has its own include-path
lists and its own DECC$SHR symbol vector, independent of CMake's
target_include_directories. My SHOW CLUSTER / F$GETSYI change slipped past the
glibc Debug build (which reported OVMX_LINK_NATIVE off) and reddened 5 native-
link jobs. Reproduced in the alpine:3.20 x86_64-musl container via
`cmake --build --target link_native_graph`:

1. Compile error: mk_libvms_shr.sh (sys_misc.c) and mk_dcl.sh (dcl_cmd_show.c)
   could not find scs_membership.h -> added -I src/vmsscs/include to both.
2. Link error: %LINK-F-ERROR, unresolved external symbol 'strftime' -- SHOW
   CLUSTER's date banner is the first native-linked consumer of strftime, which
   was not in DECC$SHR's .vms$sv symbol vector -> appended strftime=PROCEDURE
   to mk_decc_shr.sh (append-only). All other RTL calls the change makes
   (fopen/fgets/fclose/sscanf/rename/remove/getpid/time/localtime_r/toupper/
   memset/strncpy/snprintf/...) were already exported.

No new OVMX cross-image symbol and no new DCL TU were introduced (the IPC is
header-only static inline), so the mk_dcl.sh TUS / run_dcl_native.sh NOBJ
enumerations are unaffected.

Verified: the full x86_64 native-link graph links clean -- 5 libs + DECC$SHR +
DCL.EXE + LOGINOUT.EXE = 9 artifacts, all EM_X86_64, zero DT_NEEDED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
run_dcl_native.sh (the "DCL.EXE VMS-native Link + Activate" job) compiles the
DCL TUs itself with its OWN include list before calling mk_dcl.sh, so it needs
-I src/vmsscs/include for dcl_cmd_show.c's scs_membership.h include too. The
NOBJ==23 guard is unchanged (no new TU added).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@baron-3dl
baron-3dl merged commit 2ef22dc into main Aug 13, 2026
60 checks passed
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>
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