Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::format::reload Namespace Reference

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"

Typedef Documentation

◆ CrossfadeCurve

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.

◆ ReloadAbiVersionFn

◆ ReloadCreateFn

Constructs the logic Processor; the caller takes ownership of the result.

◆ ReloadDestroyFn

Destroys a Processor returned by ReloadCreateFn, via the logic image's operator delete (reserved; see kDestroySymbol).

◆ ReloadFingerprintFn

Fills *out with the logic library's own BuildFingerprint.

◆ ReloadTrustDecision

The three outcomes of applying the policy to logic_path:

  • monostate → load without trust (policy off; existing dev behavior).
  • SwapPackTrust → verify against this before loading (pass to gate_logic_image).
  • ReloadOutcome → refuse (policy on but no valid signed sidecar).

Enumeration Type Documentation

◆ KeyRole

enum class pulp::format::reload::KeyRole
strong
Enumerator
Signing 
Revocation 

◆ LeakPolicy

What the handle does with the loaded image when it is destroyed.

Enumerator
Retain 

Never unload (default). Safe for live reload — see file header.

CloseOnDestroy 

dlclose on destruction. Only when the image is provably quiesced.

◆ PackInstall

Enumerator
Ok 
NotTrusted 

verify_swap_pack failed (bad signature or integrity)

SymlinkRejected 

a declared file is a symlink or escapes the pack root

UnknownKind 

a declared file has an unrecognized kind (consumer packs)

IoError 

staging copy / rename / permission set failed

◆ RemoteFetchStatus

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

◆ RemoteUpdateVerdict

Enumerator
Accept 
RejectSignature 

bad signature or integrity (untrusted/tampered)

RejectKind 

not a UX (ui-script) pack — wrong lane

RejectStaleRevocation 

no fresh revocation info for a remote fetch

RejectRevoked 

signer key or artifact is on the revocation list

RejectDowngrade 

pack_version <= the installed version

RejectCapabilityEscalation 

requests a capability the host did not grant

◆ SrlAcceptance

Outcome of offering an SRL to the store.

Enumerator
Accepted 

epoch is strictly newer than the floor; SRL persisted as last-known

RejectedRollback 

epoch <= stored floor — replay/rollback of an older SRL, refused

FloorCorrupt 

a floor file exists but is unreadable/garbage — refuse (fail-closed)

PersistError 

could not atomically write the new last-known SRL

◆ SrlVerify

Enumerator
Ok 
ParseError 

JSON malformed or a required field missing/mistyped.

UntrustedSigner 

the SRL's signer key is not the trusted revocation key

BadSignature 

Ed25519 verification failed (or malformed sig/key sizes).

◆ SwapPackKind

Payload kind for one file in a swap pack.

WasmDsp is reserved (the Phase-4 lane); Unknown is any unrecognized kind (accepted structurally but the installer/registry decides whether to honor it).

Enumerator
UiScript 
DspGraph 
NodePack 
WasmDsp 
Unknown 

◆ SwapPackVerify

Enumerator
Ok 
ManifestParseError 

manifest JSON malformed or missing required fields

MissingFile 

a declared file is absent under the pack root

ReadError 

a declared file could not be read

HashMismatch 

a file's SHA-256 != the manifest's declared hash

UntrustedSigner 

the manifest's signer key is not the trusted key

BadSignature 

Ed25519 verification failed (or malformed sig/key sizes).

Function Documentation

◆ apply_live_swap() [1/2]

LiveSwapResult pulp::format::reload::apply_live_swap ( const std::vector< SwapStage > & stages)
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.

◆ apply_live_swap() [2/2]

LiveSwapResult pulp::format::reload::apply_live_swap ( const std::vector< SwapUnit * > & units)
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.

◆ build_signable_manifest()

PackBuildResult pulp::format::reload::build_signable_manifest ( const std::filesystem::path & bundle_dir,
const std::string & plugin_id,
std::uint64_t pack_version,
const std::string & update_channel )
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.

◆ build_update_check_url()

std::string pulp::format::reload::build_update_check_url ( const std::string & base_url,
const std::string & plugin_id,
const std::string & channel,
std::uint64_t installed_version )
inline

