Development inspector capabilities¶
The development inspector is an opt-in platform under construction. In a
GPU-enabled desktop build with a compatible window host,
pulp run --inspect[=PROFILE] constructs an authenticated network session for
the selected standalone window and displays a visual Cmd+I indicator. Pulp's
built-in macOS standalone window hosts currently provide the required event-loop
exit drain and deferred-close turn. Windows and Linux use external WindowHost
factories; an external host must explicitly implement both contracts before
active inspector profiles are accepted. A run without --inspect, a host
without those contracts,
a GPU-disabled or mobile build, and every plugin-format launch constructs no
endpoint. The installed Rust pulp, sibling pulp-cpp, and pulp-mcp clients
can discover and authenticate to an explicitly activated endpoint without a
source checkout.
This page records the checked baseline so public descriptions do not confuse code presence with runtime reachability. The trust boundaries and explicit non-claims are defined in the capability-control threat model.
Capability contract¶
Every protocol method is assigned exactly one stable capability in
inspect/include/pulp/inspect/protocol_methods.inc. Capability IDs, risk,
side effect, executor, evidence, grantability, and named-profile membership live in
inspect/include/pulp/inspect/capability_definitions.inc. The C++ registries
reject duplicate method/capability IDs at compile time and test every ID round
trip. The frozen registry also declares per-operation input/output schema IDs
and digests, required build feature, runtime contexts, host tiers, activation,
policy and grant scope, cancellation/timeout behavior, and compatibility and
deprecation state. Its canonical SHA-256 is embedded in each artifact manifest;
changing any of those fields invalidates the prior manifest identity.
Operation schemas are closed, versioned, and resource-bounded. Every string value has an explicit maximum; required resource and idempotency identifiers also have a nonzero minimum. Capture has separate closed window/node variants, state parameter IDs use the complete unsigned 32-bit range, test transport is limited to the executor's 20–400 BPM domain, and telemetry accepts at most 32 unique nonempty channel IDs. These are registry contract bytes, so relaxing or narrowing a bound requires a new digest and consent identity.
Trace session control matches the concrete Trace.startSession/
Trace.stopSession adapter: start accepts unique categories bounded to 128
entries and 128 UTF-8 bytes each, plus a
1–512 MiB ring, the host-main executor enforces the same limits before capture,
and the raw response is the declared evidence. Publication attachment remains
host-owned lifecycle.
The separate trace-control operation uses closed action variants for the
Performance, Audio, and Motion host-main adapters, including bounded motion
metrics; its receipt can carry the motion trace ID needed by a later stop.
The action-discriminated motion-start receipt requires that ID; other action
receipts cannot smuggle one. Integer-valued JSON numbers use Draft 2020-12
numeric semantics, so spellings such as 15.0 are accepted when finite and in
range. Pulp applies a source-owned CHOC compatibility patch so the equivalent
standards-valid 15e+0 spelling reaches the same executor validation.
Authoring bypass and lock changes require a nonempty anchor of at most 256
Unicode codepoints; DOM highlight
requires an exact node, is capped at 256 UTF-8 bytes, and changes overlay selection instead of
returning false success. Runtime evaluation additionally rejects NUL and
enforces a 65,536-byte UTF-8 ceiling (x-pulp-maxUtf8Bytes), because JSON
Schema maxLength alone counts characters rather than encoded bytes.
The dev.pulp.*@1 IDs below are the canonical authoring and broker contract.
The shorter Inspector IDs are compatibility spellings only. A shipped manifest
is an upper bound, never a grant. Effective authority requires all seven terms:
implemented, built, host_available, activated, policy_eligible,
client_granted, and session_live. Missing terms deny by default and surface
one stable reason such as not-built, client-not-granted, or
session-not-live.
| Canonical capability (legacy spelling) | observe |
develop |
Current reality |
|---|---|---|---|
dev.pulp.instance/read@1 (session.describe) |
yes | yes | The standalone owner publishes identity, agent context, and authenticated capability reporting |
dev.pulp.session/control@1 (session.control) |
no | yes | One-controller lease with expiry, renewal, disconnect release, and structured conflict errors |
dev.pulp.state/read@1 (state.read) |
yes | yes | The standalone session exposes its exact StateStore parameter catalog and values |
dev.pulp.render/offline@1 (render.offline) |
no | no | Frozen Product A contract; no current executor or grant path |
dev.pulp.ui/observe@1 (ui.read) |
yes | yes | The standalone session exposes its live view tree and value-channel catalog |
dev.pulp.diagnostics/read@1 (diagnostics.read) |
yes | yes | Agent context and audio configuration are attached; individual performance sources may report unavailable |
dev.pulp.logs/read@1 (logs.read) |
yes | yes | Scripted-UI console capture remains attached across in-place reloads |
dev.pulp.ui/capture@1 (capture.image) |
yes | yes | Advertised only when the initial standalone tree has an honest live or portable whole-window capture route; each request revalidates reload-sensitive native-overlay/GPU requirements; node capture remains unavailable |
dev.pulp.ui/input@1 (ui.input) |
no | no | Frozen high-risk Product A contract; no current executor or grant path |
dev.pulp.trace/control@1 (trace.control) |
no | yes | Domain components exist but the standalone owner does not advertise them without a trace binding |
dev.pulp.trace/session-control@1 (trace.session.control) |
no | yes | Process-global Trace sessions require a publication-scoped binding |
dev.pulp.state/parameter-gesture@1 (state.write) |
no | yes | The develop standalone profile applies legal parameter mutations on the main thread after acquiring the same-connection controller lease |
dev.pulp.test/input@1 (test.input) |
no | yes | Test.injectMidi accepts bounded note-on/off events and Test.setTransport applies coherent standalone play/position/tempo updates through the normal host path |
dev.pulp.authoring/tweaks@1 (authoring.tweaks) |
no | yes | Transient tweaks, exact-node highlight, anchored bypass/lock, live constants, editor URL templates, and repaint flashing stay in this capability; filesystem and editor-launch methods remain unavailable |
dev.pulp.telemetry/subscribe@1 (telemetry.stream) |
no | yes | The standalone owner claims the value-channel telemetry sidecars only when this capability is effective, then provides bounded contextual snapshots and per-client targeted subscriptions |
dev.pulp.runtime/reload@1 (runtime.reload) |
no | no | Frozen Product A contract; no current executor or grant path |
dev.pulp.runtime/evaluate@1 (runtime.eval) |
no | no | High-risk separate opt-in; --inspect-runtime-eval is required in addition to a controller-capable develop/custom selection |
dev.pulp.artifact/read@1 (artifact.read) |
no | no | Frozen publication-bound Product A contract; no current executor or grant path |
dev.pulp.unavailable/operation@1 (unavailable) |
no | no | Filesystem-backed tweak/fixture operations and editor launch are classified unavailable for the future policy |
off grants nothing. custom starts from an empty exact allow-list. These are
enforced policy definitions. develop deliberately excludes runtime.eval.
The launcher can add it only through the literal --inspect-runtime-eval
acknowledgement; custom also has to name runtime.eval and session.control.
The acknowledgement is one-run state and is not part of standalone persisted
preferences.
Broker service and local carrier foundation¶
The optional pulp::inspect-control component contains the broker-owned
identity, registration, grant, typed admission, durable receipt, cancellation,
quota, progress, and artifact-lineage state needed by the capability-control
migration. It is not linked into ordinary plugin-format artifacts and does not
replace the current explicitly activated standalone inspector transport. On
macOS, the optional pulp-control-broker executable owns the canonical
per-user LocalSocket endpoint. It currently exposes only a bounded health
probe: it has no signed launcher bootstrap, host registration channel, consent
surface, or operation executor, so authority-bearing session admission fails
closed. Darwin CLI installs place the broker beside pulp and pulp-cpp and
reconcile the owner-only dev.pulp.control-broker LaunchAgent. A successful
reconciliation proves only reachable-unverified; strict code-signature
validation at install time is an integrity check, not a trusted publisher or
authorization decision. Canonical ~/.pulp/bin installs activate
automatically. A custom install root requires explicit acceptance on first
install, and later upgrades may reuse it only when the existing owned plist
already names that exact broker path. The installed ControlClient accepts a typed
ControlClientTransport representing one authenticated, connection-bound peer
and client identity; its artifact-read API therefore has no caller-supplied
client ID. The legacy InspectorSession/server is not a compatibility
transport or a second capability-control authority path.
Ephemeral socket and liveness files are isolated from owner-private durable
receipts and artifacts under ~/.pulp/state/control-broker/v1; service stop or
removal leaves that durable state intact. The installed ControlClient accepts
a typed ControlClientTransport representing one authenticated,
connection-bound peer and client identity; its artifact-read API therefore has
no caller-supplied client ID. The legacy InspectorSession/server is not a
compatibility transport or a second capability-control authority path.
ControlService accepts a carrier-verified peer and connection-bound client
identity, but has no executor unless a later runtime adapter injects one.
Signed service activation, consent UI, host execution routing, and live
Inspector migration remain later work.
The foundation accepts only carrier-observed VerifiedControlPeerIdentity
values minted by the broker's peer verifier. Its fingerprint binds the peer
role, UID/SID, PID, process-start generation, executable identity, and verified
publisher. Payload claims and same-user status alone are insufficient. A
launcher bootstrap is single-use, short-lived, bound to that exact fingerprint,
consumed even after a wrong-peer attempt, and wiped on consumption, expiry, or
destruction. The macOS carrier gathers and validates OS peer evidence before
the composition root accepts it. Other platforms remain fail-closed until they
gain an equivalent credential-bearing verifier.
Registration is limited to Pulp-owned T0 offline jobs and T1 standalone hosts.
It validates the complete canonical control manifest and exact artifact digest,
derives the consent identity, and binds an opaque registration to one exact
session, instance, publication, peer generation, and lease. Empty or
"latest" selection is unavailable. Shared plugin hosts, including direct AUv3,
fail with host-unavailable until a separately reviewed trusted-host bridge can
attest the exact loaded slot.
Grant issuance requires a live exact client and registration, a capability subset present in that validated manifest, bounded expiry, and approval from a trusted Pulp CLI, trusted host UI, or existing user policy. Plugin UI and agent client assertions cannot approve a grant. Interactive consent decision IDs are single-use; durable policy IDs may be reused within their policy scope. Broker restart, client disconnect, registration disappearance, expiry, or explicit revocation removes authority. The bounded metadata audit records identities, decisions, and stable reason codes, never bootstrap secrets, consent text, or operation payload values.
A stored grant establishes only the client_granted term. It does not activate
an endpoint, route an operation, or bypass the other six permission terms.
Each service session negotiates its own protocol version and mandatory receipt
support before request or cancellation dispatch; progress is available only
when that session negotiated it. Admission validates JSON parameters against
the resolved operation's input schema before writing an authority-bound
idempotency receipt. Successful executor output is validated against the same
operation's output schema before a completed receipt is persisted. Operations
whose typed result exposes receipt_id explicitly bind that field to the
broker-minted durable receipt; a mismatched executor result fails closed and is
persisted as an internal failure. Unsupported or malformed schema keywords fail
closed. Exact replay returns the existing receipt without a second dispatch.
Request parameters, result details, and complete wire envelopes have distinct bounded budgets (512 KiB, 1,600 KiB, and 2 MiB respectively), including bounded JSON node counts. Bulk UI-tree, diagnostics, and log results use artifact handles instead of expanding those receipt budgets; artifact reads retain their bounded one-mebibyte chunk contract, and publication expiry is capped at 24 hours by the store rather than trusted to the executor.
The broker checks deadlines and atomically enforces active-operation quotas.
Trusted in-process executors must return within that bound or promptly return a
deferred outcome; the service cannot preempt arbitrary C++ in its own process.
The supplied main-thread adapter enforces the contract with bounded fenced RPC.
If already-started legal-thread work exceeds its response deadline, the response
is unknown-needs-refresh, while the durable receipt remains running and
retains its quota slot until deferred completion settles it. Cancellation intent
is durable. Progress events are receipt-bound, monotonic, bounded, and subject
to carrier backpressure.
Phase 3b's artifact support is intentionally minimal. Publication is blob-first; a terminal receipt may name only a stored artifact with matching producer lineage, and broker-mediated reads reauthorize the original grant, complete lineage, terminal receipt, metadata, and expiry. Per-blob and read-chunk limits exist, but aggregate quota, retention collection, deletion audit, redaction, and generalized ACL policy remain Phase 7. Expired metadata is removed lazily and orphaned content-addressed blobs may remain. Owner-private filesystem modes exclude other OS users, not malicious processes running under the same UID; broker authorization is not an at-rest secrecy boundary against such a process.
Checked implementation matrix¶
| Area | Present | Missing |
|---|---|---|
| Constructor/reachability | Explicit pulp run --inspect[=PROFILE] activation constructs one authenticated owner for a compatible GPU desktop standalone window; ordinary and plugin-format launches remain endpoint-free |
Additional host-format ownership |
| Window host | Built-in macOS standalone hosts keep their owning-thread dispatcher alive after native-loop stop until accepted inspector work retires, and schedule startup-failure close on a later native event turn | Windows/Linux external factories must implement event_loop_supports_exit_drain() with run_event_loop_until(), plus supports_deferred_close() with request_close_deferred(), to opt into active profiles |
| Build/link/install | Optional protocol, reader discovery, neutral discovery-path support, publisher/runtime, client, authoring, and pulp::inspect-control targets are component-gated and separate from the GPU overlay. Installed protocol/control/client components expose the fail-closed broker, typed service, canonical local client transport, health probe, and macOS health-only broker executable. A mandatory non-slow clean-prefix consumer compiles and runs those installed targets while rejecting direct GPU/render/format/host/CLI/MCP closure. Publisher/runtime link closure does not grant reader authority; an ordinary pulp::format fixture proves no inspector symbols are present |
Per-target shipped-product declaration and final product-manifest proof |
| Threading | The standalone owner uses bounded owning-thread RPC, responds after timely application, cancels queued work during teardown, and fences started timeouts as mayHaveApplied while discarding late responses. Reload generations rebind owned channel metadata, the sole telemetry attachment, and scripted inspector sources on the UI tick |
Additional host-format ownership |
| Discovery/security | The explicitly activated standalone path retains owner-private ephemeral record/token files, exact publication selection, mutual nonce/HMAC proofs, replay rejection, timeouts, teardown, and one-controller lease. Separately, the broker composition root owns identity-bound single-use bootstrap, exact T0/T1 registration, trusted-consent grants, lifecycle revocation, per-session negotiated envelopes, per-operation input/output schema enforcement, durable replay receipts/cancellation, active-operation quotas, and original-lineage broker reads from the minimal artifact store. Its macOS local carrier owns one endpoint in an owner-only runtime directory and binds accepted-socket UID/GID/PID and audit-token PID generation to a rechecked live code-signing identifier, CDHash, and Team or per-artifact ad-hoc identity; stale-path replacement is device/inode guarded, while insecure endpoint parents, TCP/FIFO identity, malformed UTF-8/JSON, dead peers, and mismatches fail closed | Signed launcher and host registration, trusted consent surface, execution routing, non-macOS verified-peer implementations, migration of live operations to that path, and Phase 7 aggregate artifact quota/retention/redaction/deletion policy; owner-private files are not secret from a same-UID process |
| CLI | pulp inspect profiles/list/capabilities/doctor and typed parameter/MIDI/transport mutations provide stable JSON; every live operation uses exact session/instance/publication targeting through the shared client |
Telemetry subscription lands in the next phase |
| MCP | Installed in-process shared client exposes profiles/list/capabilities/doctor plus typed parameter, MIDI, and transport tools; success carries publication identity and failures carry structured code/message/data | Telemetry subscription lands in the next phase |
| Capture/telemetry | Whole-window in-process capture (live host back-buffer when available, portable view rendering otherwise), owned value-channel metadata, snapshots, and bounded scalar/meter/vector/event subscriptions are attached to the standalone session; delivery is targeted by authenticated client identity and carries explicit source, stale, coalescing, overflow, and transport-loss state | Node capture, external-host compositing, and CLI/MCP watch commands |
| Shipping | The component gate removes live inspector targets and the control core; live CLI commands fail explicitly when disabled, while read-only inspect audit remains available. Ordinary-format symbol stripping, per-target declarations, canonical manifests, and manifest-versus-binary checks are continuously tested. The optional macOS pulp-control-broker ships in the Darwin CLI archive, installs beside the CLI, and is reconciled as the owner-only dev.pulp.control-broker LaunchAgent. It opens only the owner-private local endpoint, reports at most reachable-unverified, and refuses authority-bearing admission until signed launcher and host adapters are supplied |
Trusted service identity, broker-owned consent proof, host execution routing, and platform parity beyond the fail-closed macOS v1 verifier |
The production server binds loopback only and requires fresh, role-separated
nonce/HMAC proofs from both client and server using an owner-private
per-session credential. Discovery rejects expired or dead publishers,
duplicate live publisher identities, stale publication generations, insecure
mode bits or extended ACLs, path escapes, and ambiguous selection. Newly
created Darwin discovery objects
discard inherited ACLs before any credential material is written; readers
validate the opened object and fail closed on any remaining extended ACL.
Rejected server starts wipe their owned credential before releasing storage.
Capability dispatch is fail-closed before a domain handler runs. The old
unauthenticated direct-handler server exists only as a non-installed test
fixture for transport regression coverage.
Authenticated connections may wait idle for their next frame, but once any
header byte arrives the complete length-prefixed frame must arrive within a
bounded cumulative deadline. Partial headers and payloads are disconnected so
they cannot retain every bounded client slot.
After a complete request frame is sent, a response timeout or disconnect is
explicitly reported as mayHaveApplied; timeouts fence the connection so a
late response cannot be mistaken for a safe retry boundary.
Build presence, host wiring, profile allowance, and current enablement are
separate facts. Session.getCapabilities reports the available and effective
sets for an authenticated session; no client should infer one from another.
End-to-end validation boundary¶
The checked source workflow starts three independent standalone processes in
one discovery directory: an ordinary develop session, an observe session,
and a deliberately capability-minimal runtime-evaluation session. It selects
each process by the exact session, instance, and publication IDs that process
published. This proves ambiguous selection fails closed, observe reads work
while state mutation is denied, and runtime evaluation is unavailable without
the separate opt-in. The minimal evaluation process proves a successful typed
result plus the 64 KiB request bound without weakening the effectful live
realm used by the ordinary develop process.
The same real-process workflow proves controller acquisition, typed parameter,
transport, and MIDI mutation ordering; compositor-backed PNG capture; scalar,
vector, event, and deliberately stale value-channel snapshots; slow-client
attempt sequencing and explicit source/coalescing loss; generation-changing
reload reattachment of DOM, logs, value telemetry, and runtime-evaluation realm
authority; and independent record, credential, and lock teardown.
Those waits advance from observed process, protocol, or sequence state rather
than assuming a fixed elapsed delay. Processor-owned scripted sessions can
explicitly opt into in-place reload on the stable host root, preserving the
session that owns inspector and GPU-surface attachments instead of replacing it
through create_view(). Non-opt-in or replacement-session generations remain
pending and fail closed.
A separate packaged-client workflow starts exact develop and observe
processes and drives both the installed Rust pulp inspect client and the
installed marketplace pulp-mcp client. It covers discovery, capability,
context, parameter, DOM, capture, transport, MIDI, and typed mutation reads or
writes without a source-tree client path. An independent source scan rejects
production protocol literals that are absent from
protocol_methods.inc; its self-test injects an unmapped method and requires
the check to fail.
These proofs apply to the enabled development build described above. The ordinary-launch endpoint-free and disabled-component gates remain separate tests. Final shipped-product manifest, per-target declaration, and shipping override proof depend on the Phase 7 composition and are not claimed here.
Live-realm runtime evaluation boundary¶
runtime.eval is refused when the attached ScriptedUiSession has any
effectful ReloadCapability: exec, clipboard, filesystem, storage,
ai, runtime_import, or network. The inspector reads the immutable grant
set installed in the live WidgetBridge; it does not mask names in
globalThis, because hiding names inside the same reachable realm is not a
security boundary. Runtime.getCapabilities reports canEvaluate:false and
an exact evaluateDeniedReason after an unsafe session is attached.
The framework-owned build_editor_ui path retains its historical
CapabilitySet::all() posture and therefore rejects --inspect-runtime-eval.
A production host or custom processor that needs evaluation must explicitly
construct ScriptedUiSession with an empty
ScriptedUiOptions::granted_capabilities set. That reviewed set is retained
across hot reloads and checked again whenever the standalone host binds a
replacement scripted-UI session.
The arbitrary-execution adapter is compiled into the separate
pulp-inspect-runtime-eval archive and injected through the narrow
RuntimeEvaluator interface. The base inspector, protocol, transport, client,
and ordinary format archives do not depend on that component or contain its
high-risk binary marker. Requests are limited to 64 KiB of decoded code, use a
fixed two-second deadline, and reject serialized results or encoded responses
over 1 MiB. Result bytes, nesting depth, and cycles are bounded during QuickJS
traversal. The scripted realm is rebuilt from source after each evaluation,
preserving widget values but discarding deferred callbacks and global
mutations before the next frame pump. That rebuild has a fixed 500 ms cleanup
grace inside a three-second outer RPC fence; a failed rebuild destroys the
engine fail-closed. One owned, bounded server worker keeps the controller's
authenticated connection free to send Runtime.interrupt while evaluation is
in flight, and is included in the server's module-unload shutdown fence. These
limits compose with the bridge's single-flight, cooperative interrupt,
engine-detach, and teardown fences.
Typed test input and authoring boundary¶
test.input is deliberately narrow. Test.injectMidi accepts only note_on
and note_off, public channels 1–16, note/velocity bytes 0–127, and no raw
status bytes, SysEx, CC, timestamp, path, or script. Outstanding injected notes
belong to the controller session and are released when its lease is released,
expires, disconnects, or the session tears down. The installed one-shot clients
require a 1–2000 ms hold for note-on and send the matching note-off on the same
controller connection before releasing the lease. Test.setTransport accepts an
idempotent partial update containing at least one of playing, nonnegative
position_samples, or finite tempo_bpm from 20 through 400. Both operations
run through the owning thread and normal standalone host/processor path.
Numeric parameter changes remain state.write. Transient authoring controls
remain authoring.tweaks. Generic preset load/save, filesystem tweak
load/save, source jump, raw MIDI, and arbitrary UI scripting are not test-input
shortcuts; those methods remain unavailable. Runtime.evaluate is never an
implementation path for MIDI, transport, parameters, or authoring controls.
Client evidence loop¶
A client first runs pulp inspect list --json (or
pulp_inspect_list) and pins the returned session, instance, and publication
IDs. It authenticates capabilities with those exact IDs, reads the typed state,
performs only a capability-authorized typed mutation, rereads, and optionally
captures the selected window. The publication ID is non-reusable; a missing or
changed publication requires rediscovery. Runtime.evaluate is never a
parameter or test-input mutation path.
Live value-channel telemetry¶
Telemetry.getSnapshot, Telemetry.subscribe, and Telemetry.unsubscribe
are available only when telemetry.stream is effective. observe and custom
profiles that omit the capability do not claim or drain the exclusive
telemetry reader. Client identity is taken from the authenticated connection,
never from request JSON. Versioned response/event schemas are
pulp.inspect.telemetry.snapshot.v1,
pulp.inspect.telemetry.subscription.v1, and
pulp.inspect.telemetry.sample.v1.
A request may select at most 32 channel names. Subscriptions are limited to one
per client, default to 15 Hz, and are capped at 60 Hz. maxVectorValues is
bounded by the broker configuration; event and wire payloads are bounded too.
Slow-client loss is isolated by subscription: attempt sequence advances even
when delivery drops, and the next successful sample reports
transportDroppedSincePrevious. Continuous channels report only snapshots the
UI reader actually consumed. Their timestamp is therefore an inspector/UI
snapshot time, not a producer or audio-clock timestamp. Events use the bounded
producer tap, preserve zero-valued occurrences, and report cumulative source
overflow in snapshots plus since-delivery overflow in subscriptions, separately
from transport loss.
Every channel reports availability, source lifetime, publication/sequence,
staleness reason, coalescing, source drops, payload size, and its typed payload.
Non-finite DSP values use the inspector-wide string sentinels NaN, Infinity,
and -Infinity instead of collapsing anomaly evidence to JSON null.
Source destruction produces one terminal sample. On a successful processor
reload the subscription ID and requested channel names survive, the source
generation advances, and the first sample is marked reattached; removed names
remain explicit unavailable entries. The broker is the sidecars' sole reader
and runs on the serialized UI/control pump. Audio publication remains the
existing allocation-free, lock-free source/tap path and performs no JSON or
network work.