Skip to content
Documentation
1.0.0-beta.9 · exact release

Package lowerer contract reference

Source fda16de1b352 · Published v1.0.0-beta.9

This reference defines the versioned trusted package-lowerer protocol for first-party and third-party packages. External packages need no monorepo registration, package-specific pin, or equality with Pulse's npm version. Compatibility is determined by the exact supported protocol versions below.

Use Pulse-aware package authoring first. A package owns its JavaScript facade and the Native subset that its lowerer implements. The release catalog describes Pulse's own packages, not an external admission allowlist. Package authors remain responsible for their own target claims.

Manifests and builders execute as trusted build dependencies in the compiler's Node process. They can access its filesystem, environment, and other Node APIs. There is no sandbox, provenance check, resource quota, or isolated failure boundary. Installing a package that declares a lowerer is a build-time trust decision. The immutable invocation envelope describes supported inputs; it does not constrain what trusted JavaScript can access.

Application support and protocol compatibility

Section titled “Application support and protocol compatibility”

The versioned manifest, invocation, result, canonical operation, and provider requirement contracts are the compatibility boundary. Internal implementation modules and new compiler hooks are not additional public extension surfaces.

The present-tense owner for each stage is:

ConcernCanonical repository owner
Product role, target modes, and conformance IDspackages/<feature>/pulse.package.json
Application API and JavaScript implementationpackages/<feature>/src/ and its package exports
Package manifest and builder entrypackages/<feature>/pulsewasm.manifest.cjs and packages/<feature>/pulsewasm.compiler.cjs
Manifest validation and trust contractwasm/packages/contracts/src/library/manifest.js
Manifest discovery and trusted builder loadingwasm/packages/library-kit/src/compiler/handler-library-contracts.js
Reachable package/symbol ownershipwasm/packages/compiler/src/project/package-reachability.js
Per-module syntax recognitionTrusted packages/<feature>/pulsewasm.compiler.cjs builder
Builder invocation/result envelopeswasm/packages/contracts/src/package/package-contract.js and wasm/packages/library-kit/src/compiler/handler-library-contracts.js
Canonical package operation, lowering bundle, and provider-requirement recordwasm/packages/contracts/src/package/package-contract.js
Recognition and whole-project orchestrationwasm/packages/compiler/src/spine/package-operation-seam.js
Feature operation and payload vocabularywasm/packages/contracts/src/<feature>/contracts.js
Provider-neutral whole-project compositionwasm/packages/compiler/src/canonical-project-compiler.js
Provider capability and target realizationpackages/provider-<id>/src/
Public support and package availabilityrelease/pulse-release-manifest.json

Assets and GRIP are synchronized release references: packages/assets/pulse.package.json, packages/assets/pulsewasm.manifest.cjs, packages/assets/pulsewasm.compiler.cjs, wasm/packages/contracts/src/assets/contracts.js, packages/grip/pulse.package.json, packages/grip/pulsewasm.manifest.cjs, packages/grip/pulsewasm.compiler.cjs, and wasm/packages/contracts/src/grip/contracts.js.

JWT is the current cryptographic-composition reference: packages/jwt/pulse.package.json, packages/jwt/pulsewasm.manifest.cjs, packages/jwt/pulsewasm.compiler.cjs, and wasm/packages/contracts/src/jwt/contracts.js. It composes HS256 and ES256 through @pulse-compute/crypto; external packages may use the same versioned contracts.

This owner map is the detailed package-lowering companion to Current architecture contracts. Update the owning source, conformance, and this reference together when the protocol changes.

reachable module graph
→ package binding and contract selection
→ trusted manifest discovery
→ package-owned static recognition
→ canonical package operations
→ whole-project capability envelope
→ provider plan and binding validation
→ JavaScript execution or Native target realization

The reachable graph chooses participating package contracts and attributes their imported symbols. Recognition then runs per reachable project module. The project graph resolves the installed or workspace package at each import, including nested dependencies, before loading its declaration. Unrelated installed manifests are not executed by graph compilation. Explicit inventory tools may enumerate declared lowerers. There are no dynamic registration hooks.