The privacy-preserving update-check URL: base + only (plugin, channel, installed version).

Nothing identifying is added. Pure + testable.

◆ carry_state()

std::size_t pulp::format::reload::carry_state ( const state::StateStore & live,
state::StateStore & candidate )
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.

◆ check_and_fetch_remote_ux_update()

RemoteFetchResult pulp::format::reload::check_and_fetch_remote_ux_update ( const RemoteUpdateConfig & cfg,
bool srl_fresh,
const SignedRevocationList * srl )
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).

◆ contract_param_count()

std::size_t pulp::format::reload::contract_param_count ( const state::StateStore & store)
inline

Count parameters that are part of the swap contract (i.e.

excluding adapter-synthesized bypass params — see param_contract_diff).

◆ current_build_fingerprint()

BuildFingerprint pulp::format::reload::current_build_fingerprint ( )
inline

Capture THIS translation unit's build fingerprint.

Header-inline so the shell and the logic dylib each record their own compiler's macros.

◆ evaluate_remote_ux_update()

RemoteUpdateResult pulp::format::reload::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 )
inline
Parameters
srl_freshtrue 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.
srlthe fresh revocation list (may be null only when srl_fresh is false, which already rejects).

◆ fingerprint_diff()

std::vector< std::string > pulp::format::reload::fingerprint_diff ( const BuildFingerprint & shell,
const BuildFingerprint & logic )
inline

Human-readable per-field differences (diagnostics only). Empty == identical.

◆ fingerprints_match()

bool pulp::format::reload::fingerprints_match ( const BuildFingerprint & a,
const BuildFingerprint & b )
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).

◆ gate_logic_image()

std::variant< GatedImage, ReloadOutcome > pulp::format::reload::gate_logic_image ( const std::string & library_path,
const BuildFingerprint & host_fingerprint,
const SwapPackTrust * trust = nullptr )
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.

◆ github_backup_repo_allowed()

bool pulp::format::reload::github_backup_repo_allowed ( std::string_view repo)
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.

◆ has_unique_param_ids()

bool pulp::format::reload::has_unique_param_ids ( const state::StateStore & store)
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.

◆ install_verified_pack()

PackInstallResult pulp::format::reload::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 )
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.

◆ is_revoked()

bool pulp::format::reload::is_revoked ( const SignedRevocationList & srl,
std::string_view signer_key_fpr,
std::string_view artifact_hash )
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.

◆ is_safe_pack_member_path()

bool pulp::format::reload::is_safe_pack_member_path ( const std::string & p)
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.

◆ key_generation_banner()

std::string pulp::format::reload::key_generation_banner ( KeyRole role,
std::string_view keychain_service,
std::string_view plugin_id )
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.

◆ key_role_name()

std::string_view pulp::format::reload::key_role_name ( KeyRole r)
inline

◆ load_or_generate_key_file()

std::optional< KeyMaterial > pulp::format::reload::load_or_generate_key_file ( const std::filesystem::path & path,
bool & generated )
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).

◆ make_http_pack_fetcher()

std::function< FetchedPack(const std::string &)> pulp::format::reload::make_http_pack_fetcher ( std::string files_base_url,
std::filesystem::path download_dir,
int timeout_seconds = 20 )
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.

◆ normalize_github_repo()

std::string pulp::format::reload::normalize_github_repo ( std::string_view repo)
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.

◆ pack_read_all_bytes()

std::string pulp::format::reload::pack_read_all_bytes ( const std::filesystem::path & p)
inline

◆ param_contract_diff()

std::vector< std::string > pulp::format::reload::param_contract_diff ( const state::StateStore & live,
const state::StateStore & candidate )
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).

◆ param_contract_equal()

bool pulp::format::reload::param_contract_equal ( const state::ParamInfo & a,
const state::ParamInfo & b )
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.

◆ param_contract_superset()

ParamSupersetResult pulp::format::reload::param_contract_superset ( const state::StateStore & live,
const state::StateStore & candidate )
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.

◆ param_contracts_match()

