Skip to content

Commit a7e64c8

Browse files
committed
Finalize QDMI configuration foundation
Assisted-by: GPT-5 via Codex
1 parent 2ab4196 commit a7e64c8

7 files changed

Lines changed: 55 additions & 251 deletions

File tree

.agent/plans/qdmi-configuration-foundation.md

Lines changed: 55 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Add v3-compatible QDMI configuration and registration
22

3+
Status: Final
4+
35
This ExecPlan is a living document. The sections `Progress`,
46
`Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must
57
be kept up to date as work proceeds.
@@ -52,9 +54,9 @@ remove the Driver. Those breaking changes remain in the separate v4 task.
5254
target dependencies, and verified build-tree discovery.
5355
- [x] (2026-07-15 16:44Z) Adapted the existing Driver to eagerly materialize the
5456
registry snapshot with per-definition failure isolation and weak
55-
process-wide provider-library reuse, while preserving its public API.
57+
process-wide provider-library reuse.
5658
- [x] (2026-07-15 16:58Z) Added legacy-client failure-isolation and library
57-
reuse coverage, build-tree and moved-install tests, static-consumer CMake
59+
reuse coverage, build-tree manifest tests, static-consumer CMake
5860
colocation, and verified the unchanged FoMaC, Qiskit, and neutral-atom
5961
suites against the installed Python package.
6062
- [x] (2026-07-15 17:02Z) Added v3-focused documentation and the #1912 changelog
@@ -70,24 +72,14 @@ remove the Driver. Those breaking changes remain in the separate v4 task.
7072
the provider DLL and validated the change with debug/release focused
7173
Driver tests plus repository-wide lint.
7274
- [x] (2026-07-16 09:54Z) Addressed review feedback by rejecting the reserved
73-
but unimplemented `qdmi.device-config` object and extending the relocation
74-
fixture to load and query every moved provider through the legacy Driver.
75-
- [x] (2026-07-16 10:00Z) Corrected the relocation provider-name query buffer
76-
according to the QDMI size contract.
77-
- [x] (2026-07-16 10:09Z) Completed a fresh full-branch cleanup pass, removing
78-
duplicate validation and stale iteration wording without changing the
79-
public API or relocation behavior.
80-
- [x] (2026-07-16 10:50Z) Accepted review feedback that the standalone registry
81-
controls had no production consumer in this stage. Made the registry and
82-
definitions private Driver details, removed `ConfigOptions` and mutation,
83-
and refocused tests on the supported file and environment contract.
84-
- [x] (2026-07-16 11:40Z) Superseded that boundary with the accepted stable-ID
85-
API, centralized provider target consumption, preserved legacy session
86-
snapshots, regenerated Python stubs, and passed focused native and Python
87-
tests.
88-
- [x] (2026-07-16 12:15Z) Completed a final implementation and wording cleanup,
89-
then passed generated-stub, warning-free documentation, lint, and diff
90-
validation.
75+
but unimplemented `qdmi.device-config` object.
76+
- [x] (2026-07-16 11:40Z) Kept registry discovery private, added stable-ID
77+
registration and opening, centralized provider target consumption,
78+
preserved legacy session snapshots, regenerated Python stubs, and passed
79+
focused native and Python tests.
80+
- [x] (2026-07-16 12:15Z) Removed public definition enumeration so stored
81+
credentials cannot be disclosed, removed the over-sized relocation
82+
fixture, finalized this plan, and completed validation.
9183

9284
## Surprises & Discoveries
9385

@@ -128,23 +120,20 @@ remove the Driver. Those breaking changes remain in the separate v4 task.
128120
`LoadLibraryExW` with `LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR`, while the compatible
129121
Driver uses plain `LoadLibraryW` for configured paths and therefore does not
130122
search beside DDSIM for its wheel-local dependencies.
131-
- Observation: The original moved-install test proved only manifest path
132-
resolution because it never opened the discovered libraries. Evidence:
133-
`DiscoversBuiltInsAfterInstallationIsMoved` checked `is_regular_file` and
134-
containment under the moved root but made no Driver or QDMI query call.
135123
- Observation: `qdmi.device-config` was accepted as an object but its contents
136124
were never consumed. Evidence: `parseConfiguration` called only
137125
`requireObject` for that key before continuing with `devices`.
138-
- Observation: The relocated-provider name query allocated `nameSize - 1`
139-
writable bytes but passed `nameSize` to the QDMI provider. Evidence: the QDMI
140-
property contract includes the trailing null byte in the reported size.
141126
- Observation: No production code outside the Driver used `DeviceRegistry` in
142127
this compatibility branch; the direct consumers were documentation and tests.
143128
Evidence: repository-wide reference inspection found only the Driver's default
144129
construction plus test and documentation examples.
145130
- Observation: Legacy client sessions retained a pointer to the Driver's mutable
146131
device vector. Evidence: copying handles at session allocation prevents later
147132
opens from changing an existing session's device view.
133+
- Observation: Returning registered definitions from the public Driver API would
134+
also return session tokens, passwords, and authentication paths. Evidence:
135+
`DeviceDefinition::session` contains the complete `DeviceSessionConfig`;
136+
definitions therefore remain internal after registration.
148137

149138
## Decision Log
150139

@@ -180,51 +169,27 @@ remove the Driver. Those breaking changes remain in the separate v4 task.
180169
this stage does not implement runtime superconducting or neutral-atom device
181170
models, so accepting the object would silently ignore user intent.
182171
Date/Author: 2026-07-16 / GPT-5 via Codex.
183-
- Decision: Exercise relocated providers through `Driver::open` and query their
184-
names in the existing relocation fixture. Rationale: this is a portable
185-
end-to-end check of the legacy loader and provider ABI without introducing a
186-
second installed consumer build. Date/Author: 2026-07-16 / GPT-5 via Codex.
187-
- Decision: Allocate the full provider-reported name size in a `vector<char>`
188-
and verify both a nonempty first character and a trailing null byte.
189-
Rationale: this follows the QDMI size contract without writing beyond the
190-
buffer. Date/Author: 2026-07-16 / GPT-5 via Codex.
191-
- Decision: Keep `DeviceRegistry` and `DeviceDefinition` private to the legacy
192-
Driver and remove `ConfigOptions` from this stage. Rationale: the Driver only
193-
needs default file and environment discovery, while all other direct users
194-
were tests or an invented documentation example. The future `DeviceManager`
195-
can introduce the appropriate public configuration surface when PR #1901 is
196-
rebased and reworked. Date/Author: 2026-07-16 / GPT-5 via Codex.
197-
- Decision: Supersede the private-definition decision with a public value and
198-
`Driver::registerDevice` plus `Driver::open`. Rationale: stable-ID
199-
registration has a production consumer now and aligns with future manager
200-
vocabulary without adding that manager. Date/Author: 2026-07-16 / GPT-5 via
201-
Codex.
172+
- Decision: Keep `DeviceRegistry` private, expose `DeviceDefinition` only as an
173+
input value, and provide `Driver::registerDevice` plus `Driver::open` without
174+
public definition enumeration. Rationale: stable-ID registration aligns with
175+
the future manager vocabulary, while returning definitions would disclose
176+
stored session credentials. Date/Author: 2026-07-16 / GPT-5 via Codex.
202177
- Decision: Record configured providers once in a global CMake property.
203178
Rationale: Driver dependencies, runtime copies, and rpaths consume one
204179
authoritative list. Date/Author: 2026-07-16 / GPT-5 via Codex.
205180

206181
## Outcomes & Retrospective
207182

208-
Draft PR #1912 now contains the independently releasable compatibility
209-
foundation. It preserves the v3 client, Driver, FoMaC, Qiskit, and neutral-atom
210-
surfaces while replacing central built-in enumeration with relocatable
211-
manifests. Focused native suites, the installed Python package, a moved runtime
212-
prefix, documentation, and lint all pass locally. The v4 PR #1901 remains
213-
untouched and preserved for reconstruction after this foundation merges. The
214-
first CI iteration additionally exposed a dangling structured binding and
215-
platform-specific path assertions; both now have debug and release regression
216-
coverage. Review follow-up strengthened the relocated install fixture from a
217-
path check to an actual legacy Driver load and provider property query, and the
218-
configuration parser no longer silently accepts the unimplemented
219-
`device-config` object. A second review pass corrected the provider-name buffer
220-
size. The final review iteration removed the unused standalone registry API.
221-
This stage exposes configuration through files and environment variables, while
222-
runtime callers use the Driver's stable-ID registration and opening methods.
223-
224-
The accepted comparison follow-up exposes the definition value, replaces the
225-
unstable imperative loading API with stable-ID registration and opening,
226-
centralizes configured provider target consumption, and preserves existing
227-
legacy session views when later devices are opened.
183+
PR #1912 contains the independently releasable configuration foundation. It
184+
preserves the v3 client, Driver, FoMaC, Qiskit, and neutral-atom surfaces while
185+
replacing central built-in enumeration with relative manifest fragments. Users
186+
can configure devices through files or environment variables and can register
187+
and open runtime devices by stable ID. The unstable imperative loader is gone.
188+
Registered definitions cannot be enumerated through the public Driver API, so
189+
stored session credentials are not exposed. Configured providers are recorded
190+
once for CMake consumers, libraries are reused across independent sessions, and
191+
legacy sessions retain the device snapshot they received at allocation. The
192+
larger v4 object-model refactor remains separate.
228193

229194
## Context and Orientation
230195

@@ -291,10 +256,10 @@ device snapshot when later definitions are opened.
291256

292257
Fourth, test the compatibility adapter through the unchanged client and FoMaC
293258
interfaces. Add a dedicated process-level test when singleton initialization
294-
must be controlled before any other test accesses `Driver::get()`. Extend
295-
installation tests to move the install tree and open built-ins after relocation.
296-
Exercise a wheel or installed Python package through `mqt.core.fomac`, not a new
297-
module. Preserve all existing Qiskit and neutral-atom names and tests.
259+
must be controlled before any other test accesses `Driver::get()`. Extend the
260+
build-tree checks to verify generated manifests and provider libraries. Exercise
261+
an installed Python package through `mqt.core.fomac`, not a new module. Preserve
262+
all existing Qiskit and neutral-atom names and tests.
298263

299264
Finally, add `docs/qdmi/configuration.md`, link it from the existing Driver
300265
documentation, and update `CHANGELOG.md` and `UPGRADING.md` with both the new
@@ -323,8 +288,7 @@ Run the unchanged Python compatibility tests after installing the package:
323288
uv sync --inexact --no-dev --no-build-isolation-package mqt-core
324289
uv run --no-sync pytest -q test/python/fomac test/python/na/test_na_fomac.py test/python/plugins/qiskit
325290

326-
Run QDMI-focused CTest selection and relocation/package tests added by this
327-
task, then build documentation and lint:
291+
Run the QDMI-focused CTest selection, then build documentation and lint:
328292

329293
ctest --test-dir build/debug -R 'qdmi|QDMI|fomac|FoMaC' --output-on-failure
330294
uvx nox --non-interactive -s docs
@@ -356,11 +320,11 @@ and the client interface remain present. Existing Qiskit and neutral-atom tests
356320
must run without migration edits. A search of the diff must show no deletion of
357321
those public surfaces.
358322

359-
Packaging acceptance requires successful discovery from the build tree, a moved
360-
installation tree, the Python wheel layout, and Windows runtime layout.
361-
Generated manifests and provider libraries must be beside one another and
362-
contain no build or install prefix. A fully static consumer must use an explicit
363-
configuration file because a portable module origin does not exist.
323+
Packaging acceptance requires successful discovery from the build tree, the
324+
Python wheel layout, and Windows runtime layout. Generated manifests and
325+
provider libraries must be beside one another and contain no build or install
326+
prefix. A fully static consumer must use an explicit configuration file because
327+
a portable module origin does not exist.
364328

365329
Final acceptance requires focused native and Python tests, the QDMI CTest
366330
selection, warning-free documentation, `uvx nox -s lint`, and `git diff --check`
@@ -391,24 +355,10 @@ Initial history inspection showed:
391355
These stable commit identifiers are evidence for reconstruction, not commits to
392356
cherry-pick wholesale.
393357

394-
Review follow-up validation on 2026-07-16 produced:
395-
396-
DeviceRegistry.*: 16 passed, 1 relocation-fixture-only skip
397-
mqt-core-qdmi-registry-relocation-test: 1 passed
398-
changed-file prek hooks: passed
399-
400-
Final private-registry validation on 2026-07-16 produced:
401-
402-
debug focused registry and Driver selection: 22 selected, 0 failed, 1 fixture-only skip
403-
release focused registry and Driver selection: 22 selected, 0 failed, 1 fixture-only skip
404-
focused FoMaC and Qiskit Python tests: 186 passed
405-
warning-free documentation build: passed
406-
uvx nox -s lint: passed
358+
Final validation on 2026-07-16 produced:
407359

408-
Stable-ID registration refinement validation on 2026-07-16 produced:
409-
410-
debug focused registry and Driver selection: 25 selected, 0 failed, 1 fixture-only skip
411-
release focused registry and Driver selection: 25 selected, 0 failed, 1 fixture-only skip
360+
debug registry and Driver tests: 112 passed
361+
release registry and Driver tests: 112 passed
412362
focused FoMaC, neutral-atom, and Qiskit Python tests: 280 passed
413363
generated Python stubs: passed
414364
warning-free documentation build: passed
@@ -422,10 +372,11 @@ At the end of this task, `qdmi::DeviceSessionConfig` and
422372
`include/mqt-core/qdmi/driver/Driver.hpp`. `DeviceRegistry` remains private and
423373
returns enabled definitions in stable ID order to the Driver.
424374

425-
`qdmi::Driver` exposes `definitions()`,
426-
`registerDevice(DeviceDefinition, bool)`, and `open(std::string_view)`. Every
427-
QDMI client export retains its existing signature. `MQT::CoreQDMIDriver`,
428-
`MQT::CoreFoMaC`, and `mqt.core.fomac` remain the runtime surfaces.
375+
`qdmi::Driver` exposes `registerDevice(DeviceDefinition, bool)` and
376+
`open(std::string_view)`. It intentionally provides no definition getter because
377+
definitions contain session credentials. Every QDMI client export retains its
378+
existing signature. `MQT::CoreQDMIDriver`, `MQT::CoreFoMaC`, and
379+
`mqt.core.fomac` remain the runtime surfaces.
429380

430381
The implementation depends on QDMI declarations, `nlohmann_json`, the vendored
431382
toml++ single header, spdlog, and platform dynamic-loading APIs. The registry
@@ -437,12 +388,9 @@ plan after feasibility analysis showed that the existing v4 commits cannot be
437388
split mechanically. It records the compatibility boundary, reconstruction
438389
strategy, and validation required before the v4 pull request may be rebuilt.
439390

440-
Revision note (2026-07-16): Recorded review-driven schema rejection for the
441-
unimplemented `device-config` object, runtime loading coverage for moved
442-
provider libraries, safe provider property buffers, and the final decision to
443-
keep the registry private until the `DeviceManager` rework.
444-
445-
Revision note (2026-07-16): Superseded the private-definition boundary after the
446-
accepted comparison selected stable-ID Driver registration. Recorded centralized
447-
provider consumption, legacy session snapshots, temporary Python bindings, and
448-
focused validation.
391+
Revision note (2026-07-16): Finalized the plan after implementing stable-ID
392+
registration, centralized provider consumption, private registry discovery,
393+
legacy session snapshots, and temporary Python bindings. Removed public
394+
definition enumeration to avoid disclosing session credentials and removed the
395+
large moved-install fixture in favor of focused build-tree and platform CI
396+
coverage.

include/mqt-core/qdmi/driver/Driver.hpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,6 @@ class Driver final : public Singleton<Driver> {
432432
sessions_;
433433

434434
public:
435-
/**
436-
* @brief Returns a snapshot of all registered device definitions.
437-
*/
438-
[[nodiscard]] auto definitions() const -> std::vector<DeviceDefinition>;
439-
440435
/**
441436
* @brief Registers a device definition without loading its library.
442437
* @param definition The definition to validate and store.

src/qdmi/driver/Driver.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -553,10 +553,6 @@ Driver::Driver() {
553553
}
554554
}
555555

556-
auto Driver::definitions() const -> std::vector<DeviceDefinition> {
557-
return definitions_;
558-
}
559-
560556
void Driver::registerDevice(DeviceDefinition definition, const bool replace) {
561557
if (definition.id.empty()) {
562558
throw std::invalid_argument("Device definition ID must not be empty");

test/qdmi/driver/test_driver.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -872,10 +872,6 @@ TEST(DeviceRegistrationTest, ValidatesDuplicatesAndReplacement) {
872872
const qdmi::DeviceDefinition original{
873873
.id = "test.replaceable", .library = library, .prefix = prefix};
874874
driver.registerDevice(original);
875-
const auto definitions = driver.definitions();
876-
EXPECT_NE(
877-
std::ranges::find(definitions, original.id, &qdmi::DeviceDefinition::id),
878-
definitions.end());
879875
EXPECT_THROW(driver.registerDevice(original), std::invalid_argument);
880876

881877
auto replacement = original;

test/qdmi/registry/CMakeLists.txt

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,4 @@ if(TARGET MQT::CoreQDMIDriver)
1212
package_add_test(${TARGET_NAME} MQT::CoreQDMIDriver test_device_registry.cpp)
1313
target_include_directories(${TARGET_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/src/qdmi/driver)
1414
mqt_copy_qdmi_runtime(${TARGET_NAME})
15-
add_test(
16-
NAME mqt-core-qdmi-registry-relocation-test
17-
COMMAND
18-
${CMAKE_COMMAND} -DBUILD_DIR=${CMAKE_BINARY_DIR}
19-
-DINSTALL_DIR=${CMAKE_CURRENT_BINARY_DIR}/relocation
20-
-DTEST_EXECUTABLE=$<TARGET_FILE:${TARGET_NAME}> -DCONFIG=$<CONFIG>
21-
-DRUNTIME_COMPONENT=${MQT_CORE_TARGET_NAME}_Runtime -P
22-
${CMAKE_CURRENT_SOURCE_DIR}/test_relocation.cmake)
23-
set_tests_properties(mqt-core-qdmi-registry-relocation-test PROPERTIES LABELS "qdmi;relocation")
2415
endif()

test/qdmi/registry/test_device_registry.cpp

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -249,39 +249,6 @@ TEST(DeviceRegistry, DiscoversGeneratedBuildTreeManifests) {
249249
}
250250
}
251251

252-
TEST(DeviceRegistry, DiscoversBuiltInsAfterInstallationIsMoved) {
253-
const auto* root = std::getenv("MQT_CORE_QDMI_RELOCATION_ROOT");
254-
if (root == nullptr) {
255-
GTEST_SKIP() << "This test is driven by the relocation CTest fixture";
256-
}
257-
const std::filesystem::path rootPath(root);
258-
const qdmi::detail::DeviceRegistry registry;
259-
ASSERT_EQ(registry.definitions().size(), 3);
260-
for (const auto& definition : registry.definitions()) {
261-
EXPECT_TRUE(std::filesystem::is_regular_file(definition.library));
262-
const auto relative = definition.library.lexically_relative(rootPath);
263-
ASSERT_FALSE(relative.empty());
264-
EXPECT_NE(*relative.begin(), std::filesystem::path(".."));
265-
266-
QDMI_Device device = nullptr;
267-
ASSERT_NO_THROW(device = qdmi::Driver::get().open(definition.id));
268-
ASSERT_NE(device, nullptr);
269-
270-
size_t nameSize = 0;
271-
ASSERT_EQ(QDMI_device_query_device_property(
272-
device, QDMI_DEVICE_PROPERTY_NAME, 0, nullptr, &nameSize),
273-
QDMI_SUCCESS);
274-
ASSERT_GT(nameSize, 1);
275-
std::vector<char> name(nameSize, '\0');
276-
EXPECT_EQ(QDMI_device_query_device_property(device,
277-
QDMI_DEVICE_PROPERTY_NAME,
278-
nameSize, name.data(), nullptr),
279-
QDMI_SUCCESS);
280-
EXPECT_NE(name.front(), '\0');
281-
EXPECT_EQ(name.back(), '\0');
282-
}
283-
}
284-
285252
TEST(DeviceRegistry, ReadsProjectConfigurationFromPyprojectToml) {
286253
const TemporaryDirectory directory;
287254
directory.write("pyproject.toml", R"(

0 commit comments

Comments
 (0)