ContractValue
Manifest versionpulsewasm.lowerable-library-manifest.v2
Manifest kindpulsewasm.lowerable-library-manifest
Builder protocolpulsewasm.lowerable-compiler-builder.v1
Builder invocationpulse.package-builder-invocation.v1
Builder resultpulse.package-builder-result.v1
Canonical package operationpulse.canonical-package-operation.v1
Package lowering bundlepulse.package-lowering-bundle.v1
Provider requirement recordpulse.provider-requirement-record.v1
Required trustbuild-dependency (first-party is a legacy alias)
Canonical provider planpulse.canonical-provider-plan.v1
Canonical provider contractpulse.canonical-provider-contract.v1

Package-specific contract, plan, artifact, and payload versions are owned by their corresponding shared contract modules.

A lowerable package declares the manifest path in package.json:

{
"pulse": { "contract": "./pulse.package.json" },
"pulsewasm": {
"manifest": "./pulsewasm.manifest.cjs"
}
}

The manifest file, compiler builder, built facade, and sidecar must be included by package.json.files and available from an extracted npm tarball.

The validator requires these paths:

FieldMeaning
versionLocked manifest schema version.
contractIdStable feature contract identity.
npmPackagePackage that owns the facade and builder.
lowerableSubpathExact application import recognized by the compiler.
facade.namespaceCanonical namespace used by the facade.
facade.symbolsNon-empty static symbol list.
modes.typescript.entryNormal package TypeScript/JavaScript entry.
modes.jsEngine.entryManual JavaScript engine entry.
modes.wasm.sidecarPackage-owned AssemblyScript sidecar path.
modes.wasm.loweringsNon-empty facade-symbol to sidecar-symbol mappings.
compiler.versionBuilder protocol version.
compiler.entryPackage-relative CommonJS builder module.
compiler.exportExported builder function name.
compiler.builderOwnerMust equal npmPackage.
compiler.trustbuild-dependency, or the legacy first-party alias.

The manifest may also document public runtime symbols, compiler inputs, artifact filename, host capabilities, protocol owner, and feature policy.

'use strict'
module.exports = Object.freeze({
version: 'pulsewasm.lowerable-library-manifest.v2',
kind: 'pulsewasm.lowerable-library-manifest',
contractId: 'pulse.example',
npmPackage: '@pulse-compute/example',
lowerableSubpath: '@pulse-compute/example',
facade: Object.freeze({
namespace: 'example',
import: '@pulse-compute/example',
symbols: Object.freeze(['emit']),
}),
compiler: Object.freeze({
version: 'pulsewasm.lowerable-compiler-builder.v1',
entry: './pulsewasm.compiler.cjs',
export: 'buildExampleLoweringPlan',
artifact: 'example-lowering-plan.json',
builderOwner: '@pulse-compute/example',
trust: 'build-dependency',
}),
modes: Object.freeze({
typescript: Object.freeze({ entry: './dist/index.js' }),
jsEngine: Object.freeze({ entry: './dist/index.js' }),
wasm: Object.freeze({
mode: 'wasm-sidecar',
sidecar: './as/index.as.ts',
lowerings: Object.freeze([
Object.freeze({
tsSymbol: 'example.emit',
asSymbol: 'pulse_example_emit',
callShape: 'literal-topic-message',
hostCapabilities: Object.freeze(['example']),
}),
]),
hostCapabilities: Object.freeze(['example']),
}),
}),
})

The manifest validator additionally enforces:

  • non-empty string identities and paths;
  • a valid facade symbol array;
  • static tsSymbol and asSymbol values for each lowering;
  • valid optional public API symbol arrays;
  • owner equality between npmPackage and compiler.builderOwner;
  • supported builder protocol and explicit build-dependency trust;
  • normalization of TypeScript, JavaScript-engine, and Wasm mode objects.

A manifest validation error prevents builder execution. The manifest itself is trusted executable code and is loaded before that validation. Its npmPackage and compiler.builderOwner must match package.json; product metadata identity, trust, and authoring subpaths must agree. Duplicate contract/import owners fail. Declared files must remain inside their package after resolving symlinks.

Package versions are independent of Pulse's release. Unknown manifest or builder protocol versions are rejected explicitly. Normal application dependency resolution chooses installed versions; Pulse requires no additional package pin. No declaration grants a new provider capability: emitted requirements must still be supported by the selected provider.

The generic library kit resolves:

  1. the package manifest record;
  2. its package-relative compiler entry;
  3. the named export;
  4. the trust and owner declaration;
  5. shared library contracts.

It invokes the builder with this exact shared field set:

