Skip to content

Shipping a capability-controlled endpoint

Release artifacts are inspector-free by default. PULP_ENABLE_INSPECTOR=ON only makes optional SDK components available; it does not link a listener, discovery publisher, server, registration, or runtime evaluator into an ordinary pulp_add_plugin target.

The consolidated authoring and diagnostics reference is Capability control. Every ordinary pulp_add_plugin target emits a production-stripped canonical dev.pulp.control/artifact-manifest@1 sidecar: no endpoint and no capabilities. The installed SDK ships the canonical adapter that binds a Standalone processor and state store. Opt-in requires an exclusively Standalone target, an explicit non-production CONTROL_PROFILE, CONTROL_CAPABILITIES, and PROCESSOR_FACTORY; mixed-format declarations fail closed. macOS GPU builds support the full canonical Standalone capability set. Other supported builds currently accept only instance and state reads, and configure rejects any capability whose required installed adapter component is unavailable.

The legacy server, raw client, discovery publisher, and standalone session owner were deleted in Phase 3. Do not restore them alongside the canonical host adapter.

The Phase 4 runtime archives now implement exact T0/T1 instance status and bounded state/parameter catalog reads. They are reachable only through a carrier-authenticated ControlService session, an exact registration grant, and an injected runtime executor. There is no pulp inspect or MCP adapter for these operations yet, and ordinary production artifacts remain stripped.

Control declarations are re-read as configure-time truth on every CMake run. Changing a target from research-unsafe to a narrower profile, removing runtime.eval, or deleting its acknowledgement force-replaces the cached profile and capability values in the existing build directory. Developers do not need to delete the build tree to withdraw authority, and stale cache state cannot preserve a removed capability.

Available profiles are production-stripped, developer-local, test-deterministic, support-diagnostics, and research-unsafe. support-diagnostics accepts only instance, state, diagnostics, and log reads. Mutation control capabilities require dev.pulp.session/control@1. The installed per-user broker composes trusted T0/T1 enrollment and routing; ordinary plugin-format targets stay stripped and unsupported host tiers remain unavailable.

dev.pulp.runtime/evaluate@1 is arbitrary execution in the product process. No profile or acknowledgement implies it. The reusable host executor accepts it only for an exact research-unsafe registration with the distinct unsafe acknowledgement, the separately installed high-risk evaluator component, and broker-owned single-use consent. The adapter also requires an interrupt-capable evaluator and an explicit result redactor; cancellation, deadline, or unsafe result handling fails closed. The canonical macOS GPU Standalone adapter composes this component only for an explicitly declared research-unsafe target.

The legacy SHIP_INSPECTOR, SHIP_INSPECTOR_RUNTIME_EVAL, and INSPECTOR_CAPABILITIES spellings have been removed. Use the canonical control profile and capability declarations. They cannot be mixed with CONTROL_*; new projects should use only the canonical form.

BUNDLE_ID remains optional for a stripped legacy-compatible target. It is required as soon as a control profile or endpoint is declared, so an enabled endpoint can never ship with anonymous publisher identity.

Packaging repeats the review boundary. Use --ship-inspector for an endpoint declared by the build manifest. If and only if that manifest includes runtime.eval, also pass --ship-inspector-runtime-eval. Manifest/flag mismatches fail before packaging. pulp validate --json, pulp ship check --json, and pulp ship package --json include the capability report; package also writes artifacts/inspector-capability-package-input.json. JSON package failures remain JSON and include a nonzero exit_code plus the fail-closed diagnostic.

Every plugin and standalone binary runs a post-link control shipping scan. This includes Standalone, VST3, CLAP, LV2, AU v2, both executable AUv3 pieces, the AUv3 container, and AAX when that SDK is available. Multi-plugin VST3 and CLAP bundles enter the same scanner as ordinary production-stripped artifacts; the bundle helper is not a packaging bypass. Control endpoints are Standalone-only: in a mixed developer build, each non-Standalone artifact receives its own production-stripped manifest rather than inheriting the intentional Standalone profile.

