From e3addbfa78abd8e6f57437315c4880816b8f3c7c Mon Sep 17 00:00:00 2001 From: alvvm Date: Tue, 8 Sep 2026 08:16:08 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(sdk):=20PJ=5FDATA=5FPROCESSOR=5FFLAG?= =?UTF-8?q?=5FHISTORY=5FEXEMPT=20=E2=80=94=20a=20persisted=20node=20that?= =?UTF-8?q?=20undo/redo=20has=20no=20authority=20over=20(0.34.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A data processor created with this bit is written to the layout file like any other, but the host's undo/redo history never restores, recreates or removes it. Orthogonal to EPHEMERAL, which is never persisted at all. The bit exists for plugin-driven creations that are not part of the user's edit stream: an assistant that builds a derived series on request should not see it vanish because the user undid an unrelated plot move. Which plugins set it is an adoption decision; the host stays neutral. Additive: one flag bit, no vtable change. Older hosts reject it under the reserved-bits rule, and a flag bit is not runtime-negotiated, so a plugin that must also run there treats that rejection as unsupported and falls back on purpose. feature_floors.json lists it under 0.34.0; host_surfaces.snapshot regenerated with the repo's extractor. --- CHANGELOG.md | 15 +++++++++++++++ VERSION | 2 +- pj_base/feature_floors.json | 3 +++ pj_base/include/pj_base/plugin_data_api.h | 11 ++++++++++- pj_base/include/pj_base/sdk/plugin_data_api.hpp | 4 +++- tools/feature_floors/host_surfaces.snapshot | 1 + 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70e2f57c..885f8542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to `plotjuggler_sdk` are recorded here. Versioning policy is in [`CLAUDE.md`](./CLAUDE.md) → "Release Versioning". +## [0.34.0] + +Host contract: extended: PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT (floor 0.34.0). + +- Add `PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT` to `create_data_processor`'s + `flags` bitset: the node is persisted in the layout file like any other + processor, but the host's undo/redo history never restores, recreates or + removes it — history has no authority over it. Orthogonal to + `PJ_DATA_PROCESSOR_FLAG_EPHEMERAL` (never persisted at all, so this bit is + irrelevant on it). Purely additive: an older host that does not know this + bit rejects the create under the existing reserved-bits rule. A flag bit is + not runtime-negotiated (no `struct_size` reveals it), so a plugin that must + also run on older hosts treats that rejection as "unsupported" and falls + back deliberately — e.g. re-creating without the bit and saying so. + ## [0.33.0] Host contract: unchanged (no floor impact) — this release adds the floor diff --git a/VERSION b/VERSION index be386c9e..85e60ed1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.33.0 +0.34.0 diff --git a/pj_base/feature_floors.json b/pj_base/feature_floors.json index 92c8c77b..70d5dd84 100644 --- a/pj_base/feature_floors.json +++ b/pj_base/feature_floors.json @@ -11,6 +11,9 @@ }, "0.32.0": { "setDatasetMetadata": "PJ_data_source_runtime_host_vtable_t::set_dataset_metadata" + }, + "0.34.0": { + "PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT": "" } }, "baseline": [ diff --git a/pj_base/include/pj_base/plugin_data_api.h b/pj_base/include/pj_base/plugin_data_api.h index 6e1a4caa..d626242f 100644 --- a/pj_base/include/pj_base/plugin_data_api.h +++ b/pj_base/include/pj_base/plugin_data_api.h @@ -939,7 +939,10 @@ typedef struct { * {"scope":"all"} makes a global node publish across EVERY dataset; * absent = active dataset. * - flags : bitset; PJ_DATA_PROCESSOR_FLAG_EPHEMERAL marks a preview (never - * persisted, dropped on remove). Reserved bits must be 0. + * persisted, dropped on remove). PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT + * marks a node the host's undo/redo history has no authority over + * (still persisted like any other node; history never restores, + * recreates or removes it). Reserved bits must be 0. * * DATASET-QUALIFIED NAMES — a series' full identity is (dataset, topic, field); a * bare "topic/field" name is an abbreviation that stops being unique the moment @@ -974,6 +977,12 @@ typedef struct { /* create_data_processor flags. */ #define PJ_DATA_PROCESSOR_FLAG_EPHEMERAL (1u << 0) /* preview: never persisted, dropped on remove_data_processor */ +/* Persisted in the layout file like any other processor, but the host's undo/redo + * history never restores, recreates or removes it -- history has no authority over + * it. Orthogonal to EPHEMERAL: an EPHEMERAL processor is never persisted at all, so + * this bit is irrelevant on it. Hosts that do not know this bit reject it as a + * reserved bit, per the usual reserved-bit rule. */ +#define PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT (1u << 1) typedef struct PJ_data_processors_host_vtable_t { uint32_t protocol_version; // = 1 diff --git a/pj_base/include/pj_base/sdk/plugin_data_api.hpp b/pj_base/include/pj_base/sdk/plugin_data_api.hpp index 5e18d964..a1df513c 100644 --- a/pj_base/include/pj_base/sdk/plugin_data_api.hpp +++ b/pj_base/include/pj_base/sdk/plugin_data_api.hpp @@ -1517,7 +1517,9 @@ class DataProcessorsHostView { /// Create or replace (upsert by id) a data processor of `kind` ("transform" or /// "markers"). `outputs` may be empty for an ephemeral preview (flags & - /// PJ_DATA_PROCESSOR_FLAG_EPHEMERAL), in which case the host names the sink(s). + /// PJ_DATA_PROCESSOR_FLAG_EPHEMERAL), in which case the host names the sink(s); + /// flags & PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT instead marks a persisted node the + /// host's undo/redo history has no authority over. /// Returns the resolved physical output topic name(s) (owned copies) so the caller /// can read results back through the kind's read surface. `params_json` is forwarded /// verbatim to the script. Input names MAY carry the dataset qualifier diff --git a/tools/feature_floors/host_surfaces.snapshot b/tools/feature_floors/host_surfaces.snapshot index 8ed8d06d..9834fc27 100644 --- a/tools/feature_floors/host_surfaces.snapshot +++ b/tools/feature_floors/host_surfaces.snapshot @@ -1,4 +1,5 @@ PJ_DATA_PROCESSOR_FLAG_EPHEMERAL +PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT PJ_DESCRIPTOR_IMPORT_START_FLAGS_V1_MASK PJ_DESCRIPTOR_IMPORT_START_FLAG_NONE PJ_INGEST_COMPLETION_FLAGS_V1_MASK From 7aa866927d72c3e674211fff68988db327121ff4 Mon Sep 17 00:00:00 2001 From: alvvm Date: Tue, 8 Sep 2026 12:32:40 +0200 Subject: [PATCH 2/2] docs(sdk): HISTORY_EXEMPT is confirmed through data_processor_config, not assumed from create A flag bit is not runtime-negotiated, and a host that predates the bit may accept it silently. So a successful create proves nothing: the node's own recipe, read back through data_processor_config, carries the boolean history_exempt, and only a returned true confirms the property. A missing or false value, or a failed read, means the protection is not there and the plugin must say so. A conforming host still rejects unknown bits under the reserved-bits rule, which lets a plugin retry deliberately without the bit. --- CHANGELOG.md | 11 ++++++----- pj_base/include/pj_base/plugin_data_api.h | 11 ++++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 885f8542..9233c011 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,12 @@ Host contract: extended: PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT (floor 0.34.0). processor, but the host's undo/redo history never restores, recreates or removes it — history has no authority over it. Orthogonal to `PJ_DATA_PROCESSOR_FLAG_EPHEMERAL` (never persisted at all, so this bit is - irrelevant on it). Purely additive: an older host that does not know this - bit rejects the create under the existing reserved-bits rule. A flag bit is - not runtime-negotiated (no `struct_size` reveals it), so a plugin that must - also run on older hosts treats that rejection as "unsupported" and falls - back deliberately — e.g. re-creating without the bit and saying so. + irrelevant on it). Supporting hosts return the boolean `history_exempt` + through `data_processor_config` for transforms and markers. Only a returned + `true` confirms protection: older hosts may silently ignore unknown bits. + Conforming hosts reject unknown flags under the reserved-bits rule, allowing + a deliberate retry without the bit. A missing/false config property or a + failed config read also requires disclosing that protection is unavailable. ## [0.33.0] diff --git a/pj_base/include/pj_base/plugin_data_api.h b/pj_base/include/pj_base/plugin_data_api.h index d626242f..5ee65d51 100644 --- a/pj_base/include/pj_base/plugin_data_api.h +++ b/pj_base/include/pj_base/plugin_data_api.h @@ -980,8 +980,10 @@ typedef struct { /* Persisted in the layout file like any other processor, but the host's undo/redo * history never restores, recreates or removes it -- history has no authority over * it. Orthogonal to EPHEMERAL: an EPHEMERAL processor is never persisted at all, so - * this bit is irrelevant on it. Hosts that do not know this bit reject it as a - * reserved bit, per the usual reserved-bit rule. */ + * this bit is irrelevant on it. A conforming host rejects unknown flag bits. + * To confirm protection even on a host that silently ignores unknown bits, + * read data_processor_config after creation: only history_exempt=true confirms + * the property. A missing/false property or failed read does not confirm it. */ #define PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT (1u << 1) typedef struct PJ_data_processors_host_vtable_t { @@ -1019,7 +1021,10 @@ typedef struct PJ_data_processors_host_vtable_t { /* [main-thread] Read a node's full recipe as JSON * {"kind":"...","language":"...","inputs":[...],"outputs":[...],"params":{...}} for * re-edit (e.g. after a session reload). *out_recipe_json is borrowed, valid only - * until the next call on this vtable. An unknown id is an error. */ + * until the next call on this vtable. An unknown id is an error. + * Hosts supporting PJ_DATA_PROCESSOR_FLAG_HISTORY_EXEMPT include the boolean + * "history_exempt" for transforms and markers, reflecting the node's actual + * property. Callers must not infer support from a successful create alone. */ bool (*data_processor_config)( void* ctx, PJ_string_view_t id, PJ_string_view_t* out_recipe_json, PJ_error_t* out_error) PJ_NOEXCEPT;