Check a beamline descriptor's declared addresses against the live control system - #733
Open
xmap wants to merge 1 commit into
Open
Check a beamline descriptor's declared addresses against the live control system#733xmap wants to merge 1 commit into
xmap wants to merge 1 commit into
Conversation
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||
…trol system A descriptor names control addresses that were reverse-engineered from a controls config or a design document, and nothing has ever compared them to the running control system. The two sides are independently maintained, so a disagreement is real evidence rather than a restatement: this is the property project_independent_check_principle asks for, and re-reading the YAML cannot supply it. The drift it catches is the class that has already shipped three times on the capture-watch PV set: an address that moved, got renamed by an IOC rebuild, or reads as a wire shape its author did not expect. Named preflight, not probe, and address, not handle, to stay inside the house vocabulary. A probe in CORA is a periodic re-affirmation read that writes a coverage-trail row (CaptureProbe, the three *_probe_tick_seconds settings); this is a one-shot operator sweep that records nothing, and it is the same act capture_watch_preflight performs. An address is what ControlPort.read takes and what NoAdapterForAddressError reports, while handle already means an opaque id (TransferHandle) and is the descriptor's own word for the unrelated epics_handle provenance field, which NOT_ADDRESS_FIELDS excludes. Read-only, and more strictly than the deployment: build_control_port takes writes_enabled=False as a literal rather than inheriting Settings.control_writes_enabled, because this command has no write path at any setting. _EXIT_MISMATCH follows record_fidelity_check's 1-means-disagreement contract rather than reusing capture_watch_preflight's _EXIT_PROBLEM, which is the same symbol name for a different value. Ranging the field walk over the whole deployments/ corpus rather than over 2-BM alone found two defects in it before it ran once. Five address-bearing fields (readback_pv, slot_labels_pv, camera_rotation_pv, per_lens_focus_pv, temperature_pv) were never reached, and an address field whose value is a bare list was dropped silently. Both are pinned by a guard that fails on any address-shaped descriptor key classified as neither readable nor deliberately skipped, so the next one is a decision rather than a silent gap. epics_handle is the interesting exclusion: it carries crate provenance, so its values are ranges, globs and hostnames that would read as dead addresses. Three shapes are reported as skipped rather than dropped (a bare device prefix, a confirm: marker, a non-address token like 2-BM's GV1 valve labels), and the pass rate counts only what was read. A descriptor full of confirm: markers must not be able to report a clean sweep having contacted nothing. PyYAML moves from the dev group to a runtime dependency. It was dev-only while the descriptor was read exclusively by scripts/ and the docs build, and this command is run on a deployment host where that group may be absent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xmap
force-pushed
the
feat/descriptor-probe
branch
from
August 26, 2026 14:20
e421bb5 to
c953665
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
python -m cora.api.descriptor_preflight <descriptor>: an operator command that reads every control address a beamline descriptor declares, once, and reports whether it connects and what shapeControlPortsees it as.Why
A descriptor's addresses were reverse-engineered from a controls config or a design document, and nothing has ever compared them to the running control system. The two sides are independently maintained, so a disagreement is real evidence rather than a restatement (the property
project_independent_check_principleasks for). The drift it catches is the class that has already shipped three times on the capture-watch PV set: an address that moved, got renamed by an IOC rebuild, or reads as a wire shape its author did not expect.Naming
preflight, notprobe: a probe in CORA is a periodic re-affirmation read that writes a coverage-trail row (CaptureProbe, the three*_probe_tick_secondssettings). This is a one-shot operator sweep that records nothing, which is the actcapture_watch_preflightalready names.address, nothandle:addressis whatControlPort.readtakes and whatNoAdapterForAddressErrorreports.handlealready means an opaque id (TransferHandle) and is the descriptor's own word for the unrelatedepics_handleprovenance field._EXIT_MISMATCH = 1followsrecord_fidelity_check's "1 means what you checked disagrees" contract rather than reusingcapture_watch_preflight's_EXIT_PROBLEM, which is the same symbol name for a different value (2).Safety posture
Read-only, and more strictly than the deployment:
build_control_porttakeswrites_enabled=Falseas a literal rather than inheritingSettings.control_writes_enabled, because this command has no write path at any setting. An integration test asserts read-preflight-read leaves the value unchanged.The module docstring carries the wire-footprint note: CA search is broadcast and an unresolvable address is the noisy case, so pointing this at a beamline outside the deployment's own means pinning
EPICS_CA_AUTO_ADDR_LIST=NOwith a named gateway, and asking that beamline's staff first.The corpus guard found two defects before the command ran once
The field walk was written from 2-BM, then ranged over all 96
deployments/*/beamline.yaml. It failed immediately:readback_pv,slot_labels_pv,camera_rotation_pv,per_lens_focus_pv,temperature_pv. 2-BM went from 56 readable addresses to 65.Both are now pinned by
test_address_fields_cover_the_descriptor_corpus, which fails on any address-shaped descriptor key classified as neither readable (ADDRESS_FIELDS) nor deliberately skipped (NOT_ADDRESS_FIELDS), so the next one is a decision rather than a silent gap. The guard was mutation-tested (drop a field, confirm it fails with an actionable message) so it is not vacuous.epics_handleis the interesting exclusion: it carries crate provenance, so its values are ranges (2bmb:m100-m102), globs (2bmb:m*) and hostnames (JenaNV200D). Several would have classified as addresses and failed at read, reporting drift that is not there.Reporting
Three shapes are reported as skipped rather than dropped (a bare device prefix, a
confirm:marker, a non-address token like 2-BM'sGV1valve labels), and the pass rate counts only what was read. A descriptor full ofconfirm:markers must not be able to report a clean sweep having contacted nothing.What a green row does not mean
A connected channel proves a record answers to that name, not that it drives the device the descriptor names. A renumbered motor crate answers on every one of its old names and this command reports a clean sweep. Stated in the module docstring so no reader takes a green report for confirmation of device identity.
Tests
42, all green.
ControlPort(not-connected / timeout / access-denied / coercion / no-route), dedup, and report rendering.epicscorelibs.iocsubprocess on loopback. This tier matters more than usual here, because all-BAD is the command's normal output on a host with no route to the beamline and is also what a completely broken command prints. The soft IOC proves green rows are reachable through the realEpicsCaControlPort, that a dead address reports BAD, that an unrouted address is named as a routes gap, and that the command writes nothing.Dependency change
PyYAML moves from the
devgroup to a runtime dependency. It was dev-only while the descriptor was read exclusively byscripts/and the docs build; this command runs on a deployment host where that group may be absent.types-PyYAMLstays indev(stubs only). The lockfile diff is 4 lines;uv lock --checkpasses.