The scanner verifies the canonical manifest digest together with retained profile, format, platform, and architecture markers. It measures the actual artifact size and records which native symbol and dependency scanners ran in a <target>.<format>.control-shipping-report.json sidecar. On macOS those tools are nm, otool, and lipo; Linux uses nm and readelf; Windows uses dumpbin. A missing native scanner blocks the artifact, so each platform lane must provide its native toolchain rather than silently claiming evidence.

For an intentional profile, every declared capability and endpoint marker must be retained from the linked control implementations; the shipping helper emits only artifact identity and cannot make an empty target satisfy its declaration. Source-bearing control components own these markers, and a real-component link fixture constructs the endpoint and resolves a declared capability before its final executable is scanned. The high-risk evaluator marker must exactly match its separate acknowledgement. For production-stripped, the scanner rejects endpoint, capability, runtime-evaluation, and Remote View authority strings, known control symbols, and known control dynamic dependencies. It also scans native binaries inside the package closure (or resolved sibling loader dependencies), so renaming a helper library cannot hide retained control code. The check reads the final linked artifact, so a CMake option or an unlinked declaration is not accepted as shipping proof.

The generated dev.pulp.control/shipping-artifact@1 sidecar is per binary and names its format, platform, complete architecture list, profile, and canonical manifest digest. This makes diagnostic and research artifacts visibly distinct from ordinary production output while preserving the canonical standalone manifest used by the read-only audit command.

Custom pulp-install-<target> targets depend on every format binary they copy. That dependency is load-bearing: installation cannot copy a stale format while skipping its post-link scan. A persisted scan stamp depends on the artifact, both manifests, and the scanner itself, so changing shipping policy invalidates an earlier report even when the binary did not relink. The helper and scanner are both exported in the installed CMake SDK, and an installed-layout test builds the complete profile and format policy matrix without reaching back into the Pulp source tree.

The repository matrix exercises all five profiles and the Standalone, VST3, CLAP, LV2, AU v2, AUv3, and AAX policy labels. The local macOS proof builds universal arm64 and x86_64 artifacts and verifies both slices with lipo. The path-scoped Control shipping native matrix workflow closes the native CI boundary with real installed-SDK consumers: macOS universal, Linux x86_64 and aarch64, and Windows x64. It builds every available real plug-in format for each platform and aggregates the canonical nm/otool/lipo, nm/readelf, or dumpbin /UNDNAME reports into dev.pulp.control/native-shipping-evidence@1. A synthetic format label does not count as proof.

AAX remains developer-supplied. On a protected-main push, when both PULP_AAX_SDK_ZIP_URL and PULP_AAX_SDK_ZIP_SHA256 repository secrets exist, the macOS universal leg builds and scans a real AAX bundle from the verified out-of-tree SDK. Pull requests and manual runs never receive those secrets; they record status: unavailable, proof: false, and aax-sdk-secret-withheld-untrusted-event. A trusted run without the URL uses aax-sdk-secret-unavailable. Both are explicit availability dispositions, not AAX proof, and a configured URL without its checksum fails closed.

Directory and direct-file audits resolve artifact names only as safe basenames beside their sidecars. An exact-named direct sidecar is not sufficient by itself: its target or product identity must also match the artifact filename (including the supported .exe form). Absolute names, .. traversal, identity mismatches, and symlinked artifacts or sidecars are rejected rather than followed or consent-bound to a different publication. Canonical evidence in both direct-file and directory audits additionally requires the sidecar stem to equal the manifest target and resolves only executables named by the manifest target or product; marker-bearing renamed siblings cannot inherit stale identity. Plugin-format subtrees (.vst3, .clap, .component, .appex, .aaxplugin, and .lv2, case-insensitively) are excluded from standalone evidence, including their sidecars. Direct audits classify both the absolute caller path and its resolved path, so a working-directory-relative spelling or parent symlink alias cannot hide a plugin-format ancestor. Each candidate executable is read into one immutable audit snapshot and those same bytes drive standalone selection, surface detection, marker verification, artifact hashing, and consent identity. The audit never selects one version of a file and hashes or approves a later version.