{
version,
cwd,
workspaceRoot,
sourcePath,
sourceText,
sourceFile,
typescript,
manifest,
libraryContracts,
packageCompilerBuilder: {
owner,
entry,
export,
trust,
},
generatedBy,
routePlan,
schemaBundle,
}

The top-level envelope and normalized data values are immutable. sourceFile and typescript are the only compiler-owned syntax references; no Program, type checker, language service, cache, CLI configuration, provider object, target descriptor, runtime object, or resolved secret crosses the seam. Builders should tolerate either sourceText/sourcePath or the supplied sourceFile according to their maintained tests.

A successful package invocation is normalized before compiler orchestration:

{
version: 'pulse.package-builder-result.v1',
contractId: 'pulse.example',
npmPackage: '@pulse-compute/example',
lowerableSubpath: '@pulse-compute/example',
artifact: { /* package-owned, versioned, owner-checked */ },
contributions: {
canonicalEffects: [],
canonicalIntrinsics: [],
resultAdapters: [],
schemaReferences: [],
cryptoRequirements: [],
realizationArtifacts: [],
guestUnits: [],
},
diagnostics: [],
warnings: [],
hasErrors: false,
}

Exact artifact fields beyond shared ownership/status metadata remain package-contract-owned. Compiler orchestration consumes only the normalized contributions fields; it does not fall back to package artifact entries. Unknown result fields, owner/version mismatches, provider/compiler object bleed, and disagreement between diagnostics and hasErrors fail at this receiving boundary.

A trusted package manifest may additionally name compiler.nativeApplicationExport in the same compiler module. This internal hook applies only to a recognized terminal package application. It does not change package discovery or trust. The input is { plan, managedHandlerNativeBundle, schemaBundle }; it contains static compiler facts and no provider instance or runtime values.

The builder returns pulse.package-native-application.v1 with matching contractId, package, and intrinsic, automaticFallback: false, generated source and sourceHash, canonical effects, continuations, capabilities, and a Native codec manifest. The compiler binds this contribution into the Native plan and requires one terminal package.application return with no additional handler bodies. Source hash and effect/continuation tables must agree. The selected provider must explicitly declare the matching package-native-application realization before ordinary project compilation.

The contribution uses effectFailure: 'package-completion' and exports pulse_package_set_effect_failure(index). Managed hosts authenticate failure settlement using the same single-use effect tickets as successful results. The package receives no host exception detail and owns its failure response. This versioned contribution does not enable arbitrary compiler hooks.

A package effect passed into provider planning has this conceptual shape:

{
version: 'pulse.canonical-package-effect.v1',
contractId: 'pulse.example',
package: '@pulse-compute/example',
import: '@pulse-compute/example',
kind: 'example.emit',
providerKind: 'example',
operation: 'emit',
capability: 'example.emit',
result: 'ack',
placement: 'statement',
resource: { kind: 'literal', value: 'updates' },
payload: { topic: 'updates', message: 'ready' },
range: { start: 100, end: 142 },
loc: { file: 'src/index.ts', line: 5, column: 3 },
}

Required semantics:

  • the effect uses the shared canonical-effect version and exact allowed fields;
  • contractId, package, and import exactly match the selected manifest;
  • kind, operation, and capability agree with contract vocabulary;
  • resource and payload contain normalized, serializable data;
  • result identifies the normalized result class;
  • source range/location points to the author call;
  • no provider-specific object or lowering name appears in the effect.

The shared package contract converts this effect into the exact versioned canonical operation, derives deterministic identity/order, sorts capability sets, clones immutable data, and rejects unknown fields. The compiler orchestrates that normalizer; it does not redefine the operation, lowering bundle, catalog, or provider-requirement identities.

Package builders use source-located internal diagnostics for unsupported facade syntax. The contract module should own stable internal code names and payload status vocabulary.

When a package failure is surfaced through the public CLI, the CLI wraps it in PULSE_PACKAGE_LOWERING_FAILED. Provider-visible missing capability or binding failures use their corresponding public PULSE_* entries.

Do not publish internal PULSEWASM_* proof codes as stable public diagnostic URLs unless they are deliberately promoted into the public catalog.

Each modes.wasm.lowerings[] entry binds a TypeScript facade symbol to a stable AssemblyScript export:

{
tsSymbol: 'example.emit',
asSymbol: 'pulse_example_emit',
callShape: 'literal-topic-message',
hostCapabilities: ['example'],
}