bool pulp::format::reload::param_contracts_match ( const state::StateStore & live,
const state::StateStore & candidate )
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.

◆ parse_and_verify_srl()

SrlVerifyResult pulp::format::reload::parse_and_verify_srl ( std::string_view json,
const std::vector< std::uint8_t > & trusted_revocation_pubkey,
SignedRevocationList & out )
inline

Parse + verify in one fail-closed step. A parse failure maps to ParseError.

◆ parse_key_blob()

std::optional< KeyMaterial > pulp::format::reload::parse_key_blob ( std::string_view blob)
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).

◆ parse_srl()

std::optional< SignedRevocationList > pulp::format::reload::parse_srl ( std::string_view json,
std::string & error )
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.

◆ parse_swap_pack_manifest()

std::optional< SwapPackManifest > pulp::format::reload::parse_swap_pack_manifest ( std::string_view json,
std::string & error )
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.

◆ promote_host_symbols_once()

void pulp::format::reload::promote_host_symbols_once ( )
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.

◆ reap_stale_staged()

void pulp::format::reload::reap_stale_staged ( const std::filesystem::path & dir,
const std::string & stem )
inline

◆ reload_github_secret_name()

std::string pulp::format::reload::reload_github_secret_name ( KeyRole role,
std::string_view plugin_id,
int generation )
inline

GitHub Actions secret name, same shape as the keychain id but secret-safe: PULP_RELOAD_<ROLE>_KEY_<PLUGIN>_GEN<N>.

◆ reload_key_account()

std::string pulp::format::reload::reload_key_account ( int generation,
std::string_view fpr_hex )
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).

◆ reload_keychain_service()

std::string pulp::format::reload::reload_keychain_service ( KeyRole role,
std::string_view plugin_id )
inline

Keychain service id: pulp.reload.

<role>.<plugin_id>. Listing pulp.reload.* finds every Pulp key; role + plugin make the exact item obvious.

◆ reload_processor_from_library()

ReloadOutcome pulp::format::reload::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 )
inline

Attempt to hot-reload slot from the logic library at library_path.

Stateless core — see ReloadSession for the owning convenience wrapper.

Parameters
slotthe live RT-safe slot to swap into.
library_pathpath to the candidate logic shared library.
host_fingerprintthe shell's own build fingerprint (compare target).
live_storethe live parameter store; the new processor binds to it (must outlive the swapped-in processor).
prepare_ctxprepare() arguments (sample rate / block size).
retained_imagesimages that backed a constructed processor are appended here and kept alive for the process lifetime; images rejected before any construction are unloaded immediately.
trustoptional 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.
Returns
an outcome; only Status::Swapped means the slot changed.

◆ reload_sidecar_manifest_path()

std::filesystem::path pulp::format::reload::reload_sidecar_manifest_path ( const std::filesystem::path & logic)
inline

Sidecar manifest path for a watched logic file.

◆ resolve_reload_trust()

ReloadTrustDecision pulp::format::reload::resolve_reload_trust ( const std::filesystem::path & logic_path,
const ReloadTrustPolicy & policy )
inline

◆ serialize_key_blob()

std::string pulp::format::reload::serialize_key_blob ( const KeyMaterial & k)
inline

Serialize a keypair to a text blob: magic line, base64 public, base64 private.

◆ serialize_srl_json()

std::string pulp::format::reload::serialize_srl_json ( const SignedRevocationList & srl)
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.

◆ serialize_swap_pack_manifest()

std::string pulp::format::reload::serialize_swap_pack_manifest ( const SwapPackManifest & m)
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).

◆ srl_hex_decode()

std::optional< std::vector< std::uint8_t > > pulp::format::reload::srl_hex_decode ( std::string_view hex)
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).

◆ srl_hex_encode()

std::string pulp::format::reload::srl_hex_encode ( const std::vector< std::uint8_t > & bytes)
inline

Encode bytes to lowercase hex.

◆ srl_signed_message()

std::vector< std::uint8_t > pulp::format::reload::srl_signed_message ( const SignedRevocationList & srl)
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.

◆ srl_to_lower()

std::string pulp::format::reload::srl_to_lower ( std::string s)
inline