The manifest contains an opaque build-tree ID and the frozen control-registry digest in addition to its exact capabilities. Both participate in the embedded manifest SHA-256. The audit derives the durable consent key as SHA-256 of the verified manifest digest plus the SHA-256 of the exact artifact bytes. Any binary, identity, capability, risk, or schema change therefore produces a different consentIdentity. The audit rejects a stale registry digest and a directory that contains no auditable Pulp standalone artifact; arbitrary plugin bundle directories cannot receive an empty pass.

Before shipping, run the same read-only check yourself:

pulp inspect audit path/to/MyProduct --json

The audit does not load or activate the artifact. It verifies the canonical manifest, build and registry identity, profile and SHA-256 marker, declared capability markers, endpoint and runtime-eval boundaries, and known external surfaces. Each capability includes its risk, side-effect class, executor, and expected evidence, with focused advice for mutation or critical authority. Exit 0 is pass; exit 1 is a fail-closed block; command misuse exits 2. Legacy shipping manifests remain readable by compatibility report paths, but the control audit blocks them with audit.canonical-manifest-required; they cannot produce identity-bound consent. Every block includes a stable manifest.* or audit.* errorCode in JSON. The report includes both artifactDigest and the derived consentIdentity, so code changes cannot retain consent merely by reusing a build-tree manifest. The command is deliberately included in Inspector-disabled production SDKs.

Shipping evidence and runtime authority remain separate checks. The canonical trusted launcher, host router, typed execution, CLI/MCP clients, bounded artifact store, and telemetry path do not restore the deleted legacy authority; new support must extend the same centralized path.

The frozen operation schemas are closed and bounded. Required input and output resource, receipt, lease, stream, plugin, build, node, and idempotency identifiers cannot be empty; strings and collection sizes have explicit ceilings; capture uses distinct window and node request shapes; node requests bind an opaque attached-view generation, and UI input accepts exactly one closed-schema event per receipt with bounded coordinates, button, key, target, generation, and UTF-8 text fields; state parameter IDs cover the full unsigned 32-bit domain; transport tempo is 20–400 BPM; and telemetry accepts at most 32 unique, nonempty channel IDs. Trace sessions freeze the concrete 1–512 MiB ring and bounded category contract, and the host-main executor enforces the same unique, nonempty, 128-category/128-byte limits before capture. Other Performance, Audio, and Motion controls use closed action variants; motion-start accepts 1–32 declared metrics. Before creating a trace, the executor rejects unknown fields, empty or overlong UTF-8 names and node IDs, duplicate or unknown property names, and invalid geometry spaces or sources. Name ceilings count Unicode codepoints, not encoded bytes. Its receipt must echo its action and preserve the returned trace ID so a later stop can name it. Trace-session start and stop reject undeclared translated fields before changing capture state. Schema-integer fields—including trace-session ring_mb and Motion frame/FPS values—accept finite, in-range integral JSON numbers such as 15.0 and 15e+0, as required by Draft 2020-12. Authoring bypass and lock require a nonempty anchor of at most 256 Unicode codepoints, while highlight requires an exact node with a 256-byte UTF-8 ceiling. Runtime evaluation rejects NUL and carries both a character ceiling and the executor's 65,536-byte UTF-8 ceiling. Window capture additionally reports bounded positive dimensions, validates the PNG chunk structure and dimensions, strips ancillary metadata, and records redaction_state=redacted on a sensitive broker-owned artifact. Node capture and UI input use the host-owned exact-target adapter and remain unavailable when that component is not installed. Changing any of these limits changes the registry digest and therefore the artifact and consent identity reviewed above.