The sidecar should expose the declared asSymbol, call stable named host imports, and remain free of application-level discovery or provider SDK logic.

The generic loader may contain protocol terms such as manifest, builder, trust, and contract. It must not contain feature strings such as grip.hold, assets.lookup, or the example package import.

A release test should inspect generic loader/compiler source to enforce that separation for each new lowerer.

The late package-operation seam receives canonical operations, not a package-specific source AST or source text. Package-specific syntax recognition belongs to the trusted builder before provider planning; provider-specific lowering belongs after the complete capability envelope is known.

Package effects emit provider-neutral requirements. After the complete reachable program exists, compiler/provider eligibility validates those requirements through the canonical provider descriptor. The provider must map every required capability, plus any result capability such as opaque.pass-through.

Package lowerer code must not choose node.*, fastly.*, backend names, store names, or deployment resources. Those belong to the provider plan.

The protocol identifiers above define supported compatibility independently of npm package versions. Existing first-party declarations remain accepted. Changes to these envelopes require an explicit protocol revision and receiving validator support; an external package cannot negotiate an unknown protocol by matching Pulse's release number. Test against each compiler version you claim.

General compiler hooks, untrusted-code isolation, and automatic runtime capability registration remain outside this contract. The existing prebuilt guest-unit and provider-realization contracts keep their own restrictions.

A reachable trusted lowerer can contribute a private package-runtime-wasm realization artifact and canonical package.invoke effects. This is an additive runtime contract, independent of the static prebuilt guest-link contract and npm release versions. No package-name registration is required. The initial provider is Node, for explicitly selected Native and JavaScript targets; Fastly does not implement this capability.

The package owns its facade, protocol, policy, literal configuration, Wasm and fixtures. The provider owns HTTP capture, time, secret lookup and cryptography. Applications trust the package's executable build dependency and runtime Wasm; this mechanism is not an untrusted-code sandbox or CPU preemption boundary. Existing first-party runtime definitions remain valid and unchanged.

@pulse-compute/runtime/package accepts a definition with exact version pulse.package-runtime-definition.v1, a scoped package, contractId, providerKind: 'package', and exactly one operation:

operations: {
invoke: { kind: 'package.invoke', capability: 'package.invoke', result: 'string' }
}

effect(ctx, 'invoke', payload) still requires the actual live managed context. The exact payload is { artifact, binding, operation, configuration }; all are static strings. The compiler rejects dynamic runtime inputs and requires the same package/contract's artifact, declared operation and configuration. The result is a UTF-8 string of at most 65,536 bytes; the package defines its format. Request execution, effect limits, continuation settlement and cancellation are shared with existing effects. No ambient registration or current request exists.

A realization artifact has exactly these fields:

{
version: 'pulse.package-runtime-binding.v1',
id: 'vendor.protocol:profile',
package: '@vendor/protocol',
contractId: 'vendor.protocol',
kind: 'package-runtime-wasm',
mediaType: 'application/wasm',
data: {
abi: 'pulse.package-wasm.v1',
wasm: 'CANONICAL_BASE64',
sha256: 'LOWERCASE_HEX_SHA256',
operations: [{
name: 'verify', configuration: 'PACKAGE_DEFINED_STATIC_POLICY',
capabilities: ['http.snapshot', 'time.wall-clock', 'crypto.sha256', 'crypto.hmac-sha256']
}]
}
}

The canonical validator lives in @pulse-compute/wasm-contracts/package/runtime-binding. It checks owner identity, supported versions, unique operations (1–16), known capabilities, configuration (8,192 characters maximum), canonical base64 and hash, actual Wasm imports/exports and memory declaration. Artifacts are at most 1 MiB. There must be one defined, unshared 32-bit memory with an explicit maximum of at most 32 pages (2 MiB); imported memories and ambient host imports fail. Conflicting artifact IDs fail compilation. Wasm is a separate instance per operation; it is not linked into the application's guest memory.

The package builder receives the compiler's TypeScript API as input.typescript for inspection of its provided source file. This adds no provider, key, project configuration, TypeScript program or language-service authority. Crypto requirements must also be emitted through the existing package-owned cryptoRequirements contribution. Native explicitly selects the existing Crypto guest-source byte primitives; JavaScript explicitly selects its runtime-builtin byte backend. Native never runs JavaScript protocol logic or falls back to JavaScript crypto.

node.bindings.packages maps up to 32 literal binding names to records:

