From 9604f156b1f35266b21e1270a4b62d9b5c963d9f Mon Sep 17 00:00:00 2001 From: alice Date: Tue, 1 Sep 2026 23:41:24 +0000 Subject: [PATCH 01/28] scsd: drive the DLM rebuild-completion (op-04+op-03) to the coordinator (vms-3eb) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The coordinator counts a joiner once it COMPLETES the light-mode DLM directory-rebuild handshake on the coordinator's VMS$VAXcluster VC. OVMX reaches admission (receives the coordinator's op-06 membership burst, CSB member bit sets) and holds full connectivity, but stays BRK_NON / CLUSTER_NODES=2 because it drives ~zero cat-02 rebuild to the coordinator: it sends only the op-0d self-registration and never CLOSES that rebuild transaction the way a real joiner does (op-01 registrations → op-04 → op-03 COMMIT). This adds the completion OVMX was missing. On receiving the coordinator's op-06 admission burst, OVMX drives the op-04 completion + op-03 COMMIT to the coordinator (one-shot per epoch, re-armed on each new transition open), closing the directory-rebuild transaction its self-registration opened. HONEST BY CONSTRUCTION (INV-6): OVMX holds no persistent cluster lock (its DLM takes only transient RMS/ACP locks — scsd.c:1631 "OVMX holds no locks... revisit when it has a real lock manager to answer FROM"). So the completion registers NOTHING HELD — the builders zero the resource name (body[48:]) and the per-lock handle words (body[20:28]) and keep mode NL (body[30]=0): the honest "my rebuild contribution is complete, I hold nothing" signal. It fabricates no lock state and names no resource OVMX does not own. Frame structure reproduces the JOIN→COORD op-04/op-03 specimens on db20-b's validated member-body layout (Rule 8). This is the minimal A/B test for the CN=3 blocker: if the coordinator counts OVMX off a content-free completion, completion alone suffices; if not, the standing system-lock set is genuinely required — an executive completeness question (make OVMX's mount/XQP take real F11B$/MOU$/LNM$ locks), escalated separately. test_scs_member: new test_dlm_completion_holds_nothing pins the honesty guardrail (resname + handles + mode all zeroed). SEND SITE census updated (cm_send_dlm_completion). Both files -fsyntax-only clean; member test + census green. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01PhM3QcmFEY3p8YNnHGaYwP --- src/vmsscs/include/scs_member.h | 10 ++++ src/vmsscs/scs_member.c | 70 ++++++++++++++++++++++++++ src/vmsscs/scsd.c | 89 +++++++++++++++++++++++++++++++++ tests/vmsscs/test_scs_member.c | 44 ++++++++++++++++ 4 files changed, 213 insertions(+) diff --git a/src/vmsscs/include/scs_member.h b/src/vmsscs/include/scs_member.h index b2a6c263b..d0bda0f1c 100644 --- a/src/vmsscs/include/scs_member.h +++ b/src/vmsscs/include/scs_member.h @@ -338,6 +338,16 @@ int scs_member_build_dlm_response(const struct scs_member_params *p, int scs_member_build_dlm_selfreg(const struct scs_member_params *p, uint8_t out[SCS_MEMBER_FRAME_LEN]); +/* The joiner's rebuild-COMPLETION pair driven to the coordinator after the + * self-registration: cat 0x02 op 0x04 (completion), then op 0x03 COMMIT. OVMX + * registers NOTHING HELD (resname + per-lock handles zeroed) -- the honest + * "rebuild contribution complete, I hold nothing" signal (vms-cn3). See + * scs_member.c for the INV-6 guardrail. */ +int scs_member_build_dlm_op04(const struct scs_member_params *p, + uint8_t out[SCS_MEMBER_FRAME_LEN]); +int scs_member_build_dlm_commit(const struct scs_member_params *p, + uint8_t out[SCS_MEMBER_FRAME_LEN]); + /* Current time as a VMS 64-bit absolute time (100 ns since 17-NOV-1858). */ uint64_t scs_member_vms_time_now(void); diff --git a/src/vmsscs/scs_member.c b/src/vmsscs/scs_member.c index add86d317..fce36f69b 100644 --- a/src/vmsscs/scs_member.c +++ b/src/vmsscs/scs_member.c @@ -752,6 +752,76 @@ int scs_member_build_dlm_selfreg(const struct scs_member_params *p, return 0; } +/* + * scs_member_build_dlm_op04 / scs_member_build_dlm_commit - the joiner's + * rebuild-COMPLETION pair (cat 0x02 op 0x04, then op 0x03 COMMIT) that a real + * joiner drives to the COORDINATOR after its op-01 registrations, closing the + * directory-rebuild transaction (vms-cn3 minimal-completion test). OVMX drives + * the completion but registers NOTHING HELD: it holds no persistent cluster + * lock (its DLM takes only transient RMS/ACP locks -- scsd.c:1631 "OVMX holds no + * locks... revisit when it has a real lock manager to answer FROM"), so this is + * the honest "my rebuild contribution is complete, I hold nothing" signal. + * + * The frame STRUCTURE is reproduced from the coordinator-rebuild specimens + * (JOIN->COORD F11B$aSYSDSK1, member-body layout validated by db20-b -- cat@8 + * op@9, the 0x00030001 status word @12:16, mode@30, resname@48, per-lock handles + * @20:24 + @24:28). INV-6 / honesty guardrail, enforced by construction: + * - resname (body[48:]) is ZEROED -> claims NO named resource. + * - the per-lock handle words (body[20:28]) are ZEROED -> claims NO held lock. + * i.e. the completion carries no resource and no lock handle OVMX cannot honestly + * back. Everything else replays the specimen's opaque structural bytes (Rule 8: + * reproduce the frame shape, invent nothing). If the coordinator counts OVMX off + * this content-free completion, completion alone suffices (OPT A); if not, the + * standing system-lock set is genuinely required (OPT B, a completeness call). + */ +static const uint8_t dlm_op04_struct[40] = { + /* body[ 8:16] */ 0x02, 0x04, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, + /* body[16:24] */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* [20:24] handle ZEROED */ + /* body[24:32] */ 0x00, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, /* [24:28] handle ZEROED */ + /* body[32:40] */ 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, + /* body[40:48] */ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* [47] len ZEROED (no resname) */ +}; +static const uint8_t dlm_commit_struct[40] = { + /* body[ 8:16] */ 0x02, 0x03, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, + /* body[16:24] */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* [20:24] handle ZEROED */ + /* body[24:32] */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* [24:28] handle ZEROED */ + /* body[32:40] */ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + /* body[40:48] */ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* op-03 commits BY HANDLE with no resname; here the handle is null too. */ +}; + +static int build_dlm_completion(const struct scs_member_params *p, + const uint8_t struct40[40], + uint8_t out[SCS_MEMBER_FRAME_LEN]) +{ + if (p == NULL || out == NULL) { + return -1; + } + build_common(p, member_config_tmpl, SCS_MEMBER_ENV_CREDIT_CONFIG, out); + uint8_t *body = out + 72; + memset(body + 4, 0, SCS_MEMBER_SCA_LEN - SCS_MEMBER_BODY_OFF - 4); + put_le16(body + 0, p->sysap_send_msg); + put_le16(body + 2, p->sysap_ack_msg); + put_le16(body + 4, p->txn); /* per-VC directory-tree tag (opaque, minted) */ + put_le16(body + 6, p->checksum); /* per-VC monotonic counter (opaque, minted) */ + memcpy(body + 8, struct40, 40); /* body[8:48] structural template */ + /* body[30] MODE stays 0x00 (NL) and body[48:] resname stays 0 from the memset: + * no held mode, no named resource -- the honest content-free completion. */ + return 0; +} + +int scs_member_build_dlm_op04(const struct scs_member_params *p, + uint8_t out[SCS_MEMBER_FRAME_LEN]) +{ + return build_dlm_completion(p, dlm_op04_struct, out); +} + +int scs_member_build_dlm_commit(const struct scs_member_params *p, + uint8_t out[SCS_MEMBER_FRAME_LEN]) +{ + return build_dlm_completion(p, dlm_commit_struct, out); +} + int scs_member_build_response(const struct scs_member_params *p, const uint8_t *req_frame, size_t req_len, uint8_t out[SCS_MEMBER_FRAME_LEN]) diff --git a/src/vmsscs/scsd.c b/src/vmsscs/scsd.c index 0ea234218..d757b411e 100644 --- a/src/vmsscs/scsd.c +++ b/src/vmsscs/scsd.c @@ -1049,6 +1049,7 @@ struct peer_state { * body[6:8] counter is ps->own_cksum (shared with the barrier steps); the * txn tag is the SCSD_DLM_DIR_TAG constant. */ int dlm_selfreg_sent; /* one-shot: self-reg emitted for the current epoch */ + int dlm_completion_sent; /* one-shot: op-04+op-03 rebuild completion driven to the coordinator (vms-cn3) */ /* rd vms-ec75 (DLM rung H11): DISTRIBUTED DEADLOCK SEARCH. A contender node * holds one resource (mastered by C) and $ENQs a second that QUEUES behind the * other contender -- a genuine cross-node wait-for cycle. When the wait queues, @@ -3673,6 +3674,11 @@ static ssize_t send_frame_raw(int sock, int ifindex, const uint8_t mac[6], * between barrier steps 4 and 5 on the one CM send_seq * stream; a null-value-block directory record, CHOKED * like every other sequenced VC message + * cm_send_dlm_completion() the cat-0x02 op-0x04 + op-0x03 COMMIT pair that + * CLOSES the rebuild transaction the self-reg opened, + * driven to the coordinator after its op-06 admission + * burst (vms-cn3); content-free (OVMX holds nothing), + * two sequenced VC messages, CHOKED like the self-reg * scs_reflect_credit() the op8->op9 / op6->op7 credit handshake reply * scs_send_disconnect_self() self-directed teardown, hand-built frame * @@ -6477,6 +6483,73 @@ static int cm_send_dlm_selfreg(int sock, int ifindex, struct peer_state *ps, return 0; } +/* + * cm_send_dlm_completion - drive the joiner's rebuild-COMPLETION pair (cat 0x02 + * op 0x04, then op 0x03 COMMIT) to the COORDINATOR, closing the directory-rebuild + * transaction the self-registration opened (vms-cn3). A real joiner runs this + * after its op-01 registrations; OVMX registers NOTHING HELD (it holds no + * persistent cluster lock -- scsd.c documents this), so both frames carry no + * named resource and no lock handle: the honest "my rebuild contribution is + * complete, I hold nothing" signal. This is the minimal A/B test -- if the + * coordinator counts OVMX off a content-free completion, completion alone + * suffices; if not, the standing system-lock set is genuinely required. + * + * Both frames are SEQUENCED SCS messages on the coordinator's OPEN VC, choked + * through send_frame_vc() like the self-reg and barrier steps (spec 4h lockstep). + */ +static int cm_send_dlm_completion(int sock, int ifindex, struct peer_state *ps, + const uint8_t our_hw_mac[6], + const uint8_t our_src_logical[6]) +{ + if (ps->cm_local_conid == 0) { + return 0; + } + int sent = 0; + /* op 0x04 completion, then op 0x03 COMMIT -- two frames, in order, on the + * one CM send_seq stream (continuous cksum, as the barrier + self-reg). */ + static const struct { + int (*build)(const struct scs_member_params *, uint8_t *); + const char *what; + const char *op; + } steps[2] = { + { scs_member_build_dlm_op04, "CM DLM rebuild completion (cat 0x02 op 0x04)", "0x04" }, + { scs_member_build_dlm_commit, "CM DLM rebuild COMMIT (cat 0x02 op 0x03)", "0x03" }, + }; + for (int i = 0; i < 2; i++) { + struct scs_member_params bp; + memset(&bp, 0, sizeof(bp)); + memcpy(bp.dst_mac, ps_port_addr(ps), 6); + memcpy(bp.src_mac, our_hw_mac, 6); + memcpy(bp.src_logical, our_src_logical, 6); + memcpy(bp.peer_logical, ps_sys_addr(ps), 6); + bp.remote_conid = ps->cm_remote_conid; + bp.local_conid = ps->cm_local_conid; + bp.incarnation = ps->incarnation; + bp.recv_ack = ps->vc.seq.recv_seq; + bp.send_seq = scs_seq_advance(&ps->vc.seq); + if (ps->sysap_send == 0) { + ps->sysap_send = 1; + } + bp.sysap_send_msg = ps->sysap_send++; + bp.sysap_ack_msg = ps->sysap_recv; + bp.txn = SCSD_DLM_DIR_TAG; /* SCS$DIRECTORY tree tag, as the self-reg */ + bp.checksum = ++ps->own_cksum; /* continuous per-VC counter */ + uint8_t dframe[SCS_MEMBER_FRAME_LEN]; + if (steps[i].build(&bp, dframe) == 0 && + send_frame_vc(sock, ifindex, ps, ps->pb, steps[i].what, + dframe, sizeof(dframe)) > 0) { + scs_vc_record_sent(&ps->vc, bp.send_seq, monotonic_ms()); + sent++; + log_ts(stdout); + printf(" SCSD-I-DLMCOMPLETE, originated rebuild completion %s to the" + " coordinator (holding nothing; cksum=0x%04x seq=%u)\n", + steps[i].op, bp.checksum, bp.send_seq); + fflush(stdout); + } + } + return sent; +} + /* * cm_send_ack - emit one category-0x04 SYSAP acknowledgement naming our current * high-water mark on the VC the connection-manager dialogue is riding. @@ -7296,6 +7369,21 @@ static void scsd_sysap_msg_input(struct scs_cdt *cdt, const void *msg, size_t ms ps_port_addr(ps)[4], ps_port_addr(ps)[5]); fflush(stdout); } + /* vms-cn3: having received the coordinator's op-06 admission + * burst, CLOSE the directory-rebuild transaction OVMX opened + * with its self-registration -- drive the op-04 completion + + * op-03 COMMIT to the coordinator, the frames a real joiner + * sends after its op-01 registrations and that OVMX currently + * never originates (it drives ~0 cat-02 to the coordinator). + * Content-free (OVMX holds no cluster lock -- honest "done, + * holding nothing"); one-shot per epoch. ps IS the coordinator + * here (the op-06 membership burst is its publication). This is + * the minimal A/B test: does completing the handshake count us? */ + if (!ps->dlm_completion_sent) { + ps->dlm_completion_sent = 1; + cm_send_dlm_completion(rx->sock, (int)rx->ifindex, ps, + rx->our_hw_mac, rx->our_src_logical); + } } /* vms-c21 (spec §4(O.33)): the cat 0x01 op 0x04 role 0x50 CM @@ -7545,6 +7633,7 @@ static void scsd_sysap_msg_input(struct scs_cdt *cdt, const void *msg, size_t ms * the SCS$DIRECTORY self-registration is re-emitted at this * barrier's start (cm_send_dlm_selfreg). */ ps->dlm_selfreg_sent = 0; + ps->dlm_completion_sent = 0; /* vms-cn3: re-arm the rebuild-completion one-shot */ /* vms-584: the open carries the post-transition cluster * facts. Latch them now, apply them when the transition * is real (see ovmx_cluster_relearn). */ diff --git a/tests/vmsscs/test_scs_member.c b/tests/vmsscs/test_scs_member.c index 265bfee49..a391c852f 100644 --- a/tests/vmsscs/test_scs_member.c +++ b/tests/vmsscs/test_scs_member.c @@ -953,6 +953,49 @@ static void test_dlm_selfreg_null_guards(void) CHECK(scs_member_build_dlm_selfreg(&mp, NULL) == -1, "build_dlm_selfreg NULL out"); } +/* + * vms-cn3: the rebuild-COMPLETION pair (op-04 + op-03 COMMIT) OVMX drives to the + * coordinator must be HONEST BY CONSTRUCTION -- no named resource, no held-lock + * handle. OVMX holds no persistent cluster lock, so the completion asserts + * "done, holding nothing". This pins that guarantee: resname@48 zeroed, per-lock + * handles@20:28 zeroed, mode@30 zeroed (NL). If any of those ever carries a value + * it would be claiming a lock OVMX cannot honestly back -- a fabrication, caught + * here rather than on the wire. + */ +static void test_dlm_completion_holds_nothing(void) +{ + struct scs_member_params mp; + joiner_params(&mp, 0, 0, 0x0064, 0x0113); + mp.txn = 0x0003; + mp.checksum = 0x0100; + + uint8_t op04[SCS_MEMBER_FRAME_LEN], op03[SCS_MEMBER_FRAME_LEN]; + CHECK(scs_member_build_dlm_op04(&mp, op04) == 0, "build_dlm_op04 ok"); + CHECK(scs_member_build_dlm_commit(&mp, op03) == 0, "build_dlm_commit ok"); + + const uint8_t *b4 = op04 + 72; + const uint8_t *b3 = op03 + 72; + + CHECK(b4[8] == 0x02 && b4[9] == 0x04, "op-04: cat 0x02 op 0x04"); + CHECK(b3[8] == 0x02 && b3[9] == 0x03, "op-03: cat 0x02 op 0x03 (COMMIT)"); + + /* THE HONESTY GUARDRAIL -- no resource, no held handle, no held mode. */ + for (int i = 48; i < 64; i++) { + CHECK(b4[i] == 0 && b3[i] == 0, "completion: resname@48 is ZEROED (no named resource)"); + } + for (int i = 20; i < 28; i++) { + CHECK(b4[i] == 0 && b3[i] == 0, "completion: lock handles@20:28 are ZEROED (no held lock, INV-6)"); + } + CHECK(b4[30] == 0 && b3[30] == 0, "completion: mode@30 is 0x00 (NL -- holds nothing)"); + + /* The SYSAP envelope + minted per-VC fields are laid down (real send). */ + CHECK((uint16_t)(b4[4] | (b4[5] << 8)) == 0x0003, "op-04: dir-tree tag minted"); + CHECK((uint16_t)(b4[6] | (b4[7] << 8)) == 0x0100, "op-04: per-VC counter minted"); + + CHECK(scs_member_build_dlm_op04(NULL, op04) == -1, "build_dlm_op04 NULL p"); + CHECK(scs_member_build_dlm_commit(&mp, NULL) == -1, "build_dlm_commit NULL out"); +} + int main(void) { test_op14_byte_exact(); @@ -974,6 +1017,7 @@ int main(void) test_params_member_vs_joiner_form(); test_dlm_selfreg_byte_exact(); test_dlm_selfreg_null_guards(); + test_dlm_completion_holds_nothing(); if (failures == 0) { printf("test_scs_member: ALL PASSED\n"); From ee4d1f3a5025d8741552cc66798c34aa0b873523 Mon Sep 17 00:00:00 2001 From: alice Date: Wed, 2 Sep 2026 01:00:39 +0000 Subject: [PATCH 02/28] vmsfs/lock: MOUNT holds the standing F11B$v volume lock for the mount life (vms-25e) [Layer 1, test pending] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layer 1 of faithful cluster DLM registration (vms-3eb). A real VMS MOUNT holds the per-volume lock from $MOUNT to $DISMOUNT; that standing lock is the cluster-wide "this node has this volume mounted" marker the connection manager re-registers to the coordinator during a directory rebuild. OVMX's executive previously held only the TRANSIENT XQP sync lock (F11B$s, taken per on-disk write and dropped) — nothing standing to register. This adds the standing one: - Generalize acp_vol_resnam -> acp_vol_resnam_kind(vol, kind, ...) so the per-volume resource name is built for any sub-lock ('s' sync, 'v' volume, 'a' allocation); acp_vol_resnam stays a 's' wrapper (call sites unchanged). - vms_lock_acp_vol_standing() — an NL-mode $ENQ on F11B$v