SET PROCESS/NAME writes the executive process table, untruncated, and refuses like VMS (vms-fbe) - #46
Conversation
DCL's SET PROCESS/NAME qualifier only ever wrote ctx->process_name, a struct private to the DCL process that ran the command -- nm on the built DCL.EXE showed it imported vms_kif_getjpi_self, vms_kif_open and vms_kif_procscan, and no vms_kif_setprn symbol at all, even though VMS_IOCTL_SETPRN already exists in the kernel and $CREPRC already uses it. SET PROCESS/NAME=X then F$PROCESS() read "X" back out of the same private struct it had just written: a single-process test that passed perfectly while sharing nothing with any other process (Rule 11). cmd_set_process() (src/vmsdcl/dcl_cmd_set.c) now calls vms_kif_setprn() -- the same VMS_IOCTL_SETPRN path $CREPRC already uses -- and lets the executive decide legality (SS$_IVLOGNAM) and UIC-group uniqueness (SS$_DUPLNAM), both already oracle-pinned (vms-8019) and already enforced kernel-side. ctx->process_name is still updated on success only, so dcl_lexical.c's F$PROCESS() (out of scope this round; a sibling item owns that file) does not go stale, and a refused rename does not locally claim a name the executive refused to record. tests/qemu/test_syssvc_setname.c proves the property with the required A-writes/B-reads shape: one DCL.EXE runs SET PROCESS/NAME and stays alive; the executive's own row (vms_kif_procscan, the same reader SHOW SYSTEM uses) carries the name; a SECOND, independent DCL.EXE's SHOW SYSTEM names it at the right pid; a THIRD DCL.EXE's SET PROCESS/NAME for the same name is refused with the oracle-pinned %SYSTEM-F-DUPLNAM through DCL's own error path (proven separately from the kernel-level refusal already covered elsewhere); a FOURTH DCL.EXE succeeds once the holder exits and the name frees. Reverting the fix (git stash) turns the suite red; the full run_facility_negctl.sh proctab-duplicate-name mutation reddens exactly this suite's DUPLNAM assertion plus the three pre-existing ones, and nothing else -- verified as an exact red-set equality against a real /dev/vms, not just against a locally reverted fix. tests/qemu/facility_defects.sh: wires the new suite into the proctab-duplicate-name negative control (suites_red + knock_on_fail + knock_on_why), which the manifest's own coverage self-test now requires for every derived suite. Full host ctest: 53/53. Full QEMU harness: 27/27 suites, 705/705 assertions. Facility negative controls: 3/3 (coverage, positive control, proctab-duplicate-name red-set equality). Duplicate-name refusal status/message were already oracle-pinned by vms-8019 (SS$_DUPLNAM 148, SS$_IVLOGNAM 340); this item does not introduce or self-certify any new VMS constant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed name (vms-fbe round 2)
Round 1 fixed the primary defect (DCL now calls vms_kif_setprn() through
the same executive path $CREPRC uses) but introduced a second one: upname
was sized sizeof(ctx->process_name) (16) instead of VMS_PRCNAM_XFER (64),
so an oversized name was truncated to 15 characters BEFORE it ever reached
the executive. The executive then received an already-legal-looking name
and could never refuse it -- a 16-char SET PROCESS/NAME=X "succeeded",
truncated, with no message, where real VMS refuses it outright
(src/kernel/vms_ioctl.h:653-658, VAX1 OpenVMS VAX V7.3 oracle transcript).
Three changes, matching the item:
1. upname is now sized VMS_PRCNAM_XFER, so an oversized name arrives at
vms_kif_setprn() intact and name_is_valid() (vms_proctab.c) can see it
is not NUL-terminated within VMS_PRCNAM_SIZE and refuse it.
2. SET PROCESS/NAME's DUPLNAM/IVLOGNAM refusal now renders in the
oracle's two-line shape -- "%SET-E-NOTSET, error modifying process
name" followed by a "-SYSTEM-F-<ident>, <text>" continuation line --
instead of the bare single-line "%SYSTEM-F-DUPLNAM" round 1 guessed
and was never observed on VAX1.
3. test_syssvc_setname.c: P5 no longer asserts only that the DUPLNAM
message is absent (satisfiable by SET PROCESS/NAME doing nothing at
all) -- it now spawns a second holder and reads the executive's own
row back through vms_kif_procscan() to prove the name was ACTUALLY
SET. New P6 covers the 16-char blocker directly: a 15-char boundary
name is accepted, a 16-char name is refused in the oracle's two-line
shape, the truncated form never appears anywhere in the output, and
SHOW SYSTEM still names the process with its pre-existing name,
unchanged, after the refusal.
METHOD 5: probed two other inputs the truncation could have been hiding.
Neither is oracle-pinned in this repo, so both are reported as OBSERVED
facts (printf), not asserted as right or wrong:
- SET PROCESS/NAME="" is a SILENT NO-OP -- a pre-existing
`if (name_val && *name_val)` guard skips the whole block before
vms_kif_setprn() is ever called. Different code path from the
upname buffer this item fixes; flagged, not fixed, here.
- SET PROCESS/NAME="OVMXFBE!BAD" is ACCEPTED -- name_is_valid() only
checks for a NUL within VMS_PRCNAM_SIZE bytes, never validates
character set.
Verified in QEMU (podman -f tests/qemu/Dockerfile): clean run 27 suites,
713 PASS, 0 FAIL (test_syssvc_setname: 18/18). Mutation-control run
(FACILITY_DEFECT=proctab-duplicate-name) reproduces the exact red set
{test_kmod_procnam, test_syssvc_procnam, test_syssvc_setname,
test_syssvc_startup_service} with test_syssvc_setname reddening exactly
one assertion (P4's DUPLNAM check) -- both unchanged from round 1,
confirming the existing facility_defects.sh wiring needed no changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…us (vms-fbe round 3)
Round 2's P6 built its capture from FOUR DCL commands -- SET(15-char),
SHOW SYSTEM, SET(16-char, refused), SHOW SYSTEM -- so `out` holds TWO
SHOW SYSTEM outputs. sys_row_for() scans from byte 0, so the assertion
matched the row printed BEFORE the refused rename, while its own text
claims the name survived the rename AFTER it. An implementation whose
refusal WIPED the name passed it.
Round 2's commit message asserted that P6 proves "SHOW SYSTEM still
names the process with its pre-existing name, unchanged, after the
refusal". That claim was not supported by the assertion it described.
It is withdrawn, not restated.
FIX (test only, no behaviour change): cut the capture at the
MSG_IVLOGNAM refusal message and search only what follows, so the
POST-refusal SHOW SYSTEM is the only thing that can satisfy the check.
If the refusal message is absent the assertion goes red rather than
falling back to the whole capture.
PROVEN BY EXECUTION, not by a green run. sys_row_for() was copied
verbatim into a standalone harness with round 2's expression and round
3's side by side, and fed two inputs: a faithful capture, and the same
capture with the POST-refusal row's name field wiped (row shape from
src/vmsdcl/dcl_cmd_show.c:545, "%08X %-15s %s"). Result:
faithful capture round2 = GREEN round3 = GREEN
MUTATED (refusal wiped it) round2 = GREEN round3 = RED
Also, in lockstep so the negative-control equality check still holds:
P4's assertion label said "the oracle-pinned %SYSTEM-F-DUPLNAM message"
-- round 1's single-line shape, which the code has not checked since
round 2 -- and is now "the oracle-pinned two-line %SET-E-NOTSET /
-SYSTEM-F-DUPLNAM shape", matching MSG_DUPLNAM. facility_defects.sh's
knock_on_fail line and one comment carry the same edit;
`facility_defects.sh selftest` re-confirms every require_fail/
knock_on_fail text exists literally in a suite source.
NOT fixed, and named rather than silently left: the two-line message
text is still hardcoded in the test instead of being derived from
run_print_condition()/sys$getmsg. That is a different change (the test
would have to link or invoke the message formatter) and is out of this
round's scope.
Verified in QEMU (docker -f tests/qemu/Dockerfile -t ovmx-fbe-r3).
Clean run: 27 SUITE banners all rc=0, FINAL RESULTS 29 suites passed /
0 failed, 713 PASS / 0 FAIL, test_syssvc_setname 18/18 -- identical to
round 2. Mutation control (FACILITY_DEFECT=proctab-duplicate-name via
/inject_and_run.sh): red set is EXACTLY {test_kmod_procnam,
test_syssvc_procnam, test_syssvc_setname, test_syssvc_startup_service},
5 unique FAIL lines equal to require_fail(2) + knock_on_fail(3)
verbatim, test_syssvc_setname 17/1 -- exactly one assertion, P4's.
The container exits 1 (run_tests.sh:66, `set -o pipefail` with
`echo | grep -q` -> EPIPE) on a run with zero failures. Pre-existing,
reproduced on unmodified main, filed separately; the run is judged by
the FINAL RESULTS line and the SUITE banners.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Test-only. src/ is untouched: the product was already correct on both
counts, and both CI failures were defects in the new suite itself.
1. NEGATIVE CONTROL (executive absent): the suite exited 1, not the
contract's 77. CI diagnosed "a fabricated success"; that diagnosis is
wrong and is filed as vms-c9c -- rc=1 only means "77 was not reached",
and both fabricated-success assertions PASSED in that run.
OBSERVED BY EXECUTION, not read off the source: with no /dev/vms the
shipped DCL.EXE does not crash. It prints, in 139 bytes, and exits 0:
%OVMX-E-SETPRNFAIL, SET PROCESS/NAME could not reach the executive (status %X000002A4)
SYSTEM logged out at ...
The failing assertion was a crash PROXY -- "%DCL-" or "%SYSTEM-" or
"$" -- whose three substrings do not include the message the product
actually emits, and a piped DCL prints no "$" prompt. It tested the
harness's guess about DCL's output, not DCL's behaviour.
LEGAL ANSWER TAKEN: the command already fails honestly, and the
OVMX-branded (not %SYSTEM-) shape is correct precisely because an
absent executive is a condition OpenVMS never faces and has no message
for -- inventing one would be the Rule 10 self-certification this epic
exists to kill. dcl_cmd_set.c:552-600 already routes it that way. No
degraded mode is resurrected: the product cannot reach this state at
all (vms-0ff), which is why only the executive-less rig observes it.
The assertion now pins that honest refusal POSITIVELY.
NON-VACUITY, MEASURED: the predicate was evaluated standalone against
the real capture and five defect shapes. GREEN on the real capture
only; RED on a silent success, a crash, a truncated message and an
invented %SET-E-NOTSET. The disjunction it replaces was GREEN on a
silent userspace fallback printing a bare "$" -- the exact Rule 9
defect the gate exists to catch. It also went RED unaided in an
intermediate run where DCL.EXE was staged without a shared library.
2. ATTRIBUTION (bind-client-no-register), a control rounds 1-3 also never
ran: the suite died at rc=141 = SIGPIPE. It writes a DCL script into a
pipe whose only reader is a DCL.EXE that, with the bind deleted, exits
before the write. A suite whose verdict is a signal number attributes
nothing. Fixed with the remedy test_syssvc_showterm.c:392-405 already
applied for the identical failure under the identical control:
signal(SIGPIPE, SIG_IGN), so the writes fall into their checked
branches. Nothing is weakened -- a signal death becomes named failures.
Then the same thing happened that showterm records: THE DECLARED SET
GREW BECAUSE THE OBSERVATION GOT BETTER. The suite now runs to
completion under the mutation (8 passed, 7 failed, rc=1), so CI's three
truncated reds are seven. All seven are one missing bind reaching the
command layer -- DCL reaches the process table only through kif_bind()
-- and are declared in knock_on_fail with a knock_on_why, not narrowed
away: the value of this suite is that it drives the real DCL.EXE end to
end, so it SHOULD be sensitive to the stack beneath the command. Eight
assertions stay GREEN under it, including the round-1 truncation check.
VERIFIED, BOTH BUILDS RUN THIS TIME (docker, workshop):
negative control every test_syssvc_* suite rc=77 including setname
(5 passed, 0 failed); fabricated-success assertions
still PASS.
clean run 27 SUITE banners all rc=0; FINAL RESULTS 29 passed,
0 failed; 713 PASS / 0 FAIL; setname 18/18.
bind-client-no-register setname rc=1 (was 141); replaying the gate's
own checks 4/5/6/7: no stray suites, red set EXACTLY
118 named = 118 observed, all five blind suites green.
proctab-duplicate-name PRESERVED EXACTLY: red set is still
{test_kmod_procnam, test_syssvc_procnam,
test_syssvc_setname, test_syssvc_startup_service};
5 unique FAIL lines == require_fail(2) +
knock_on_fail(3) verbatim; setname 17/1, the 1 being P4.
facility_defects.sh selftest green, including "every
require_fail/knock_on_fail text exists literally in a
suite source".
NOT DONE, NAMED: run_facility_negctl.sh's own POSITIVE control cannot pass
on this host -- the pristine container exits 1 through the pre-existing
run_tests.sh pipefail/SIGPIPE bug (rd vms-b1f), unrelated to either defect
here -- so checks 4/5/6/7 were replayed against the real captured run
using that script's own extractors verbatim, rather than driven by it.
Still open from round 3: the two-line message text is hardcoded in
dcl_cmd_set.c rather than rendered via run_print_condition/sys$getmsg, and
the stale single-line prose at test_syssvc_setname.c:492.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 4 —
|
| run | result |
|---|---|
| Negative control | test_syssvc_setname rc=77, 5 passed / 0 failed; every test_syssvc_* at 77 |
| Clean | 27 SUITE banners rc=0 · FINAL RESULTS: 29 suites passed, 0 suites failed · 713 PASS / 0 FAIL · setname 18/18 |
bind-client-no-register |
setname rc=1 (was 141); no strays; red set 118 named = 118 observed; all 5 blind suites green |
proctab-duplicate-name |
red set preserved exactly — {test_kmod_procnam, test_syssvc_procnam, test_syssvc_setname, test_syssvc_startup_service}, setname 17/1 (P4 only) |
Note for reviewers
run_facility_negctl.sh cannot be driven on the workshop dev host at all: its own pristine positive control exits 1 through the pre-existing pipefail+SIGPIPE bug in run_tests.sh, so it refuses to inject. Round 4 replayed its checks 4/5/6/7 against the real captured run using the script's own extractors verbatim. That is now the second agent forced into the same manual substitute, so vms-b1f has been raised to p1. It is host-sensitive and does not fire on GitHub runners — judge this PR by CI, not by a local container exit code.
🤖 Generated with Claude Code
|
CI is green at Left as a draft deliberately: the merge decision is the operator's. |
Four statements in the 2026-08-01 revision were measurably wrong and are corrected in a new section 0: podman (the host has docker), vms-a30 being outside the closure (it blocks vms-150), the lab path (/data/training/vax), and main's SHA (moved to ce11330). Several origin branches are duplicate refs at the same SHA, so the doc now says to establish a round by SHA. Frontier state: vms-fbe is draft PR #46 at 35/35 green; vms-ecf + vms-f26 are draft PR #47, adversary-cleared but CI-red on a source-scan refusal that is the gate working correctly; vms-cb5 round 4 is pushed and in verification. The executive-resident count is now 7, not 10 -- sys$enq/sys$enqw/sys$deq were demoted to PARTIAL on measured evidence (vms-82a). Section 5 gains the lessons earned this round: open the draft PR early because CI is a different and cheaper oracle; a passing test can conceal the failure mode it was written to detect; distinguish a container's exit status from a suite's; a gate printing a confident wrong cause is worse than one printing none; test a disqualifier for over-firing, since under-claiming is also a wrong measurement; and go to the lab before declaring a VMS value unpinnable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The eight defects found in passing while settling the three branches were filed unwired; this records where triage put them. A new parent vms-95f groups the four harness-integrity items (vms-b1f, vms-c9c, vms-215, vms-008) and blocks vms-150. vms-38c also blocks vms-150. vms-82a, vms-e60 and vms-2f8 block vms-042; vms-2d37 blocks vms-898. The harness parent blocks vms-150 and deliberately NOT vms-b33: Phase 2 rests on per-suite evidence these defects do not corrupt, and it is the near-term unblock. Phase 3 veracity is exactly the claim that our measurements mean what they say, so it waits. Closure goes 17 -> 26 open, no cycles. That is a sequencing decision, not a scope decision, and section 3 records the undo: un-wiring the five items returns it to 17 and ships the epic sooner on softer evidence. That trade is the operator's. All three branches are now PRs #46, #47 and #48, marked ready for review. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR #46 (vms-fbe, c243992) and PR #47 (vms-ecf + vms-f26, 57fc164) are merged; main is eff4fe3 and all three items are closed. PR #48 (vms-cb5) remains open at 1598ef5 with main merged in and the knock_on_fail fix for the two scenario-G/OPCOM+ reds under bind-client-no-register. vms-b33 is unblocked and active: its last two blockers closed with #47, so the fifth Phase 2 verdict is the next thing to run. vms-150 does NOT unblock when #48 merges -- vms-a30, vms-95f and vms-38c also block it. Closure is 23 open, not 26: eight defects triaged in against three items closed on merge, from a starting 17. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: handoff for the vms-14f executive-residency dispatch Records the execution pointer, the three open branches and their named blockers, the method rulings that were paid for during the dispatch, and the harness arithmetic reconciled in vms-215. Wave 47 outcomes are appended in a follow-up commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs vms-14f: rewrite the handoff after the 2026-08-02 frontier round Four statements in the 2026-08-01 revision were measurably wrong and are corrected in a new section 0: podman (the host has docker), vms-a30 being outside the closure (it blocks vms-150), the lab path (/data/training/vax), and main's SHA (moved to ce11330). Several origin branches are duplicate refs at the same SHA, so the doc now says to establish a round by SHA. Frontier state: vms-fbe is draft PR #46 at 35/35 green; vms-ecf + vms-f26 are draft PR #47, adversary-cleared but CI-red on a source-scan refusal that is the gate working correctly; vms-cb5 round 4 is pushed and in verification. The executive-resident count is now 7, not 10 -- sys$enq/sys$enqw/sys$deq were demoted to PARTIAL on measured evidence (vms-82a). Section 5 gains the lessons earned this round: open the draft PR early because CI is a different and cheaper oracle; a passing test can conceal the failure mode it was written to detect; distinguish a container's exit status from a suite's; a gate printing a confident wrong cause is worse than one printing none; test a disqualifier for over-firing, since under-claiming is also a wrong measurement; and go to the lab before declaring a VMS value unpinnable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs vms-14f: record the PM triage of the eight items found this round The eight defects found in passing while settling the three branches were filed unwired; this records where triage put them. A new parent vms-95f groups the four harness-integrity items (vms-b1f, vms-c9c, vms-215, vms-008) and blocks vms-150. vms-38c also blocks vms-150. vms-82a, vms-e60 and vms-2f8 block vms-042; vms-2d37 blocks vms-898. The harness parent blocks vms-150 and deliberately NOT vms-b33: Phase 2 rests on per-suite evidence these defects do not corrupt, and it is the near-term unblock. Phase 3 veracity is exactly the claim that our measurements mean what they say, so it waits. Closure goes 17 -> 26 open, no cycles. That is a sequencing decision, not a scope decision, and section 3 records the undo: un-wiring the five items returns it to 17 and ships the epic sooner on softer evidence. That trade is the operator's. All three branches are now PRs #46, #47 and #48, marked ready for review. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs vms-14f: two of the three frontier branches are merged PR #46 (vms-fbe, c243992) and PR #47 (vms-ecf + vms-f26, 57fc164) are merged; main is eff4fe3 and all three items are closed. PR #48 (vms-cb5) remains open at 1598ef5 with main merged in and the knock_on_fail fix for the two scenario-G/OPCOM+ reds under bind-client-no-register. vms-b33 is unblocked and active: its last two blockers closed with #47, so the fifth Phase 2 verdict is the next thing to run. vms-150 does NOT unblock when #48 merges -- vms-a30, vms-95f and vms-38c also block it. Closure is 23 open, not 26: eight defects triaged in against three items closed on merge, from a starting 17. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs vms-14f: rewrite the handoff for the seventh Phase 2 verdict The 2026-08-02 revision named PR #48 as its only loose end; #48 merged twelve minutes after that revision was committed, so the document was stale within the hour. Main has since moved eff4fe3 -> 8e8be98 through eight merges closing vms-b33 blockers, plus four SCS merges from another dispatch. vms-b33 is blocked again, by exactly five items -- vms-c79, vms-004e, vms-659, vms-d894, vms-35f -- every one a two-edit escape from a static gate, all filed by the seventh verdict's adversary. Closure re-derived by DAG walk from vms-14f: 35 open, counting the epic, up from 23. Numbers re-derived rather than carried: the eight merge SHAs against gh api (all green on Build & Test and the per-facility negative control job); the ctest counts at 44870ac / 8552c29 / d9b69cc / 8e8be98 (56 / 56 / 58 / 59) by configure-only enumeration, which is what makes "a green on a stale base is not evidence" a measurement rather than an assertion; the CI timeout raise 20 -> 35; the ledger's 22 rows checked against rd's live open set by hand. What was NOT re-measured is labelled as such -- the ctest 2-failure pair and the rd_citations_fresh flake are carried from run 7, and the round ran under a no-builds constraint at 95% disk. Keeps visible: 13 of 44 entry points remain unwired, so Phase 2 closing means what is wired is proven, not that the executive is complete; and the vms-1e1 (B) ruling with its one-command undo, whose binding side condition is now itself under attack by vms-004e and vms-35f. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: alice <alice@workspace.local>
…x (10 items) (#158) * vms-c9c: negative-control diagnostic prints the condition, not an inferred cause rc!=77 (and rc!=0) for test_syssvc_* only means "77 was not reached" -- it has two distinct causes (a fabricated success, or an unrelated assertion failure), and the old message asserted the first as fact. Proven false on PR #46 (run 30725753152): both fabricated-success assertions passed, the real defect was DCL.EXE crashing. Now the message states the ambiguity and pastes the suite's own FAIL line(s) so the reader attributes from evidence already in the same output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vms-86a: shard the per-facility negctl job to fit CI under concurrent load Root cause (measured, rd vms-86a trail): the job ran all 58 manifest defects sequentially in ONE job -- 27m solo, 50m under moderate CI load, >60m and timeout-killed with three PRs in flight. Raising timeout-minutes was rejected (flaky-test rule): it hides the margin problem, it doesn't fix it. Splits the per-facility loop into 6 independent matrix shard jobs, each running ~1/6 of `facility_defects.sh list` (partitioned by NR%6, so it tracks the manifest as it grows/shrinks -- never a hand-maintained sublist). Each shard still runs the SAME positive control and the SAME per-defect equality check (red set EXACTLY require_fail+knock_on_fail, attribution, blind-suite gaps) the single job did, just over a subset. A new aggregate job (keeping the ORIGINAL job name for branch-protection/ doc continuity) unions every shard's emitted execution record and runs the full-manifest comparison against the committed tests/qemu/facility_negctl_observed.tsv in both directions -- the exact check the single-job driver ran on a full run, just over the union instead of one sequential execution. Verified: the 6-way NR%6 partition covers the manifest exactly (58/58, no gaps, no dupes); reconstructing the union from the real committed record and running fnr_compare against it passes; dropping one shard's rows from the union is correctly caught as a mismatch. Both existing static selftests (facility_defects.sh selftest, facility_record_negctl.sh) still pass unmodified. actionlint clean except pre-existing style-level shellcheck notes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vms-b3b: key the facility-negctl red-set equality on (suite, text), not text alone run_facility_negctl.sh's per-defect equality (check 6) compared bare assertion text against require_fail/knock_on_fail, discarding the suite attribution fail_map() already carries (`cut -f2-`). MEASURED: the text "child: a LOCAL flag set by the parent is NOT visible here (local clusters stay per-process)" -- named by bind-client-no-register, expected from test_syssvc_ef_mproc.c (in that defect's suites_red) -- is also printed verbatim by test_kmod_eflag_mproc.c, which is NOT in suites_red. Under the old equality, a red from either suite satisfied the requirement, so a red from the wrong suite could mask the right suite's own red going missing. Fix: tests/qemu/facility_negctl_equality.sh's fne_scope_map() scopes the observed (suite, text) rows to the defect's suites_red glob (or "(harness)") before the text comparison runs, so a same-text red from an out-of-scope suite can no longer stand in for the suite the manifest actually named. Swept the whole manifest at the same normalisation facility_defects.sh's own selftest uses: every require_fail/knock_on_fail text in every defect is still found within its own suites_red-scoped sources except this one already measured case -- the fix does not narrow any other defect's requirement. tests/qemu/facility_negctl_equality_negctl.sh is the negative control (no QEMU needed): it pins the real collision as still-grounded, proves a red from the right suite still satisfies the requirement, and proves a same-text red from the wrong suite (test_kmod_eflag_mproc) no longer does -- reproducing the driver's own comparison shape end to end. Registered as ctest facility_negctl_equality (label "harness", no container/QEMU). * vms-41b: root rule's header clause requires a NON-static declaration The census credited any function prototyped in a header the build compiles as a root (rule 2, "exported API surface"), because the P-record reading never carried the static/extern qualifier. MEASURED exploit: a dead helper declared AND defined `static` in a multi-includer header (dcl_cmd.h, included by 9 TUs) bought a root exactly like the earlier two-edit recipes this gate already closed -- a `static` declaration can never be an exported entry point, since each includer gets its own private symbol. Fix: call_edges() now tags each P record static|extern, and root rule 2 only seeds from non-static declarations in non-TU files. Verified by hand against the prior recipe (now rc=1, naming vms_kif_chkpriv) and against the pristine tree (unchanged, rc=0). Added negative control 48 to pin it; all 42 controls in test_kif_caller_census_negctl.sh pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vms-d33: close the header-residency root-rule loophole in the kif census Rule 2 of the census's call graph grants a root to "every product function prototyped in a header the build compiles" -- correct for a genuinely exported symbol, but the (origin file, name) tagging that tells a `.c` translation unit's private static from an extern definition only fired when the origin was itself one of the compiled TUs. A header never is, so a `static` function whose declaration AND body both lived directly in a compiled header (e.g. src/vmsdcl/include/dcl/dcl_cmd.h) fell through untagged and landed on the same bare-name node an actually-exported symbol gets -- granting root status, and therefore a product path, to a function with internal linkage that could never be called from outside its own translation unit. MEASURED before the fix: two edits (a static declaration+body in dcl_cmd.h, plus retiring vms_kif_chkpriv's OVMX-UNWIRED token) bought rc=0 at 44/32/12, one extra root (731 -> 732). Fixed by tracking header-resident static definitions independent of the per-TU tagging and excluding them from rule 2's grant. Pristine tree unaffected (731 roots, 1547 reached, 31/44 unchanged) because the loophole requires a function that additionally carries a standalone forward declaration -- a shape no existing static-inline header helper in the tree has. The same two-edit recipe is now rc=1, naming vms_kif_chkpriv. Captured as negative control 48 in test_kif_caller_census_negctl.sh (42 passed, 0 failed, no regressions across all pre-existing controls). This closes one purely-static loophole in vms-d33's "product path, not execution" question -- it does not close vms-d33 itself. A genuinely extern function declared in a header and defined in one .c file is still a root whether or not it is ever called at runtime, and is still indistinguishable here from a real caller nobody exercises -- that gap is execution, not linkage, and needs the per-assertion runtime-attribution instrument's groundwork (docs/design-runtime-attribution.md, residual R7) before it can close. Documented as a disclosed residual, not claimed closed, in both the gate's header comment and the design doc addendum. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vms-cb5: $SETUAI's SYSPRV test comes from the executive, not the caller's own PCB Round 5 of the Phase 3 security review, against origin/main c871334. sys$setuai -- the one service that rewrites SYSUAF.DAT, UAI$_PWD included -- guarded itself with: struct vms_pcb *pcb = vms_pcb_get(); if (pcb && !(pcb->cur_privs & PRV$M_SYSPRV)) return SS$_NOPRIV; Two ways through. vms_pcb_get() returns NULL for a process that never called vms_pcb_init(), so `pcb &&` made the condition false and NO privilege test ran at all. And where a PCB did exist the mask was pcb->cur_privs, which sys$setprv writes for the calling process with no validation -- the caller's own claim about itself. The test now reads the row the executive holds for the process (vms_kif_getjpi_self), the same source tools/vms_authorize.c uses since vms-b2e, and refuses when that read does not come back (Rule 9: no absent-executive branch). Also fixes the rewrite's UIC write-back base. parse_uaf_line() reads the two UIC fields with strtoul(..., 8) after vms-e60; this fprintf still printed them with %u, so rewriting any record whose UIC digits differ between the bases changed that account's UIC. USER1 ships 200|202 and would have been written 128|130. tests/qemu/test_syssvc_setuai.c drives all of it against a real /dev/vms: a caller with no PCB, a caller with an authenticated non-SYSPRV identity, a caller whose own PCB claims SYSPRV over an executive row that does not, and the SYSPRV positive that keeps the three refusals from being blanket. The SYSUAF.DAT evidence is read by the parent -- a process that neither authenticated nor wrote. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vms-38c: re-measure runtime attribution post vms-2b2, fix stale anchors vms-2b2 closed: all 16 wired vms_ioctl_* handlers are now MEASURED (25/33 total, 8 UNPROBED = the OVMX-UNWIRED exempt set). Re-measuring the register against that finds 8 of 10 OVMX-EXECUTIVE claims MEASURED, exactly 2 still UNMEASURED: sys$readef and sys$setef. Root cause is NOT unprobed handlers -- vms_ioctl_readef/setef are each measured-dependent elsewhere (test_syssvc_ef_ local, test_kmod_eflag, test_kmod_bind) -- it's a suite-scope mismatch: the defects that mutate their WASSET/WASCLR status word never redden an assertion in test_syssvc_ef_mproc.c, the suite cited as these two claims' own proof. Fixes landed: - test_userspace_service_register.sh: the UNMEASURED branch now distinguishes "handler measured elsewhere, suite mismatch" from "handler unprobed anywhere" instead of always citing the now-closed vms-2b2 as the reason. The stale "2 of 10 measured" / "9 of 33 handlers" comment block is replaced with a re-derivable description instead of a count that will drift again. - facility_attribution.sh selftest checks 3 & 5 hardcoded vms_ioctl_wflor as a "known unprobed" anchor. vms-2b2's own follow-up (vms-2ed) later gave it real coverage in that exact suite, which silently broke the selftest (a stale hardcoded fact, the same mistake class this file argues against). Now derives the anchor from `handlers` output each run. - facility_attribution_negctl.sh control B hit the same staleness (the recorded 2-edit sys$wflor buy no longer represents an unpaid claim, since wflor is now honestly measured in that suite). Control B now detects that organic graduation and falls through to a fresh, currently-live equivalent: one ignored call added to sys$readef's own already-declared EXECUTIVE proof does not flip its standing UNMEASURED to MEASURED -- the adversarial round this item required, run against current data instead of a resolved case. Not enforced: a pristine tree would still red 2 of 10 claims, so the register stays report-only per the item's done-condition. Verified: register (rc=0), register negctl (49/49), facility_attribution selftest (6/6), facility_ attribution_negctl (8/8), facility_defects selftest, facility_record_negctl (25/25). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vms-05e7: close the composed rename+shared-.inc census exfiltration The census's third definition reading (vms-e2b) namespaced its unrestricted region read to `vms_kif_` names, to tell an exfiltrated interface wrapper apart from vms_syscall.h's 47 static inline syscall stubs. That name filter left an escape: exfiltrate a wrapper's body into a .inc shared with a second product TU (defeating the private-origin rule) AND rename it out of the vms_kif_ namespace (defeating the name filter). MEASURED before this change: 7 edits, universe 44->43, rc=0, PASS -- a silent shrink. Fix: call_edges() now tracks the `inline` keyword alongside `static` and tags a defs-mode static definition "static-inline" when both are present. A new fourth definition-reading term reads the interface TU's full region with no name filter, excluding only "static-inline" definitions -- the tell that separates vms_syscall.h's generic stubs (all `static inline`, verified) from a real wrapper's body (plain `static`, verified against vms_kif.c's own kif_bind/kif_call/etc.). The recipe now reds naming the renamed entry point instead of silently leaving the universe. Added negative control 48 reproducing the full 7-edit recipe; all 42 existing controls stay green; pristine tree rc=0, universe unchanged at 44. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vms-cb5: test_syssvc_setuai bootstraps the device table before resolving SYSUAF SYSUAF_PATH is a VMS filespec; vmsfs_to_linux_path() cannot resolve it until the system device is in this process's device table, which is what every shipped image does at startup. Without it the suite failed on a missing file instead of on $SETUAI's privilege test, so its refusals would have been explained by the wrong thing. The resolved path is printed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vms-cb5: negative control setuai-sysprv-caller-declared, and the suite's logical-name bootstrap VMS_SYSUAF_PATH is "SYS$SYSTEM:SYSUAF.DAT", so resolving it needs the logical name table as well as the device table. MEASURED before this: the path resolved to /vms/sysuaf.dat and the suite failed on a missing file instead of on $SETUAI's privilege test. The control deletes the mask test and nothing else -- the state $SETUAI was in for every caller with no PCB. It names the three refusals in require_fail and the file-unchanged check in knock_on_fail, with the reason. facility_defects.sh selftest PASS; coverage PASS (59 defects >= floor 58, all anchored, 27 suites named). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vms-e90: teach divider-integrity detector to recognize table separator rows src/vmsscs/include/scs_mscp_srv.h:601 is a legitimate markdown-style comment table ("header offset | size | field" / "---|------|---...") added by vms-4e31 (ddce7ec) to document the SCA block-transfer header layout, not a corrupted divider. The FUSED_RE detector was matching the dash run and flagging the rest of the row as fused-onto-line text. Fix by shape, not by allowlist: a tail made up of nothing but '|' and divider characters (a table separator row's remaining cells) is exempted, the same way a '*/' comment closer already is. Real fusion -- prose from the next comment line -- still trips the check immediately, per new test test_still_flags_fusion_immediately_after_a_table_style_run. Proof suite: 14/14 pass (was 12; added the table-row true-negative and a paired true-positive). Full-tree gate sweep: 777 files, 0 findings. * vms-cb5: $GETUAI/$SETUAI stop losing every empty SYSUAF field Found by the new suite, not by reading: test_syssvc_setuai read USER1's row back out of SYSUAF.DAT after a $SETUAI and got uic_group=202, uic_member=0 where 200 and 202 belong. parse_uaf_line() split the row with seven strtok_r(buf, "|") calls. strtok treats a RUN of delimiters as ONE, so every empty field was dropped and every field after it read one position early. Five of the six shipped rows have an empty field, so $GETUAI answered the wrong hash, the wrong UIC and the wrong privileges for those accounts, and $SETUAI wrote the misparse back. USER1||200|202|SYS$SYSDEVICE:[USERS.USER1]||TMPMBX,NETMBX -> password_hash="200", uic_group=202, uic_member=strtoul(defdir,8)=0 uic_member 0 is why this is more than a parsing bug: tools/vms_login.c does setuid(rec->uic_member), and setuid(0) is not a drop. What stops that on the shipped SYSUAF is that all four accounts this misparse gives member 0 carry no password hash and cannot authenticate (vms-08f) -- not anything here. The replacement split is the one src/libvms/rtl/sysuaf.c's sysuaf_scan() already uses, so the two readers of this file now agree by construction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vms-ed8: close register-universe residuals #1-3 disclosed by vms-c19 Closes three of the five gaps test_userspace_service_register.sh disclosed (not claimed closed) after vms-c19: 1. A target declared under tests/, holding its own sys$ definition in a source under tests/, that is nevertheless installed. The tests/ exclusion only asked "is the compiling target declared outside tests/ AND does it compile a non-tests/ source"; a target failing both halves but shipped by install(TARGETS ...) was never asked about. Fixed by scanning every CMakeLists.txt for install(TARGETS ...) and treating a named target as a product target regardless of which directory declared it. 2. A source CMake compiles only under an option this configure leaves OFF, living outside src/ and tools/ (inside those two the glob still catches it, e.g. src/imgact/ under OVMX_IMGACT=OFF). Fixed with a mechanical scan (register_optguard.awk) for add_subdirectory() calls gated by an OFF option that resolve outside src/+tools/; the gate now REFUSES rather than silently certifying a hole, naming the option and path. 3. compile_commands.json was parsed by line shape with no defense against a PARTIAL parse (a "file" field that never reaches a matching object close would have silently dropped that entry). The parser is pulled out into tests/integration/lib/register_buildset.awk, which now counts "file" fields seen vs. objects closed and refuses on a mismatch instead of certifying a shrunk set. Each fix is measured before/after against the real gate on a sandboxed tree: the pre-fix gate PASSes while missing the minted service; the post-fix gate reds naming exactly it. register_buildset.awk's partial-parse path is also unit-tested directly against a hand-built malformed compile_commands.json, since no product-source mutation can perturb cmake's own JSON shape. Pristine tree: rc=0, universe unchanged at 88 services. Negative controls added to test_userspace_service_register_negctl.sh for all three; gate header's "WHAT REMAINS OPEN" disclosure updated to drop the two closed bullets (deleted, not reworded, per the standing prose ruling). Residuals #4 (assembly aliasing) and #5 (shared broken-build-set message prefix) are out of scope for this item and remain open. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: alice <alice@workspace.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Closes
vms-fbe— SET PROCESS/NAME writes the process name into per-process memory.Three rounds. Rounds 1 and 2 were each refused by an adversary; round 3 was verified MERGEABLE by the same adversary that found the round-2 defect.
What lands
SET PROCESS/NAMEnow writes the executive's process table through the samevms_kif_setprnpath$CREPRCuses;ctx->process_nameupdates on success only, so a refused rename cannot locally claim a name the executive rejected.tests/qemu/test_syssvc_setname.cis a genuine A-writes / B-reads proof across four separateDCL.EXEprocesses (Rule 11): one sets its name and stays alive; the executive's own row carries it viavms_kif_procscan— the same readerSHOW SYSTEMuses; a second independentDCL.EXEnames it at the right pid; a third is refused for the duplicate; a fourth succeeds once the holder exits.Round 2 fixed the truncation defect:
upnamewas sizedsizeof(ctx->process_name)(16) instead ofVMS_PRCNAM_XFER(64), so an oversized name was truncated before it reached the executive and the executive never got the chance to refuse it — the exact defect that constant exists to prevent. The refusal now renders in the oracle's two-line shape (%SET-E-NOTSET/-SYSTEM-F-<ident>) per the VAX V7.3 transcript atsrc/kernel/vms_ioctl.h:653-658. No constant was self-certified:SS$_DUPLNAM(148) andSS$_IVLOGNAM(340) were oracle-pinned byvms-8019.Round 3 is test-only —
git diff 91b0770..7108c16 -- src/is empty. Round 2 had shipped a new vacuous assertion of the class it was opened to kill: P6's "name unchanged after refusal" scanned the whole capture, which holds twoSHOW SYSTEMoutputs, and matched the row printed before the rename. Round 3 cuts the capture at the refusal message and searches only the tail.Evidence
Rebuilt from a clean
git archive, judged by theFINAL RESULTSline and the SUITE banners:FINAL RESULTS: 29 suites passed, 0 suites failed· 713 PASS / 0 FAIL ·test_syssvc_setname 18/18FACILITY_DEFECT=proctab-duplicate-name: red set exactly{test_kmod_procnam, test_syssvc_procnam, test_syssvc_setname, test_syssvc_startup_service}— 5 unique FAIL lines matchingrequire_fail(2) + knock_on_fail(3)verbatim; setname 17/1 (P4 only)nmon/initramfs/bin/DCL.EXEconfirmsvms_kif_setprnis genuinely importedThe P6 fix was settled by execution, not by review —
sys_row_forcopied verbatim into a standalone harness, round 2 vs round 3 over six inputs:Round 2 was green on all four defect shapes — fully vacuous, as charged. The
strstrcut returns NULL rather than re-searching from byte 0, so there is no silent fallback.Known and named, not hidden
run_print_condition()/sys$getmsg— out of scope, left named.test_syssvc_setname.c:423prose still describes the superseded single-line shape.SET PROCESS/NAME=""is a silent no-op andSET PROCESS/NAME="OVMXFBE!BAD"is accepted — both observed, not oracle-pinned, reported as facts rather than asserted as right or wrong.Judge CI by its own result:
vms-b1frecords thattests/qemu/run_tests.shcan exit 1 on a fully green run viapipefail+SIGPIPE, but that is host-sensitive and does not fire on GitHub runners.🤖 Generated with Claude Code