Namespaces | |
| namespace | detail |
Classes | |
| struct | BuildFingerprint |
| struct | KeyMaterial |
| struct | SwapStage |
| One half of a live swap. More... | |
| struct | LiveSwapResult |
| class | SwapUnit |
| The contract a swappable subsystem implements to join a live-swap transaction More... | |
| struct | PackBuildResult |
| struct | PackInstallResult |
| struct | ParamSupersetResult |
| Result of the SUPERSET contract check ("add params and stay live"): a candidate is a valid superset when every LIVE parameter is present in the candidate with an identical contract (existing automation / saved sessions stay valid), and the candidate may ALSO declare new parameters. More... | |
| class | ProcessorHotSwapSlot |
| class | ReloadController |
| class | ReloadLibrary |
| class | DspReloadSwapUnit |
| Adapts a ReloadSession reload-from-library to a SwapUnit. More... | |
| struct | ReloadMetrics |
| DSP-axis reload timings (control-thread, off the audio path). More... | |
| struct | ReloadOutcome |
| struct | GatedImage |
| A logic image that passed the pre-construction gates (reload-ABI version + build fingerprint) with its factory resolved. More... | |
| struct | SwapPackTrust |
| Trust material for verifying a swap-pack-delivered logic image BEFORE it is loaded. More... | |
| class | ReloadSession |
| Owns the session-stable state for a series of hot reloads of one plugin instance — the slot, the live store, the host fingerprint, the prepare context, and the retained images — so callers (a standalone shell, a file-watch controller) issue reload(path) without re-threading five coupled arguments, and the lifetime contract is enforced by construction. More... | |
| struct | ReloadTrustPolicy |
| class | ReloadableShell |
| struct | RemoteUpdateResult |
| struct | RemoteFetchResult |
| struct | FetchedPack |
| What an injected fetcher returns. More... | |
| struct | RemoteUpdateConfig |
| struct | SignedRevocationList |
| A signed revocation list. More... | |
| struct | SrlVerifyResult |
| struct | SrlFloorState |
| State of the persisted floor for a namespace. More... | |
| class | EpochFloorStore |
| Directory-backed store of the highest SRL epoch SEEN, namespaced by (plugin_id + revocation_pubkey_fpr + update_channel). More... | |
| class | ScriptedUiSwapUnit |
| struct | SwapPackFile |
| struct | SwapPackManifest |
| struct | SwapPackVerifyResult |
Typedefs | |
| using | CrossfadeCurve = signal::TransitionCurve |
| The crossfade amplitude law is the shared signal::TransitionMixer primitive — extracted so the hot-swap slot, the convolver IR swapper, and the Phase-2 SwapUnit transitions all fade identically and are covered by one click-free fixture. | |
| using | ReloadAbiVersionFn = int (*)() |
| using | ReloadFingerprintFn = void (*)(BuildFingerprint* out) |
| Fills *out with the logic library's own BuildFingerprint. | |
| using | ReloadCreateFn = Processor* (*)() |
| Constructs the logic Processor; the caller takes ownership of the result. | |
| using | ReloadDestroyFn = void (*)(Processor*) |
| Destroys a Processor returned by ReloadCreateFn, via the logic image's operator delete (reserved; see kDestroySymbol). | |
| using | ReloadTrustDecision = std::variant<std::monostate, SwapPackTrust, ReloadOutcome> |
The three outcomes of applying the policy to logic_path: | |
Enumerations | |
| enum class | PackInstall { Ok = 0 , NotTrusted , SymlinkRejected , UnknownKind , IoError } |
| enum class | KeyRole { Signing , Revocation } |
| enum class | LeakPolicy { Retain , CloseOnDestroy } |
| What the handle does with the loaded image when it is destroyed. More... | |
| enum class | RemoteUpdateVerdict { Accept = 0 , RejectSignature , RejectKind , RejectStaleRevocation , RejectRevoked , RejectDowngrade , RejectCapabilityEscalation } |
| enum class | RemoteFetchStatus { Disabled , Applied , UpToDate , Rejected , Unavailable } |
| enum class | SrlVerify { Ok = 0 , ParseError , UntrustedSigner , BadSignature } |
| enum class | SrlAcceptance { Accepted = 0 , RejectedRollback , FloorCorrupt , PersistError } |
| Outcome of offering an SRL to the store. More... | |
| enum class | SwapPackKind { UiScript , DspGraph , NodePack , WasmDsp , Unknown } |
| Payload kind for one file in a swap pack. More... | |
| enum class | SwapPackVerify { Ok = 0 , ManifestParseError , MissingFile , ReadError , HashMismatch , UntrustedSigner , BadSignature } |
Functions | |
| BuildFingerprint | current_build_fingerprint () |
| Capture THIS translation unit's build fingerprint. | |
| bool | fingerprints_match (const BuildFingerprint &a, const BuildFingerprint &b) |
| Field-by-field equality (the gate). | |
| std::vector< std::string > | fingerprint_diff (const BuildFingerprint &shell, const BuildFingerprint &logic) |
| Human-readable per-field differences (diagnostics only). Empty == identical. | |
| std::string | serialize_key_blob (const KeyMaterial &k) |
| Serialize a keypair to a text blob: magic line, base64 public, base64 private. | |
| std::optional< KeyMaterial > | parse_key_blob (std::string_view blob) |
| Parse a key blob. | |
| std::optional< KeyMaterial > | load_or_generate_key_file (const std::filesystem::path &path, bool &generated) |
Load the key at path, or generate a fresh keypair, write it there (0600), and return it with generated=true so the caller can scream. | |
| LiveSwapResult | apply_live_swap (const std::vector< SwapStage > &stages) |
Apply stages as one transaction. | |
| LiveSwapResult | apply_live_swap (const std::vector< SwapUnit * > &units) |
| Run an ordered set of SwapUnits as one transaction: collect each unit's stage (snapshotting current state), then delegate to apply_live_swap. | |
| std::string | pack_read_all_bytes (const std::filesystem::path &p) |
| PackBuildResult | build_signable_manifest (const std::filesystem::path &bundle_dir, const std::string &plugin_id, std::uint64_t pack_version, const std::string &update_channel) |
Walk bundle_dir, hash each regular file into a manifest entry (path relative to the bundle root, POSIX separators), and infer declared_capabilities by running autocaps over the concatenated JS. | |
| std::string | swap_pack_content_id (const SwapPackManifest &m) |
| The content id is the SHA-256 of the pack's signed message — every signed field (id, plugin, version, policy, per-file hash+kind) feeds it, so two byte-identical packs collide (dedup) and any change yields a new dir. | |
| PackInstallResult | install_verified_pack (const std::filesystem::path &source_root, const SwapPackManifest &manifest, const std::vector< std::uint8_t > &trusted_public_key, const std::filesystem::path &install_base, bool reject_unknown_kinds=true) |
Verify source_root against trusted_public_key, then install the pack's files into install_base/<content-id>/ as read-only, rejecting symlinks and (for consumer packs) unknown kinds. | |
| std::string_view | key_role_name (KeyRole r) |
| std::string | reload_keychain_service (KeyRole role, std::string_view plugin_id) |
| Keychain service id: pulp.reload.
| |
| std::string | reload_key_account (int generation, std::string_view fpr_hex) |
| Keychain account/label carrying the generation and a short fingerprint prefix, e.g. | |
| std::string | to_secret_token (std::string_view s) |
| Upper-snake, secret-name-safe form of an identifier (non-alnum → _). | |
| std::string | reload_github_secret_name (KeyRole role, std::string_view plugin_id, int generation) |
| GitHub Actions secret name, same shape as the keychain id but secret-safe: PULP_RELOAD_<ROLE>_KEY_<PLUGIN>_GEN<N>. | |
| std::string | normalize_github_repo (std::string_view repo) |
| Normalize a repo string (owner/name, URL, or scp-style; strips .git + host) to lowercase owner/name, or "" if it has no clean owner/name shape. | |
| bool | github_backup_repo_allowed (std::string_view repo) |
| A signing key may be published as a GitHub Actions secret ONLY into a plugin's own repository — never the core Pulp repo, and never an empty/malformed target. | |
| std::string | swap_pack_signing_summary (const SwapPackManifest &m) |
| A readable summary of everything the signature will cover, so the signer can confirm exactly what they are vouching for before a key touches it. | |
| std::string | key_generation_banner (KeyRole role, std::string_view keychain_service, std::string_view plugin_id) |
| The loud, no-surprise banner shown when Pulp generates a key the developer did not already have. | |
| bool | param_contract_equal (const state::ParamInfo &a, const state::ParamInfo &b) |
True iff a and b describe the same normalization + interpretation contract for one parameter. | |
| bool | has_unique_param_ids (const state::StateStore &store) |
True iff store registers each ParamID at most once. | |
| std::vector< std::string > | param_contract_diff (const state::StateStore &live, const state::StateStore &candidate) |
| Human-readable contract differences (diagnostics). | |
| std::size_t | contract_param_count (const state::StateStore &store) |
| Count parameters that are part of the swap contract (i.e. | |
| bool | param_contracts_match (const state::StateStore &live, const state::StateStore &candidate) |
| The gate: the candidate's parameter contract is equivalent to the live one (same set of IDs, same range/flags per ID). | |
| ParamSupersetResult | param_contract_superset (const state::StateStore &live, const state::StateStore &candidate) |
| Superset gate: relaxes param_contracts_match from "identical" to "candidate
is a superset of live". | |
| std::size_t | carry_state (const state::StateStore &live, state::StateStore &candidate) |
Copy each live parameter value into candidate for IDs the candidate also defines, so a hot-swap preserves the current sound. | |
| bool | staged_pid_is_alive (long pid) |
| void | reap_stale_staged (const std::filesystem::path &dir, const std::string &stem) |
| void | promote_host_symbols_once () |
| Promote the HOST image's symbols to global loader scope, once per process | |
| std::optional< ReloadOutcome > | verify_pack_before_load (const SwapPackTrust &trust, const std::string &library_path) |
Verify trust on the RAW bytes on disk and confirm library_path is a member of the verified pack — the strict pre-dlopen trust phase. | |
| std::variant< GatedImage, ReloadOutcome > | gate_logic_image (const std::string &library_path, const BuildFingerprint &host_fingerprint, const SwapPackTrust *trust=nullptr) |
Load library_path and run the pre-construction gates — reload-ABI version, then build fingerprint — and resolve the create symbol. | |
| ReloadOutcome | reload_processor_from_library (ProcessorHotSwapSlot &slot, const std::string &library_path, const BuildFingerprint &host_fingerprint, state::StateStore &live_store, const PrepareContext &prepare_ctx, std::vector< ReloadLibrary > &retained_images, const SwapPackTrust *trust=nullptr) |
Attempt to hot-reload slot from the logic library at library_path. | |
| std::filesystem::path | reload_sidecar_manifest_path (const std::filesystem::path &logic) |
| Sidecar manifest path for a watched logic file. | |
| ReloadTrustDecision | resolve_reload_trust (const std::filesystem::path &logic_path, const ReloadTrustPolicy &policy) |
| RemoteUpdateResult | evaluate_remote_ux_update (const std::filesystem::path &pack_root, const SwapPackManifest &m, const std::vector< std::uint8_t > &trusted_public_key, std::uint64_t installed_pack_version, const view::CapabilitySet &host_granted, bool srl_fresh, const SignedRevocationList *srl) |
| std::string | url_query_escape (std::string_view s) |
| Percent-encode a query value (RFC 3986 unreserved kept; everything else escaped), so a plugin id / channel with odd characters cannot break out of the query. | |
| std::string | build_update_check_url (const std::string &base_url, const std::string &plugin_id, const std::string &channel, std::uint64_t installed_version) |
| The privacy-preserving update-check URL: base + only (plugin, channel, installed version). | |
| bool | is_safe_pack_member_path (const std::string &p) |
True only if p is safe to join onto a download directory, per runtime::is_safe_relative_path (no rooted form, no ..). | |
| RemoteFetchResult | check_and_fetch_remote_ux_update (const RemoteUpdateConfig &cfg, bool srl_fresh, const SignedRevocationList *srl) |
| Orchestrate one opt-in remote-update check. | |
| std::function< FetchedPack(const std::string &)> | make_http_pack_fetcher (std::string files_base_url, std::filesystem::path download_dir, int timeout_seconds=20) |
Convenience default transport over runtime::http_*: GET the manifest, then download each declared file from <files_base_url>/<relative path> into download_dir. | |
| std::string | srl_to_lower (std::string s) |
| Lowercase a hex/ASCII string for case-insensitive fingerprint/hash compares. | |
| std::optional< std::vector< std::uint8_t > > | srl_hex_decode (std::string_view hex) |
| Decode a hex string to bytes. | |
| std::string | srl_hex_encode (const std::vector< std::uint8_t > &bytes) |
| Encode bytes to lowercase hex. | |
| std::vector< std::uint8_t > | srl_signed_message (const SignedRevocationList &srl) |
| Deterministic canonical payload the SRL signature covers. | |
| std::optional< SignedRevocationList > | parse_srl (std::string_view json, std::string &error) |
| Parse an SRL from JSON. | |
| std::string | serialize_srl_json (const SignedRevocationList &srl) |
| Serialize an SRL to a stable, canonical JSON string (fixed field order). | |
| SrlVerifyResult | verify_srl (const SignedRevocationList &srl, const std::vector< std::uint8_t > &trusted_revocation_pubkey) |
Verify the SRL's Ed25519 signature against trusted_revocation_pubkey. | |
| SrlVerifyResult | parse_and_verify_srl (std::string_view json, const std::vector< std::uint8_t > &trusted_revocation_pubkey, SignedRevocationList &out) |
| Parse + verify in one fail-closed step. A parse failure maps to ParseError. | |
| bool | is_revoked (const SignedRevocationList &srl, std::string_view signer_key_fpr, std::string_view artifact_hash) |
True iff signer_key_fpr is a revoked signer OR artifact_hash is a revoked artifact in srl. | |
| SwapPackKind | swap_pack_kind_from_string (std::string_view s) |
| const char * | swap_pack_kind_to_string (SwapPackKind k) |
| Stable canonical spelling of a kind, for the signed message (never localized / reformatted). | |
| std::optional< std::vector< std::uint8_t > > | swap_pack_hex_decode (std::string_view hex) |
| Decode a lowercase/uppercase hex string to bytes. | |
| std::optional< SwapPackManifest > | parse_swap_pack_manifest (std::string_view json, std::string &error) |
| Parse a swap-pack manifest from JSON. | |
| SwapPackVerifyResult | verify_swap_pack_integrity (const std::filesystem::path &root, const SwapPackManifest &manifest) |
Verify every declared file exists under root and its bytes hash to the declared SHA-256. | |
| std::vector< std::uint8_t > | swap_pack_signed_message (const SwapPackManifest &m) |
| Deterministic canonical payload the pack signature covers. | |
| SwapPackVerifyResult | verify_swap_pack_signature (const SwapPackManifest &manifest, const std::vector< std::uint8_t > &trusted_public_key) |
| Verify the pack's Ed25519 signature. | |
| SwapPackVerifyResult | verify_swap_pack (const std::filesystem::path &root, const SwapPackManifest &manifest, const std::vector< std::uint8_t > &trusted_public_key) |
| Full fail-closed verify: signature FIRST (binds the file hashes without reading the files), THEN per-file integrity (the files match the signed hashes). | |
| std::string | swap_pack_hex_encode (const std::vector< std::uint8_t > &bytes) |
| Lowercase-hex encode bytes (for signer/signature fields on the way out). | |
| std::string | swap_pack_json_escape (std::string_view s) |
| Minimal JSON string escaper for the fields the manifest emits. | |
| std::string | serialize_swap_pack_manifest (const SwapPackManifest &m) |
| Serialize a manifest to JSON that parse_swap_pack_manifest reads back verbatim (round-trips). | |
Variables | |
| constexpr uint32_t | kBuildFingerprintSchema = 1 |
| Bump when the fingerprint layout/semantics change. | |
| constexpr std::string_view | kKeyBlobMagic = "PULP-RELOAD-KEY-v1" |
| constexpr int | kReloadAbiVersion = 1 |
| Reload entry-point ABI version. | |
| constexpr const char * | kAbiVersionSymbol = "pulp_reload_abi_version" |
| constexpr const char * | kFingerprintSymbol = "pulp_reload_fingerprint" |
| constexpr const char * | kCreateSymbol = "pulp_reload_create" |
| constexpr const char * | kDestroySymbol = "pulp_reload_destroy" |
The crossfade amplitude law is the shared signal::TransitionMixer primitive — extracted so the hot-swap slot, the convolver IR swapper, and the Phase-2 SwapUnit transitions all fade identically and are covered by one click-free fixture.
Smoothstep = equal-gain (default), EqualPower = constant power; both click-free (evaluated over the smoothstep ramp). See transition_mixer.hpp.
| using pulp::format::reload::ReloadAbiVersionFn = int (*)() |
| using pulp::format::reload::ReloadCreateFn = Processor* (*)() |
Constructs the logic Processor; the caller takes ownership of the result.
| using pulp::format::reload::ReloadDestroyFn = void (*)(Processor*) |
Destroys a Processor returned by ReloadCreateFn, via the logic image's operator delete (reserved; see kDestroySymbol).
| using pulp::format::reload::ReloadFingerprintFn = void (*)(BuildFingerprint* out) |
Fills *out with the logic library's own BuildFingerprint.
| using pulp::format::reload::ReloadTrustDecision = std::variant<std::monostate, SwapPackTrust, ReloadOutcome> |
The three outcomes of applying the policy to logic_path:
|
strong |
|
strong |
|
strong |
|
strong |
| Enumerator | |
|---|---|
| Disabled | opt-in feature is off (default) — no network is touched |
| Applied | a newer signed pack passed the gate and was installed |
| UpToDate | reachable, but nothing newer to apply (server offered <= installed) |
| Rejected | a pack was returned but the gate refused it (a security event) |
| Unavailable | offline / unreachable / timeout / malformed transport — keep current UI |
|
strong |
|
strong |
Outcome of offering an SRL to the store.
|
strong |
|
strong |
|
strong |
|
inline |
Apply stages as one transaction.
On the first stage that returns false or throws, the already-applied stages are rolled back in reverse and the failing stage's name/detail is reported. Returns ok=true only if EVERY stage applied.
|
inline |
Run an ordered set of SwapUnits as one transaction: collect each unit's stage (snapshotting current state), then delegate to apply_live_swap.
Order units easiest-rollback-first (UX before the DSP slot swap). Null entries are skipped.
|
inline |
Walk bundle_dir, hash each regular file into a manifest entry (path relative to the bundle root, POSIX separators), and infer declared_capabilities by running autocaps over the concatenated JS.
Files are sorted for a deterministic manifest (identical bundles produce identical content ids). Rejects symlinks.
|
inline |
The privacy-preserving update-check URL: base + only (plugin, channel, installed version).
Nothing identifying is added. Pure + testable.
|
inline |
Copy each live parameter value into candidate for IDs the candidate also defines, so a hot-swap preserves the current sound.
Returns the number of values carried. Caller should gate this on param_contracts_match(); it is safe to call regardless (it simply skips IDs the candidate lacks).
NOTE: this is the ALTERNATE state-continuity model, for hosts that give each processor its OWN StateStore. The canonical standalone-shell path (reload_transaction.hpp) instead binds the new processor directly to the single live store and does not call this — see that file's header.
|
inline |
Orchestrate one opt-in remote-update check.
Fail-closed + offline-graceful: disabled → no network; non-HTTPS or no fetcher → Unavailable; unreachable → Unavailable (+ optional offline fallback); reachable → parse → gate → (on accept) content-addressed install. srl_fresh / srl gate revocation freshness exactly as the pure gate requires (a remote fetch fails closed without fresh revocation).
|
inline |
Count parameters that are part of the swap contract (i.e.
excluding adapter-synthesized bypass params — see param_contract_diff).
|
inline |
Capture THIS translation unit's build fingerprint.
Header-inline so the shell and the logic dylib each record their own compiler's macros.
|
inline |
| srl_fresh | true only if the caller obtained/confirmed a current revocation list for THIS fetch. Remote updates fail closed when it is false — a withheld SRL must never let a since-revoked pack slip through. |
| srl | the fresh revocation list (may be null only when srl_fresh is false, which already rejects). |
|
inline |
Human-readable per-field differences (diagnostics only). Empty == identical.
|
inline |
Field-by-field equality (the gate).
Compares POD fields; ignores trailing padding (so a struct memcmp can't false-fail on uninitialized pad bytes).
|
inline |
Load library_path and run the pre-construction gates — reload-ABI version, then build fingerprint — and resolve the create symbol.
The SINGLE source of truth for that gate ordering, shared by reload_processor_from_library() and ReloadableShell's initial load so they can never disagree about what "compatible" means. Returns the GatedImage on success (caller retains lib + calls create), or a fail-closed ReloadOutcome on rejection (the image backed no C++ object, so it is set CloseOnDestroy and unloaded rather than leaked).
When trust is non-null the swap-pack signature + integrity (+ revocation hook) are verified on the RAW bytes BEFORE the dlopen — dlopen runs a native image's static constructors, so trust MUST precede load. When null (the unsigned local-dev default) the image is loaded directly, unchanged.
|
inline |
A signing key may be published as a GitHub Actions secret ONLY into a plugin's own repository — never the core Pulp repo, and never an empty/malformed target.
Fail closed: when unsure, refuse rather than risk leaking a key into the wrong repo.
|
inline |
True iff store registers each ParamID at most once.
A duplicate-ID store is a plugin authoring bug; the contract gate fails closed on it (below) rather than risk a multiset mismatch slipping through.
|
inline |
Verify source_root against trusted_public_key, then install the pack's files into install_base/<content-id>/ as read-only, rejecting symlinks and (for consumer packs) unknown kinds.
Idempotent: an already-installed content id is re-verified and reused. On Ok, installed_root is the immutable directory the loader must read from. Fail-closed: nothing is published unless the STAGED bytes re-verify.
|
inline |
True iff signer_key_fpr is a revoked signer OR artifact_hash is a revoked artifact in srl.
Comparison is case-insensitive (lowercase hex). An empty query field is not matched (skips that dimension). Callers should verify the SRL (or load a previously-verified one) before trusting this.
|
inline |
True only if p is safe to join onto a download directory, per runtime::is_safe_relative_path (no rooted form, no ..).
The manifest is the untrusted server's word (its signature is verified only later), so a fetcher MUST screen every declared file path with this before writing it, or a hostile ../../x escapes the download directory.
|
inline |
The loud, no-surprise banner shown when Pulp generates a key the developer did not already have.
Names what was created, where it lives, and the exact consequences — so a key is never a silent surprise.
|
inline |
|
inline |
Load the key at path, or generate a fresh keypair, write it there (0600), and return it with generated=true so the caller can scream.
Never regenerates an existing key. Returns nullopt only on generation failure or an unreadable/corrupt existing file (fail closed rather than silently minting a second identity).
|
inline |
Convenience default transport over runtime::http_*: GET the manifest, then download each declared file from <files_base_url>/<relative path> into download_dir.
Unreachable/non-2xx → reachable=false (offline). Override with your own fetcher for a different layout, auth, or CDN.
|
inline |
Normalize a repo string (owner/name, URL, or scp-style; strips .git + host) to lowercase owner/name, or "" if it has no clean owner/name shape.
|
inline |
|
inline |
Human-readable contract differences (diagnostics).
Empty == equivalent. Reports parameters only in live (removed), only in candidate (added), and IDs present in both whose contract changed. Order-independent (keyed by ID).
|
inline |
True iff a and b describe the same normalization + interpretation contract for one parameter.
Ranges are author-specified literals, so exact float equality is intended — a changed bound must be caught.
|
inline |
Superset gate: relaxes param_contracts_match from "identical" to "candidate is a superset of live".
A strict match is the special case is_superset==true && added_ids.empty(). When is_superset is true with non-empty added_ids, the reload can hot-swap AND the host must then rescan the parameter list to pick up the additions (the live-store registration + per-adapter host-notify the check is pure and mutates nothing). Fails closed on duplicate IDs, exactly like the strict gate.
|
inline |
The gate: the candidate's parameter contract is equivalent to the live one (same set of IDs, same range/flags per ID).
True == safe to hot-swap and carry state; false == requires a full reload.
|
inline |
Parse + verify in one fail-closed step. A parse failure maps to ParseError.
|
inline |
Parse a key blob.
Returns nullopt on a bad magic line or non-base64 fields (fail closed — a malformed key never silently yields empty material).
|
inline |
Parse an SRL from JSON.
Fail-closed (returns nullopt, writes error) on any structural problem. Required: integer epoch. Optional: schema_version (default 1), issued_utc, revoked_signer_key_fprs / revoked_artifact_hashes (arrays of strings, default empty), signer / signature (hex strings). A malformed hex signer/signature is left empty so verification fails closed rather than parsing to garbage bytes.
|
inline |
Parse a swap-pack manifest from JSON.
Returns nullopt on any structural problem (missing/mistyped required field), writing a reason to error. Required: string id, string plugin_id, array files of {string path, string sha256, string kind}. format_version defaults to 1.
|
inline |
Promote the HOST image's symbols to global loader scope, once per process
A DAW loads a plugin bundle (.vst3/.component/.clap) with RTLD_LOCAL, so the bundle's pulp::* SDK symbols are NOT in the global scope that thin (RESOLVE_FROM_HOST) reload logic binds against at dlopen — the thin image then fails to load with "symbol not found in flat namespace". Re-opening the host image (the bundle that contains THIS code, located via dladdr) with RTLD_GLOBAL | RTLD_NOLOAD promotes its already-loaded symbols into global scope WITHOUT reloading it, so subsequently-dlopened thin logic resolves pulp::* via flat-namespace lookup. Verified by the P0.1 spike: the baseline RTLD_LOCAL load fails; after this promotion the thin image binds.
Harmless for the static-link reload model (that logic image has no undefined pulp::* symbols to resolve) and for ordinary two-level-namespace plugins (only images doing flat-namespace lookup — i.e. thin reload logic — consult the promoted symbols). No-op on Windows (the static model is used there).
DEV-ONLY: the thin-logic dlopen path is compiled out of shipping builds . Multi-instance caveat (dev-time only): two DIFFERENT Pulp plugins reloading thin logic in the SAME host would share one global set of pulp::* symbols; that is a niche dev scenario and the shared symbols are the same SDK build, so stateless SDK calls are unaffected (only process-global SDK singletons could cross-talk). Documented as a dev-loop limitation.
|
inline |
|
inline |
GitHub Actions secret name, same shape as the keychain id but secret-safe: PULP_RELOAD_<ROLE>_KEY_<PLUGIN>_GEN<N>.
|
inline |
Keychain account/label carrying the generation and a short fingerprint prefix, e.g.
gen1-ab12cd34. Old generations keep distinct accounts so a rotation never overwrites the previous key in place (it stays importable during a transition).
|
inline |
Keychain service id: pulp.reload.
<role>.<plugin_id>. Listing pulp.reload.* finds every Pulp key; role + plugin make the exact item obvious.
|
inline |
Attempt to hot-reload slot from the logic library at library_path.
Stateless core — see ReloadSession for the owning convenience wrapper.
| slot | the live RT-safe slot to swap into. |
| library_path | path to the candidate logic shared library. |
| host_fingerprint | the shell's own build fingerprint (compare target). |
| live_store | the live parameter store; the new processor binds to it (must outlive the swapped-in processor). |
| prepare_ctx | prepare() arguments (sample rate / block size). |
| retained_images | images that backed a constructed processor are appended here and kept alive for the process lifetime; images rejected before any construction are unloaded immediately. |
| trust | optional swap-pack trust material; when non-null the signed pack is verified on the RAW bytes BEFORE the dlopen. Null = unsigned local-dev load, unchanged. |
|
inline |
Sidecar manifest path for a watched logic file.
|
inline |
|
inline |
Serialize a keypair to a text blob: magic line, base64 public, base64 private.
|
inline |
Serialize an SRL to a stable, canonical JSON string (fixed field order).
Used as the on-disk offline-last-known form; round-trips through parse_srl.
|
inline |
Serialize a manifest to JSON that parse_swap_pack_manifest reads back verbatim (round-trips).
Policy fields + signer/signature included; the field order mirrors the struct for readability (parse is order-independent).
|
inline |
Decode a hex string to bytes.
Returns nullopt on odd length or a non-hex digit (fail-closed — a malformed key/sig never verifies).
|
inline |
Encode bytes to lowercase hex.
|
inline |
Deterministic canonical payload the SRL signature covers.
Every variable-length field is LENGTH-PREFIXED (u32 LE) and every count is bound, so the encoding is unambiguous — no field value (even one containing the magic or a newline) can be reinterpreted to collide with a different SRL. The signer key + signature fields are NOT bound (Ed25519 already binds the message to the verifying key; a signature cannot cover itself). Fingerprints and hashes are lowercased so sign/verify agree regardless of input case.
Field order is fixed: magic, schema_version, epoch, issued_utc, then the two revocation lists (count-prefixed). Order MUST match between signer + verifier.
|
inline |
Lowercase a hex/ASCII string for case-insensitive fingerprint/hash compares.
|
inline |
|
inline |
The content id is the SHA-256 of the pack's signed message — every signed field (id, plugin, version, policy, per-file hash+kind) feeds it, so two byte-identical packs collide (dedup) and any change yields a new dir.
Signature bytes are excluded, so re-signing the same content installs to the same place.
|
inline |
Decode a lowercase/uppercase hex string to bytes.
Returns nullopt on an odd length or a non-hex digit (fail-closed — a malformed key/sig never verifies).
|
inline |
Lowercase-hex encode bytes (for signer/signature fields on the way out).
|
inline |
Minimal JSON string escaper for the fields the manifest emits.
|
inline |
|
inline |
Stable canonical spelling of a kind, for the signed message (never localized / reformatted).
Must round-trip with swap_pack_kind_from_string.
|
inline |
Deterministic canonical payload the pack signature covers.
Binds pack identity, target plugin, format version, and every file's path + hash + kind — so a tampered file hash OR a re-pointed/re-kinded file breaks the signature. Independent of JSON formatting (re-serialization can't break it).
Every variable-length field is LENGTH-PREFIXED (u32 LE) and the file count is bound, so the encoding is unambiguous: unlike a newline-delimited join, no field value (even one containing newlines or the "files" label) can be reinterpreted to collide with a different manifest. The signature field itself is NOT bound. (Hardening over the node_pack '
'-join scheme — review 3.1b.)
|
inline |
A readable summary of everything the signature will cover, so the signer can confirm exactly what they are vouching for before a key touches it.
|
inline |
Upper-snake, secret-name-safe form of an identifier (non-alnum → _).
|
inline |
Percent-encode a query value (RFC 3986 unreserved kept; everything else escaped), so a plugin id / channel with odd characters cannot break out of the query.
|
inline |
Verify trust on the RAW bytes on disk and confirm library_path is a member of the verified pack — the strict pre-dlopen trust phase.
Returns std::nullopt when the pack is trustworthy AND library_path is one of its verified files (safe to load those exact bytes); otherwise a fail-closed ReloadOutcome. Ordering mirrors swap_pack::verify_swap_pack — authenticate the signature first (it binds every file hash), THEN re-hash the files, THEN the revocation hook, THEN the loaded-file membership check. No dlopen happens here or anywhere upstream of a passing result.
|
inline |
Verify the SRL's Ed25519 signature against trusted_revocation_pubkey.
Fail-closed: the signer key must be exactly the trusted revocation key (UntrustedSigner otherwise), and the detached signature must authenticate the canonical message (BadSignature otherwise, including any malformed size).
|
inline |
Full fail-closed verify: signature FIRST (binds the file hashes without reading the files), THEN per-file integrity (the files match the signed hashes).
Order mirrors node_pack: authenticate before touching content.
This is the ONLY sanctioned "is this pack trustworthy?" entry point. The install path (3.1c) must call this — never verify_swap_pack_integrity alone (that proves the files match the manifest, NOT that the manifest is signed).
|
inline |
Verify every declared file exists under root and its bytes hash to the declared SHA-256.
Fail-closed: the first problem wins. Control thread (reads files + hashes); does not install or mutate anything.
|
inline |
Verify the pack's Ed25519 signature.
Fails closed on: a signer key that is not trusted_public_key (UntrustedSigner), or a malformed/inauthentic signature (BadSignature). Does NOT touch files — pair with verify_swap_pack_integrity for the hash layer (or use verify_swap_pack).
|
inlineconstexpr |
|
inlineconstexpr |
Bump when the fingerprint layout/semantics change.
|
inlineconstexpr |
|
inlineconstexpr |
|
inlineconstexpr |
|
inlineconstexpr |
|
inlineconstexpr |
Reload entry-point ABI version.
Bump when the exported symbol set or their signatures change (NOT for C++-ABI changes — that is the build fingerprint).