{
package: '@vendor/protocol', contractId: 'vendor.protocol',
artifact: 'vendor.protocol:profile', operations: ['verify'],
secret: 'PROTOCOL_KEY', keyId: 'local-key', keyVersion: '1',
trustDomain: 'tenant-a', origin: 'https://api.example.test'
}

The provider checks package, contract, artifact, allowed operation and exact configuration before invoking Wasm. Wire metadata cannot select a secret name. Only the provider reads the named secret; UTF-8 key material is 32–4,096 bytes and never enters effect inputs or protocol memory. The host HMAC verification command uses constant-time comparison. This first version requires a named key and HTTP origin even for operations using fewer capabilities.

origin is an explicit trusted HTTPS termination setting. The operator must ensure the transport serves that external origin; this setting is not inferred from Host, forwarded headers, a normalized URL, or a client assertion. The Node adapter retains original method, origin-form target, ordered field instances and exact buffered body before creating its Web Request. Limits are 128 fields, 16,384 bytes per value and target, 131,072 combined name/value bytes, and 65,536 body bytes. Non-ASCII field bytes, absolute/authority/asterisk targets, fragments, backslashes and opaque/transforming bodies are refused.

The optional provider-driver capturePackageRequest hook returns the versioned pulse.provider-package-http.v1 envelope with host execution options and close(). Node uses branded, request-owned captures; passing an application record cannot mint one. CLI HTTP development and built Node launchers capture and close them automatically. Launcher operators supply packageBindings explicitly; project development settings and secret values are not imported into a production launcher. HTTP fixture tests that bypass transport must supply a capture through the host integration API. The low-level @pulse-compute/provider-node/runtime/package-operations export is host integration, not a portable application facade.

Outbound signing is a host integration operation: the host freezes the exact intended message, captures it with direction outbound, invokes the package, and installs returned fields before sending. No generic send transaction or proof of a later send is provided. Inbound server captures cannot be relabeled by an application's payload. Hosts must close direct captures in finally.

The only allowed imports are (i32) -> i32 pulse_package.input_len, (i32, i32, i32) -> i32 pulse_package.input_read and optional env.abort. Input reads copy bytes into package memory and return a length, or -1 when undeclared/unavailable. Input slots are:

SlotContentRequired host capability
0, 1, 2Configuration, operation, reserved {}None
3–6Original method, origin target, scheme, authorityhttp.snapshot
7LE u32 field count, then LE u32 length + ASCII bytes for each name and valuehttp.snapshot
8Exact body byteshttp.snapshot
9One nonnegative integer epoch-seconds sampletime.wall-clock
10–12Key ID, key version, trust domaincrypto.hmac-sha256
13inbound or outboundhttp.snapshot

Exports are memory, start(): i32, resume(length: i32): i32, and zero-argument i32 accessors buffer, capacity, output_ptr, output_len, aux_ptr, aux_len. Pointers are byte offsets in the private memory. Buffer capacity is 65,536–262,144 bytes. start/resume return a command: 0 completes with UTF-8 output, 1 hashes output with SHA-256, 2 signs output with HMAC-SHA256, 3 verifies output using the 32-byte tag at the auxiliary pointer. Commands 1/2 write 32 result bytes at buffer; command 3 writes one byte (0 or 1). The host then calls resume with that length. At most four crypto commands run per operation and all output/crypto inputs are at most 65,536 bytes. Every pointer is bounds-checked.

The host checks request activity and capture lifetime before and after async work and before resumption. Late values never resume or settle a cancelled request. Key staging, input/output copies, returned crypto bytes and the whole private Wasm memory are wiped on completion/failure once host work settles. Provider errors use fixed codes/messages. Native traces and JavaScript effect observations exclude payloads and results for these operations, regardless of package-supplied redaction declarations. Returned application responses remain application-owned output. This contract does not preempt a looping trusted Wasm function or promise transactional rollback.

JavaScript source packages with runtime artifacts carry an optional hashed pulse.package-runtime-artifacts.v1 record. Native artifacts retain the existing private realization artifact container. Node launchers validate stored hashes before execution; packages without these artifacts retain their prior layout.

Focused evidence: node wasm/scripts/run-wasm-tests.cjs --task package-runtime --task provider-toolchain --task external-lowerers --task package-exports and packages/runtime/test/package-runtime.test.ts. Protocol-specific parity and host integration fixtures remain in the independent protocol package.