Lowercase a hex/ASCII string for case-insensitive fingerprint/hash compares.

◆ staged_pid_is_alive()

bool pulp::format::reload::staged_pid_is_alive ( long pid)
inline

◆ swap_pack_content_id()

std::string pulp::format::reload::swap_pack_content_id ( const SwapPackManifest & m)
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.

◆ swap_pack_hex_decode()

std::optional< std::vector< std::uint8_t > > pulp::format::reload::swap_pack_hex_decode ( std::string_view hex)
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).

◆ swap_pack_hex_encode()

std::string pulp::format::reload::swap_pack_hex_encode ( const std::vector< std::uint8_t > & bytes)
inline

Lowercase-hex encode bytes (for signer/signature fields on the way out).

◆ swap_pack_json_escape()

std::string pulp::format::reload::swap_pack_json_escape ( std::string_view s)
inline

Minimal JSON string escaper for the fields the manifest emits.

◆ swap_pack_kind_from_string()

SwapPackKind pulp::format::reload::swap_pack_kind_from_string ( std::string_view s)
inline

◆ swap_pack_kind_to_string()

const char * pulp::format::reload::swap_pack_kind_to_string ( SwapPackKind k)
inline

Stable canonical spelling of a kind, for the signed message (never localized / reformatted).

Must round-trip with swap_pack_kind_from_string.

◆ swap_pack_signed_message()

std::vector< std::uint8_t > pulp::format::reload::swap_pack_signed_message ( const SwapPackManifest & m)
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.)

◆ swap_pack_signing_summary()

std::string pulp::format::reload::swap_pack_signing_summary ( const SwapPackManifest & m)
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.

◆ to_secret_token()

std::string pulp::format::reload::to_secret_token ( std::string_view s)
inline

Upper-snake, secret-name-safe form of an identifier (non-alnum → _).

◆ url_query_escape()

std::string pulp::format::reload::url_query_escape ( std::string_view s)
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.

◆ verify_pack_before_load()

std::optional< ReloadOutcome > pulp::format::reload::verify_pack_before_load ( const SwapPackTrust & trust,
const std::string & library_path )
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.

◆ verify_srl()

SrlVerifyResult pulp::format::reload::verify_srl ( const SignedRevocationList & srl,
const std::vector< std::uint8_t > & trusted_revocation_pubkey )
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).

◆ verify_swap_pack()

SwapPackVerifyResult pulp::format::reload::verify_swap_pack ( const std::filesystem::path & root,
const SwapPackManifest & manifest,
const std::vector< std::uint8_t > & trusted_public_key )
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).

◆ verify_swap_pack_integrity()

SwapPackVerifyResult pulp::format::reload::verify_swap_pack_integrity ( const std::filesystem::path & root,
const SwapPackManifest & manifest )
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.

◆ verify_swap_pack_signature()

SwapPackVerifyResult pulp::format::reload::verify_swap_pack_signature ( const SwapPackManifest & manifest,
const std::vector< std::uint8_t > & trusted_public_key )
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).

Variable Documentation

◆ kAbiVersionSymbol

const char* pulp::format::reload::kAbiVersionSymbol = "pulp_reload_abi_version"
inlineconstexpr

◆ kBuildFingerprintSchema

uint32_t pulp::format::reload::kBuildFingerprintSchema = 1
inlineconstexpr

Bump when the fingerprint layout/semantics change.

◆ kCreateSymbol

const char* pulp::format::reload::kCreateSymbol = "pulp_reload_create"
inlineconstexpr

◆ kDestroySymbol

const char* pulp::format::reload::kDestroySymbol = "pulp_reload_destroy"
inlineconstexpr

◆ kFingerprintSymbol

const char* pulp::format::reload::kFingerprintSymbol = "pulp_reload_fingerprint"
inlineconstexpr

◆ kKeyBlobMagic

std::string_view pulp::format::reload::kKeyBlobMagic = "PULP-RELOAD-KEY-v1"
inlineconstexpr

◆ kReloadAbiVersion

int pulp::format::reload::kReloadAbiVersion = 1
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).