Repository navigation
Expose protocol operations and make the test suite portable - #42
jonathanstokes merged 14 commits into
Conversation
jonathanstokes
left a comment
There was a problem hiding this comment.
Thanks @tony-xmelon. The four new operations are exactly the kind of thing I want in here, and the Windows fixes are overdue. I'd like to get those in quickly.
The catalog refresh is where I have to slow down. My unit is on 4.0.1 and I'm keeping it there for a while, so I can't verify the 4.1 constants, and tests/hardware/test_generated_constants.py would fail on my unit the day this merges. The doc changes about PresetDirty and the connect burst were also observed on 4.1, so I'd rather record them as 4.1 findings next to the 4.0.1 ones than replace what I measured.
Where I want this library to go is support for both firmware versions at once. Connect, detect what we're talking to, and hand back the right client and device object for it. That's the same shape I asked for on PR #31 for the Mini. Once that exists, a 4.1 catalog has a home that doesn't break 4.0.1 users.
So, if you don't mind, could you split this in two?
PR A, mergeable once I verify it on 4.0.1:
inhibited_modules(),set_device_name(),undo(),redo()and their offline wire tests- the Windows fixes:
as_posix()in the gate, UTF-8 on the generator writes, the CRLF-tolerant fixture hash - the watchdog thread baselining in
test_state.py, with a comment saying it's for the combined run - the api.md, manual-coverage.md and changelog entries for the four operations
- the new hardware test for name and undo/redo
PR B, after we agree on the two-version design:
- the regenerated
models.py,options.py,params.pyand the 412 to 420 counts - the renamed constants and the eleven removed option enums, declared as breaking with an old-to-new map
- the
PresetDirty, connect-burstVersioncount and dropped-read observations, recorded as 4.1.0 findings with the capture behind them - the
RecallPresetretry and the_retry_readhelper, since the drops were seen on 4.1 - the loosened hardware assertions that came with those observations
- the STEERING and protocol.md baseline change, which I'll write as an ADR
I have other smaller feedback I'll save for the split PRs. If splitting is more than you want to take on, say so and I'll carve PR A out myself and credit you on it.
Stokes
acf3ffe to
0ead41d
Compare
|
Split as requested. This PR is now PR A: the four operations, offline/hardware coverage, and the Windows/combined-run fixes only. The CorOS 4.1 catalog, retry, and observation-driven assertions are isolated in draft PR #44, with the breaking-name migration map and the version-design/capture/ADR work called out as remaining. I also rebased PR A onto current main. |
|
Thank you, Stokes, for the swift and detailed feedback — it was very helpful. I have split the work as requested:
We have more potentially useful work locally, particularly around ModelRepo clone/parameter inheritance and metadata, native local backups, and additional remote-control surfaces. We will keep these as small, focused contributions and test them thoroughly — offline and on hardware where applicable — before submitting anything further. Two adjacent areas are deliberately not being claimed as finished pyquadcortex features yet: our tap-tempo control currently uses the documented MIDI CC #44 rather than a verified HID operation, and although the protobuf exposes Any other feedback on scope, API shape, testing expectations, or preferred priorities is very welcome. Thanks again. |
|
As promised, the additional work has now been split into small, independent PRs:
Each was branched directly from The screenshot route is now established: a bare Tap tempo remains unsubmitted because the only verified remote route is the documented MIDI CC #44, not a HID/protobuf operation in this client. I did not want to submit a speculative API. Any guidance on whether MIDI-backed helpers belong in this project—or any other priorities, API-shape feedback, or testing pointers—is very welcome. Thanks again. |
0ead41d to
83d54ae
Compare
jonathanstokes
left a comment
There was a problem hiding this comment.
Thank you for splitting this so quickly. This half is clean, and I want to get it in.
What I tested on my unit (CorOS 4.0.1, firmware d14e). All three hardware tests pass here: the device-name round trip, the inhibited-modules read, and the undo/redo test against a scratch preset. So none of these four operations is 4.1-only, and the docstrings can say they are confirmed on both versions. For the undo test I made a copy of my loaded preset in an empty slot, pointed the env var at that copy, and deleted it afterwards. That worked well and I think it is the shape the test itself should use (see the inline notes).
While testing I also found one thing about the unit that matters for this PR. A Version READ is answered twice: the full reply, and about 1 ms later an echo of the READ that carries only action. version() correlates by type, so two calls close together alternate between the full reply and the empty echo. I saw this directly: five calls in a row returned full, empty, full, empty, full. The name test reads version().custom_name and writes it back, so on a bad turn it would write an empty name. The 1 s sleeps happen to hide this. Details inline.
Two things I could not attach to a line, because the files are not in this diff:
docs/protocol.mdhas no coverage-table rows yet forset_device_name,undo/redoandinhibited_modules. The rows were in the original PR and got lost in the split. The table header also says every operation was exercised on 4.0.1, so the rows can now say "4.0.1 and 4.1.0" honestly.docs/domain-model.mdstill says (around line 1145) that there is no grid-level undo a host can trigger, and rows 1274 and 1300 still give "never driven" as the reason for omitting it. This PR proves otherwise, so those lines need a small update.
Everything else is inline. Items marked Nit: are optional. Thanks again, and please tell me if any of this is unclear.
Stokes
|
One correction to my previous note: by remote tap I meant clicking the physical Quad Cortex touchscreen from the app, not tap tempo. We have now recovered and hardware-verified that Cortex Control flow and submitted it separately as #50. #50 adds live 800 x 480 screen capture plus raw-coordinate touchscreen taps. It is distinct from #49, which renders a preset screenshot rather than capturing the live framebuffer. The touch path was tested twice on CorOS 4.1: it opened the intended block's Parameter Editor, returned cleanly to the Grid, and left the preset hash, slot, scene, and dirty state unchanged. Thank you again for the swift feedback and for prompting the split. We will continue submitting further additions only as small, independently reviewed and thoroughly tested PRs. Any further pointers on scope, API shape, priorities, or protocol expectations are very welcome. |
|
Thank you for the swift and detailed feedback, Stokes. I pushed f64984b addressing the review: Version replies are filtered to the full device response; device-name presence/type handling is tightened; the undo/redo hardware test now creates and cleans up its own scratch preset and restores the original state; the controlled evidence wording and coverage tables are corrected; watchdog baselines are inclusive; and the optional fixture/readability nits are included. The complete offline suite is green (2,543 passed, 3 skipped), mypy is clean, and git diff --check is clean.\n\nI have also split the broader work into focused PRs and will submit further additions in similarly small, tested pieces. If you have any further feedback or pointers on scope, protocol evidence, or preferred API shape, they are very welcome. |
|
@jonathanstokes All requested changes are now pushed, each inline comment has a specific response, and all 15 review conversations are resolved. When convenient, could you please re-review this PR? Thank you again. |
…ies on Pre-PR self-review findings, addressed: - The test held the firmware half of the identity predicate and not the serial half; a predicate on app_fw_version alone passed it. Both halves are now asserted. - Listener-before-wake ordering was pinned for request() only. A sibling test in test_transport.py holds it for await_broadcast, which version() now uses and the identity entry's arrival count relies on. - LoopbackTransport's replies fallback is scoped to the reads that really answer with a same-type message (SAME_TYPE_READS), so any other type filed in the wrong dict still hits the loud error. - ReplyingTransport asserts the trigger sent exactly one message before keying the canned reply off it. - The CLI's _open_unconnected docstring no longer gives a stale reason. - version()'s docstring and the coverage row record the post-fix hardware run: five back-to-back calls through the new path all returned the full reply. - ADR-0020: status line matches the file's convention; the version() consequence no longer says the fix "lands first" or demands the full reply; ATMA = Mini is labelled as inferred from the schema; the catalog PR is named as #42, split into #44; the footswitch sentence reads as intended and names its source. - STEERING, CLAUDE.md and architecture.md say the seam is not built yet rather than describing it in the present tense, and attribute the "d14e on 4.1.0" claim to the contributor. - protocol.md's new Version section is 4.4, after 4.3, instead of an unnumbered heading that swallowed the rest of section 4; the host undo measurement behind the domain-model correction has a coverage row.
Two contributions arrived from units the maintainer does not have - a Mini opened over USB (stokes-audio#31) and a CorOS 4.1.0 catalog (stokes-audio#42, then stokes-audio#44) - and neither could be verified on the unit that defines the baseline. ADR-0020 decides the shape before the seam is built: a connection resolves a device profile from device_type and zenos_git_hash in the unit's own Version reply, an unknown pair refuses to connect, profile= is the deliberate way to measure an unmeasured unit, and everything that differs by firmware or model lives on the profile. Profiles are named by CorOS version, never by app_fw, because d14e is reported on both 4.0.1 and 4.1.0. STEERING sections 2 and 6, the decision table and change log, the protocol.md header, the architecture checklist, README, contributing.md and CLAUDE.md now describe profiles with evidence levels instead of one baseline. protocol.md gains the record behind the version() fix, "A Version READ is answered twice". domain-model.md corrects the claim that no host can trigger undo (UndoRedo{UPDATE, undo: true} does, measured on 4.0.1 and reported on 4.1.0) and records the U+00A4 separator seen in 27 labels of the 4.1.0 catalog and none of 4.0.1. No code branches on a version string yet. protocol.models, params and options still mean the Quad Cortex 4.0.1 snapshot, and CC_VERSION still announces 4.0.1.
…tocol-coverage # Conflicts: # docs/domain-model.md # docs/protocol.md # pyquadcortex/protocol/client.py
|
Updated onto current main in b3cd54c and resolved the overlap with ADR-0020/#51 by keeping the maintainer's await_broadcast-based version() implementation and partial-identity semantics. The four contributed operations remain isolated here. Validation: 2,544 passed, 3 skipped; mypy clean; git diff --check clean. GitHub would not allow me to submit a formal review request through the API, so @jonathanstokes, could you please re-review when convenient? |
The suite is the instrument that measures a profile, and it could not be run against the unit that needs measuring: it connected with no profile= and refused before a test could look. --profile CLASSNAME resolves a name over QuadCortex and everything registered under it, and an unknown name stops the run naming the real ones. The 4.1 docstring also cites its evidence: PR #42's description, 2026-09-03, 2742 passed / 8 skipped on CorOS 4.1.0 / d14e, by tony-xmelon - a contributor's report the maintainer has not reproduced.
|
Updated onto the merged ADR-0020/device-profile seam from #52 in 4dcafd8. The PR remains limited to the four operations and portability fixes; the new hardware-operation inventory now marks set_device_name, undo, redo, and inhibited_modules explicitly. Validation on the updated branch: 2,698 passed, 3 skipped; mypy clean; git diff --check clean. @jonathanstokes, could you please re-review when convenient? Thank you. |
|
Synced with upstream �2331cc (including #53 and the new #54 contribution policy) at head 50238a1. I moved this back to draft so the required hardware suite can be rerun on this exact final commit before requesting review. Offline validation after the sync: 2702 passed, 3 skipped; mypy clean; git diff --check clean. |
|
@jonathanstokes, thank you again for the detailed review. The requested split and follow-up changes are complete, the final head is cb4d96e, and the PR is marked ready with fresh offline and targeted hardware evidence in the description. GitHub does not permit this contributor account to invoke the re-request-review API here, so please treat this as the re-review request when convenient. |
…tocol-coverage # Conflicts: # changelog.md # docs/api.md # docs/domain-model.md # docs/manual-coverage.md # docs/protocol.md # pyquadcortex/protocol/client.py # tests/hardware/readme.md
|
Thanks @tony-xmelon. I re-reviewed this on I did not run the hardware suite. Four things need changing first, and three of them came in with the September 19 sync from main rather than from your earlier work. I checked all fifteen threads from the September 3 review against the code instead of reading the replies, and those are genuinely addressed. This is new ground. One thing to say up front, because most of what follows is about my unit. You do not have a 4.0.1 unit, and the full suite refuses on yours while the 4.1 snapshots are held back in #44. So 4.0.1 behavior was never something you could check, and I am not treating any of it as something you missed. Catching it is what the maintainer run is for. I am writing it down so the next push has it. 1. The connect-burst test cannot pass on my unit.
expected_announces = {412: 1, 420: 0}
assert factory_count in expected_announces412 stopped being the 4.0.1 count on September 15. There was no way for you to see this. The number only bites on a 4.0.1 unit, your runs were on 2. That assertion keys firmware behavior on a model count. CLAUDE.md says anything that differs by firmware or model lives on the profile class, and nothing else tests a version string. A factory-model count is standing in for the CorOS version here. 3. A 4.1.0 observation is recorded only in a test docstring. The note about four 4.1.0 connections carrying no announce answer should sit in The simplest fix for all three is to put 4. The undo test can discard my unsaved edits.
assert qc.preset_dirty() is False, "the loaded preset has unsaved edits; save or reload it first"If I run the suite with unsaved edits on the loaded preset, the test saves those edits into the copy, then teardown recalls the original and discards them. ADR-0005 asks a successful run to leave the unit as it found it. Your run passed, and mine would pass too on any day the loaded preset happens to be clean. The guard is for the day it is not. Switching to Smaller things, none of them blocking:
The four operations are good and the docstrings read well. I want this in. This is waiting on you now. Once the hardware test file is back to base and the dirty-preset guard is there, I will run the suite on my 4.0.1 unit and we can finish it. Stokes |
…tocol-coverage # Conflicts: # changelog.md
|
Thank you for the careful re-review and for distinguishing the 4.0.1 findings from things we could have observed on our 4.1 unit. All requested changes are now on
The upstream September 21 changes are merged as well. Exact-head validation on Windows is The branch is ready for your 4.0.1 hardware run when convenient. Thanks again—the profile guidance and the new shared fixture made the end state materially clearer. |
jonathanstokes
left a comment
There was a problem hiding this comment.
Re-reviewed on 68be881f, and I ran the hardware suite on my 4.0.1 unit. This is an approval.
The hardware run
Full suite, on this exact head, CorOS 4.0.1 / d14e:
============== operations on QuadCortex (CorOS 4.0.1, MAINTAINER) ==============
tests: 120 passed, 0 failed, 0 errored, 3 skipped; operations measured: 21 of 21 claimed
passed: ['active_scene', 'clear_expression', 'create_local_backup', 'inhibited_modules', 'read_current_preset', 'redo', 'set_bypass', 'set_chain_input', 'set_device_name', 'set_expression', 'set_global_eq', 'set_hold_timing', 'set_input_port', 'set_param', 'set_scene_color', 'set_scene_label', 'set_tempo_mode', 'switch_scene', 'tempo_mode', 'undo', 'update_settings'] (21)
failed or skipped: [] (0)
passed, not VERIFIED: [] (0) <- candidates to add
VERIFIED and claimed by a test, failed: [] (0) <- regressions by name
120 passed, 3 skipped in 285.29s (0:04:45)
All four new operations passed and are named by a test: set_device_name, undo, redo, inhibited_modules. Nothing failed, nothing errored, and the run left the unit as it found it.
The four points from my September 21 re-review
I checked each against the code rather than the reply. All four are genuinely fixed.
tests/hardware/test_model_state.pyis byte-for-byte what is on main. The 412 count is gone, and so is the assertion that carried it.- The model count no longer stands in for a firmware version. The revert took it with it.
- The 4.1.0 connect-burst observation moved to #44. Nothing left here depends on it.
- The undo test takes
scratch_presetand gets the dirty-preset guard with it. It is the post-#76 fixture, keyed on the start-of-session flag, not a shape that only still imports.
Of the five smaller ones, three are fully fixed: the readme sentence, HasField on all three name reads, and Transport.request(match=) on the production side.
What I am not blocking on
None of the below can fail CI or the hardware run, and none of it changes what a 4.0.1 user gets. I am happy for this to merge as is and for these to come later, but they are all real and four of them are the shape this repo cares most about: a true statement replaced by a false one.
Two of the six refreshed reasons in UNMARKED_OPERATIONS are not true. This is my point (d), answered a little too eagerly. Suggestions inline.
docs/protocol.md now has two undo/redo rows. Line 2159 is mine, already on main, and it says the operations "land in PR #42". This PR added a second row at 2095 instead of updating that one. After merge a reader gets two different answers, and one of them says the work has not landed yet. The same stale forward reference sits in the prose at line 1786.
undo, redo and inhibited_modules are not in QuadCortex41.VERIFIED. You added set_device_name, which is what I named, but the sentence I wrote about it is now true of the other three word for word.
Three test doubles kept the match= you dropped from the real transport. Harmless today, and a trap later.
Thanks for this. The four operations are clean, the wire bytes are pinned both ways, and the evidence is in the docstring and the coverage table where it belongs.
| "read_preset": "the undo/redo test reads its scratch copy to choose an edit, but verifies the history result rather than the read operation itself", | ||
| "find_preset": "no hardware test yet; it needs a name that exists on the unit, which the loaded preset supplies - just not written", | ||
| "wait_for_listing": "no hardware test yet; it is the polling wrapper round list_presets and says nothing until a write changes a listing", | ||
| "wait_for_listing": "scratch_preset teardown uses it to confirm deletion; no test independently verifies its polling and stability contract", |
There was a problem hiding this comment.
wait_for_listing appears nowhere under tests/hardware/. _release_scratch calls delete_preset and nothing else, so the teardown does not use it and confirms no deletion. The reason it replaced was correct. The marker test only checks a reason is longer than 20 characters, so CI cannot catch this.
| "wait_for_listing": "scratch_preset teardown uses it to confirm deletion; no test independently verifies its polling and stability contract", | |
| "wait_for_listing": "no hardware test yet; it is the polling wrapper round list_presets and says nothing until a write changes a listing", |
| "save_current_preset": "the undo/redo test's scratch_preset fixture confirms a save into a free User slot, but the test verifies undo/redo rather than the save wire shape", | ||
| "recall_preset": "no hardware test yet; a recall discards the loaded preset's unsaved edits, so it needs scratch_preset around it", | ||
| "delete_preset": "no hardware test yet; only scratch_preset's teardown deletes anything, and a teardown asserts nothing", | ||
| "delete_preset": "the undo/redo test's scratch_preset teardown deletes its copy and confirms absence; no test isolates the delete wire shape itself", |
There was a problem hiding this comment.
The teardown does not confirm absence. _release_scratch deletes unconditionally, and the fixture's own comment explains that a listing guard was tried there and removed on purpose, because list_presets is unreliable on a single READ. delete_preset also returns None rather than raising when the unit does not answer.
| "delete_preset": "the undo/redo test's scratch_preset teardown deletes its copy and confirms absence; no test isolates the delete wire shape itself", | |
| "delete_preset": "the undo/redo test's scratch_preset teardown deletes its copy, but the delete is deliberately unconfirmed and a teardown asserts nothing", |
| ``create_local_backup`` has its own dated 4.1.0 capture and is VERIFIED; | ||
| ``create_local_backup`` has its own dated 4.1.0 capture. The contributed | ||
| device-name round trip also verified ``set_device_name``; both are VERIFIED. | ||
| other inherited operations refuse under ``Support.VERIFIED`` and run with |
There was a problem hiding this comment.
Splitting the semicolon left this sentence starting lowercase.
| other inherited operations refuse under ``Support.VERIFIED`` and run with | |
| Other inherited operations refuse under ``Support.VERIFIED`` and run with |
| MEASURED_ON = ("4.1.0",) | ||
| EVIDENCE = Evidence.CONTRIBUTED | ||
| VERIFIED = frozenset({"create_local_backup"}) | ||
| VERIFIED = frozenset({"create_local_backup", "set_device_name"}) |
There was a problem hiding this comment.
This is the one thing I would most like settled, and it is yours to settle. What I wrote about set_device_name is now true of three more: on a 4.1.0 unit with the default Support.VERIFIED, qc.undo(), qc.redo() and qc.inhibited_modules() raise ControlNotDrivable, while their docstrings say they are confirmed on 4.1.0. Your evidence for those three looks the same as for set_device_name. If it is, promote them. If you are holding them back on purpose, say why here and I will take that.
| | connect handshake | `ResetCommsBuffers` + `Version` UPDATE + `ModelRepo` READ + `Connection` + subscribe READs | read-back | the connect gate; state pushes flow only after it | | ||
| | version read | `Version{action: READ}` | read-back | two messages come back; `version()` accepts only one carrying `device_serial_number` or `app_fw_version` (section 4.4) | | ||
| | `set_device_name` | `Version{UPDATE, custom_name}` | read-back + on-unit | sparse echo, read-back, and restoration confirmed on CorOS 4.0.1 and 4.1.0 | | ||
| | `undo` / `redo` | `UndoRedo{UPDATE, undo}` / `{UPDATE, redo}` | read-back + on-unit | a bypass edit was reversed and reapplied on disposable preset copies; empty-history behaviour is unknown | |
There was a problem hiding this comment.
This table already has an undo/redo row at line 2159, which I wrote: host undo / redo (undo() / redo() land in PR #42). Two rows for one operation, and they disagree on the verification column. The fix is to update 2159 and drop this one, rather than add beside it. Line 1786 needs the same treatment: its (PR #42, where undo() and redo() land) becomes false on merge.
| Presence matters here: protobuf also returns false when an optional field was | ||
| never sent, and reporting that default as device state would be a guess. | ||
| """ | ||
| # Read twice because a first state READ can be dropped; both successful |
There was a problem hiding this comment.
inhibited_modules() goes through _read_state to await_broadcast, which raises TimeoutError when no matching broadcast arrives. There is no retry. So on the unit this comment describes, a dropped first READ fails the test after the timeout rather than being covered by the second pass. The loop repeats the assertion, which is still worth doing; it just is not what the comment claims.
| # Read twice because a first state READ can be dropped; both successful | |
| # Read twice so a second reply must also carry explicit fields rather | |
| # than protobuf defaults. A dropped first READ raises; there is no retry. |
| self.sent.append(msg) | ||
|
|
||
| def request(self, msg, timeout=5.0): | ||
| def request(self, msg, timeout=5.0, match=None): |
There was a problem hiding this comment.
You dropped match= from the real Transport.request, but the doubles here, in tests/test_device.py and in tests/test_state.py kept it, and the last two implement the match semantics fully. The doubles are now wider than the thing they stand for: a later self._t.request(msg, match=...) in client.py would pass the whole offline suite and raise TypeError against a real unit. Worth removing from all three while it is still free. self.last_match on the next line has no reader either.
#42 was approved and its hardware suite passed on the 4.0.1 unit. These are the non-blocking findings from that review, applied rather than left to the author. None of them changes what the library does. Two of the six refreshed reasons in UNMARKED_OPERATIONS were not true. The wait_for_listing entry said "scratch_preset teardown uses it to confirm deletion"; wait_for_listing appears nowhere under tests/hardware/, and _release_scratch calls delete_preset and nothing else. The reason it replaced was correct, so it is back. The delete_preset entry claimed the teardown "confirms absence"; the fixture's own comment explains that a listing guard was tried there and removed on purpose, because list_presets is unreliable on a single READ, and delete_preset returns None rather than raising when the unit does not answer. Nothing catches either one: the marker test checks only that a reason is longer than 20 characters. docs/protocol.md carried two undo/redo rows in one table. The maintainer's row was already there and said the operations "land in PR #42"; #42 added a second row beside it instead of correcting it. After the merge a reader gets two answers, and one of them says the work has not landed. writing.md asks a correction to replace the wrong sentence rather than sit next to it, so the two rows are folded into the original's position. Every fact from both survives: the 2026-09-03 4.0.1 measurement, the contributor's 4.1.0 run, the bypass edit reversed and reapplied on disposable preset copies, and empty-history behaviour being unknown. The verification column takes the stronger of the two, "read-back + on-unit". The same stale forward reference in the prose at section 11 now names undo() and redo() as the drivers instead of promising them. Three test doubles kept a request(match=) argument that the real transport no longer has, and two of them implemented its semantics fully. A double wider than the thing it stands for is a trap: a later request(msg, match=...) in client.py would pass the whole offline suite and raise TypeError against a unit. In test_client.py the same edit also set self.last_match on every request() call, which would overwrite the predicate await_broadcast stores there and turn the assertions that read it into a TypeError on None. The read-twice comment in the inhibited_modules hardware test said the second read covers a dropped first READ. It does not: _read_state goes to await_broadcast, which raises TimeoutError with no retry. Reading twice is still worth doing, so the loop stays and the comment now says what it is for. profiles.py starts a sentence lowercase, from the semicolon #42 split. Not fixed here: undo, redo and inhibited_modules are absent from QuadCortex41.VERIFIED while their docstrings claim 4.1.0 confirmation, which is the defect #42 fixed for set_device_name alone. Promoting them needs 4.1 hardware evidence this repository does not have, and that evidence is the contributor's. It is raised on #42 for him to settle. No changelog entry: nothing here is visible to someone who installs the package.
Two findings from /triage-pr on the first commit, both the same defect this pull request exists to remove: a sentence the code contradicts. The merged undo/redo coverage row claimed "read-back + on-unit". The table's own legend defines on-unit as confirmed on the unit's own screen, and no record in this repository says anyone watched the screen. Section 11 says "Measured 2026-09-03 on CorOS 4.0.1 by preset read-back". The undo and redo docstrings say a bypass edit was reversed and reapplied, with no screen reading. test_identity_and_history.py asserts only through read_current_preset(). The fold took the stronger of the two columns as though strength were the question; it is not, the measurement is, and the row it replaced already had it right. Back to "preset read-back", which is the maintainer's own wording. Nothing catches this: no test parses the Verified-by column. The section comment above the preset-library entries still said the scratch_preset fixture exists "but no test uses it yet". #42 landed the test that uses it, and the two entries immediately below the comment both describe that test. Four consecutive lines gave two answers to the same question. Also corrected: "drive it" in section 11 pointed at the 4.1.0 measurement rather than at the messages, because that measurement is the nearest antecedent. Dismissed: the merged row spelling the redo half `{redo: true}` rather than `{UPDATE, redo}` - the action is carried by the first half and this is the pre-existing row's shorthand. Raised for later, not here: nothing pins the test doubles' request() signature against protocol.transport.Transport.request, which is why #42 could widen three doubles and stay green. That wants a source-reading test of its own.
Six follow-ups from #42, four of them a statement the code contradicts
Summary
inhibited_modules(),set_device_name(),undo(), andredo()api.md,manual-coverage.md, the hardware guide, and the changelogThis is PR A from the split requested in review. The CorOS 4.1 catalog and profile evidence remain isolated in #44.
Related issue
Split and revised in response to the review on this PR.
Testing
cb4d96epython -m pytest -q:2702 passed, 3 skipped51 passedpython -m mypy: passedgit diff --check: passedHardware
No compatible unit is available for a full final-head run: the attached standard Quad Cortex runs CorOS 4.1.0, while this PR deliberately excludes the CorOS 4.1 snapshot now isolated in #44 at the maintainer's request. Consequently the full suite correctly refuses its three missing 4.1 generated snapshots. A maintainer with the baseline CorOS 4.0.1 profile will need to run
pytest tests/hardware --hardwarebefore merge.The four operations added here were nevertheless run individually on final commit
cb4d96eagainst the standard Quad Cortex on CorOS 4.1.0, with read-back/device confirmation and restoration:set_device_namewas read back and restored. Undo and redo reversed and reapplied a disposable scratch-preset edit, after which the original preset state was restored and the scratch preset removed.inhibited_modulesreturned both optional module flags from the unit.Checklist
Upstream sync — 2026-09-12
Merged current upstream main at 640a8e9 (including merged PRs #63–#65) into this branch. Current head:
5b9bca4Full offline Windows suite:
2727 passed, 3 skipped; mypy: clean across 46 source files;git diff --check: clean.The Windows path-separator and CRLF-fixture portability checks pass on this branch because their fixes are part of #42.
Upstream sync - 2026-09-19
Merged upstream main at
cfe39cc6592148d4196fa3028c2776f6618c41b6(including maintainer PRs #70-#75) and resolved overlaps in favor of the current upstream API and evidence model.Current head:
f0ac2e4039d058ca4fa96efea920f641c9ac9194Focused validation: 4 focused protocol tests passed; the updated manual-coverage count check passed.
GitHub CI on this exact head is green: build + twine check, mypy, and tests on Python 3.11, 3.12, and 3.13 all pass.
No new maintainer comments were posted during this sync. This PR is ready for maintainer re-review; the displayed CHANGES_REQUESTED decision is the earlier review and has not yet been refreshed.