feat(plugin): name-addressed XRecord CRUD on the HostApi (API v8)

Add create/read/update/delete/list for XRecord objects under
named-object-dictionary paths to the plugin HostApi, with five additive
PluginRequest/PluginResponse variants, dispatch through the shared
handler, in-process HostSession methods and both out-of-process proxies.

- dictionary paths walk from the NOD root; create seeds missing
  dictionaries (ensure_image_dictionary bootstrap) and refuses existing
  names; update refuses missing ones; refusals precede the undo step
- entry group codes validated (1..=369, never 5/105); payload content is
  the plugin's own
- each mutating call is one undo step: the pending history snapshot
  restores the objects map byte-exactly, auto-created dictionaries
  included (tested)
- typed entries survive DWG and DXF write/reload round-trips (tested);
  update drops verbatim raw bytes so saves emit the new content
- bump API_VERSION 7 -> 8 with the build.rs version_info in lockstep,
  mirroring the v7 addition; MIN_SUPPORTED stays 3
This commit is contained in:
Sebastian committed 2026-10-11 19:57:20 +02:00
1 parent 6cde0e02d8
commit ca2f0c9857
11 files changed
+1212 -3

No files matched your search

+18
View File
@@ -339,3 +339,21 @@ plugin gate (60 real-IPC host tests, 115 plugin API, 20 plugin crate, 16 Python
`--no-fail-fast` (1,805 passed, 1 failed: `fonts_parse_and_resolve`, which fails on pristine `main` on this Mac too; `arc_grips_drive_center_start_and_end_but_not_midpoint`
now passes, fixed upstream). The change is 72 files, +30,705 / -178 against current `main` (fork-only workflow excluded).
### 2026-10-11: XRecord objects (API v8, no Python surface yet)
New additive v8 requests `CreateXRecord` / `ReadXRecord` / `UpdateXRecord` / `DeleteXRecord` / `ListXRecords`
give plugins a non-graphical record carrier: name-addressed typed `XRecordEntry` pairs under the drawing's
named-object dictionary, with no carrier entity. `dictionary_path` is walked from the NOD root (an empty path
is the root), creating missing dictionaries on create only — the `ensure_image_dictionary` bootstrap — so the
first call seeds a namespace; `create` refuses an existing name and `update` refuses a missing one, so neither
can silently overwrite or create. Names and path fragments are symbol-validated, entry group codes are checked
(1..=369, never the handle codes 5/105), and every refusal is decided before the undo step is recorded. Update
replaces the entries atomically (handle, owner and name stay, the record's verbatim raw payloads are dropped, so
saves emit the new content); delete returns the removed record; list reports only the keys that address
XRecords, in dictionary order. Undo: objects-map mutations are captured by the pending history's full structure
snapshot, so each mutating call is one undo step whose undo restores the objects map byte-exactly, the
auto-created dictionaries included. Evidence: `xrecord_crud_round_trips_refusals_and_list`,
`xrecord_mutations_are_single_undo_steps_that_restore_objects` (four steps, refusal paths record none),
`xrecords_survive_dxf_and_dwg_round_trips` (typed entries reload from both writers), the IPC round-trips
`xrecord_request_and_response_roundtrip` and `xrecord_requests_await_their_typed_responses`. The API is
host-level and stands on its own; a Python mapping (`doc.xrecords`-style) is future work.
+17
View File
@@ -26,6 +26,7 @@ without it keeps working, and the default `HostApi` methods refuse.
| Selection | `selection`, `set_selection` | Ordered, tab-scoped, exact (missing or duplicate handles reject the request). |
| Solids | `solid_operation` | Kernel-backed create, transform, booleans, extrude, region and surface from a profile, picture embedding. Every result is verified to lift back from its ACIS payload without loss; a refusal leaves the operands untouched. |
| Tables | `table_operation` | Layers, text and dimension styles, blocks and their contents, linetypes, layouts (see below). |
| Objects | `create_xrecord`, `read_xrecord`, `update_xrecord`, `delete_xrecord`, `list_xrecords` | Name-addressed XRecords under the drawing's named-object dictionary (API v8, see below): a non-graphical record carrier with strict create-vs-update semantics and one undo step per change. |
| Commands | `run_command` | Drives the real OCS command one step at a time (see below). |
| Settings | `system_variable`, `set_system_variable` | `CLAYER`, `SNAPANG`, and a read-only `CTAB` (the current layout). |
@@ -85,6 +86,22 @@ recorded, so a failure changes nothing and leaves the history alone.
Style and layout operations act on the active document, because they use the
application's shared style and view machinery.
## XRecords (API v8)
`create_xrecord`, `read_xrecord`, `update_xrecord`, `delete_xrecord` and
`list_xrecords` address XRecords by name under a dictionary path walked from the
named-object dictionary root (an empty path is the root itself); missing
dictionaries are created on create only, so the first call seeds a plugin's
namespace. The payload is the codec's typed `XRecordEntry` pairs — the host puts
no opinion on the content. Names and path fragments are symbol-validated, and
dictionary addressing follows the codec's case-insensitive lookup. Create never
overwrites and update never creates; each refusal is decided before the undo step
is recorded, so a failure changes nothing, and each change is one undo step whose
undo restores the objects map byte-exactly, the auto-created dictionaries
included. `list_xrecords` reports only the keys that address XRecords, in the
dictionary's own order, so audits need no prior knowledge. Not yet mapped to
Python.
## Commands
`run_command` takes a `CommandRequest` (`Run`, `Start`, `Point`, `Text`, `Token`,