Skip to main content
Version: 0.1.0-alpha.5 (alpha)

Exact web release admission

The directory catalog admits signed browser and SSR packages using independent tenant-scoped publication references, following ADR-0038. The shared HTTP listener, renderer adapter, Angular builder and public web management operations integrate this catalog API.

Package profile​

Use the existing OCI package kinds: browser-assets for client/prerendered documents, or ssr-package for a renderer and its client assets. Add the exact Asset-role JSON layer metadata/web-application.json, described by the web application schema.

The closed lsf.web-release.v1 document contains formatVersion: 1, profile, assetsDigest, assets, routes, and an optional renderer. The public asset table is sorted by canonical URL path. Each entry names one public/ layer and its exact SHA-256, byte size and MIME type. A path cannot alias a second layer. Both URL and layer suffix must match the supported MIME type. Package metadata, SBOMs, build receipts and renderer code stay private, including Asset-role layers outside this table.

Each route names one exact path and a client, prerender, or server mode. Client/prerender routes name a declared HTML asset. Server routes require the package's renderer and cannot name an unrelated document. Hostnames, tenant selection, public authentication and deployment aliases are host policy; the package cannot grant them to itself.

An SSR renderer names the package entrypoint's Renderer-role Wasm layer, its digest and size, the exact public asset-tree digest, and a profile digest. renderer_profile_digest binds the public WIT bytes, host ABI profile, engine profile and renderer-specific restrictions. Native target/CPU configuration and authenticated AOT identity remain separate preparation inputs.

wasm-web-buffered-v1 validates the exact public async latent:web/application@0.1.0 export and the closed supported context imports. Validation uses bounded structural and WIT checks without compiling or executing untrusted Wasm. angular-ssr-component-v1 now validates the composed public adapter with separate finite binary-work ceilings and the unchanged public WIT limits. The installed renderer requires an explicit node profile. The original private synchronous qualification ABI still fails the public interface check. Checked web publication is not yet a callable capsule or deployment grant; #226 supplies that authority integration.

Immutable publication and evidence bytes are staged under the bounded catalog writer before taking the final policy fence. This lets the existing control owner renew its finite clock lease during large payload I/O. The current grant is checked before staging and again under the policy fence before the durable HEAD commit. Expiry or revocation during staging cannot publish positive authority; an interrupted mutation retains its storage charge and requires restart plus operation lookup.

Publisher, builder and inventory policy​

SupplyChainAuthority::verify_web and recover_web use the same approved publisher, builder, SBOM, tenant, generation, clock-lease and retirement owners as capsule admission. Every request carries exact manifest/configuration/layers, one publisher envelope and one builder envelope, with SBOM presence determined by policy. Registry discovery does not substitute for these checks.

Web builder statements use the distinct predicate https://latent.dev/web-provenance/v1 and build type https://latent.dev/build/web-package-assembly/v1. The builder policy must explicitly approve that type and its source requirement. The web observation and statement describe supplied-file assembly: lsf-web-package-assembly, recipe version 1, explicit-supplied-files. Source origin remains operator asserted; the revision is the supplied snapshot digest's hex, not a verified remote Git commit.

The observation authenticates the ordered output descriptors, count and byte sum. Its output hash excludes only package/sbom.cdx.json and package/build-inputs.json to avoid cycles. The exact final signed package binds both excluded layers. Required materials are source-snapshot, build-recipe, toolchain-config and package-assembler; names are unique and bounded. Hermetic execution is false and dependency completeness is explicitly limited. The verifier trusts an approved builder's assertions; the unsigned observation and the signer API do not themselves observe a compiler. The maintained observed Angular recipe uses the separately approved build type https://latent.dev/build/angular-component/v1, with closed composition parameters and exact renderer, toolchain, source and bundle materials. Reproducibility is a separately checked builder assertion, never inferred from a repeated input list.

The web admission receipt uses lsf.web-admission.v1 and binds tenant, package, web manifest, public tree, publisher/builder/evidence/SBOM identities and policy/time history. It contains no release field. Historical receipts never confer permission; recovery checks original structure/content before classifying current policy denial, then reverifies the selected evidence against current trust.

Exact selection and lifecycle​

The trusted host authenticates and authorizes the scope and actor before calling these DirectoryArtifactRepository methods:

MethodResult
publish_web_packageAdmit an exact package with expected generation zero.
select_web_publicationRetain one coherent, currently eligible association.
read_web_asset / read_web_rendererRead the selected integrity-checked blob with retained capacity ownership.
transition_web_publicationRevoke or retire with an exact generation precondition.
renew_web_evidenceReplace evidence for the same retained package and advance its generation.
reverify_web_publicationRefresh a positive proof from selected raw evidence without rewriting history.
web_publication_status / web_operation_statusInspect bounded historical status or a retained operation result.
reclaim_uncommitted_web_contentCollect bounded uncommitted packages and obsolete evidence revisions.

Mutations require an operation ID and compare-and-swap generation. The host's response preflight runs before staging or lifecycle writes. An exact retained retry returns its original receipt; changing the request under that scoped ID conflicts. Receipt retention is finite, so absence is not proof of rollback. After an uncertain write, reopen and inspect operation/lifecycle state before retrying. Terminal records cannot be revived by renewal, refresh or rollback.

Two authorized tenants can admit identical package bytes. Correcting an embedded SBOM produces a separate immutable package/publication even when the renderer is unchanged. Revocation, selected evidence and generations stay independent. The legacy capsule indexes and RPC component fields keep their original meaning; a browser package is never assigned a placeholder component.

Call with_current at response or renderer-start acceptance. It checks the repository incarnation, captured generation, authenticated tenant and current admission while holding the relevant synchronous fences. It is not a lock held through an async network send. Already accepted work may complete after a later revocation, retaining its real read/activation charges through cleanup. Dropping the repository invalidates its held tokens even after a new owner reopens it.

WebSelection::asset_url returns /_lsf/assets/<publication:sha256:...>/<asset path>. Changing a deployment does not mutate this association. Old documents can fetch their original assets while that publication remains eligible; revocation, retirement or expired authority can deny a subsequent fetch. No historical URL grants perpetual public access.

Do not embed the package's own final ID in its stored HTML: that creates an identity cycle. The serving integration must bind the selected asset base at response time, or serve a pinned document with supported relative references. Arbitrary HTML is not rewritten or sanitized by catalog admission; the renderer, browser-serving and browser-defense tickets define those response semantics.

Storage, bounds and recovery​

Web publications share the catalog's OS root lock, single admission work slot, publication writer, index budget and immutable blob store. No dormant web application owns a process, thread, timer, listener, read buffer or renderer.

Before the first web payload, ADMISSION_MODE becomes lsf-enforced-admission-web-v2. Older binaries accepting only v1 refuse this root before shared-content recovery/GC. Unchanged capsule-only roots keep v1. This adds a web storage profile without reinterpreting the existing capsule lifecycle format 2 or the control-plane route catalog's separate format version.

Original exact files are linked under web/publications/<publication hex>/. The bounded canonical web/HEAD stores sorted lifecycle records, original admission-completion digests and finite operation receipts, followed by a checksum. Each JSON line has its own typed and lexical bounds. Only the durable head is authoritative. .next files are bounded, charged and never interpreted as committed permission. Missing committed history or changed original bytes fails closed, including when a publication is already revoked or proof-expired.

Renewed proof material lives in separately bounded web/evidence/<digest>/ directories. Current and terminal rows retain their original package and selected evidence. Bounded explicit collection can remove uncommitted packages and superseded/interrupted evidence; shared zero-reference blob collection remains the existing catalog maintenance operation. A failed deletion retains its conservative charge until recovery.

BoundCurrent profile/default
Web manifest256 KiB; 252 public assets and 128 exact routes
Public asset8 MiB each; 16 MiB aggregate
Renderer input32 MiB; lower repository limits still apply
Catalog indexShared 250,000 entries / 64 MiB conservative metadata; lifecycle entry limits also apply
Content storageShared 4 GiB conservative blob plus publication-link exposure
Recent web operations256 scoped receipts; lower lifecycle settings apply
Evidence revisions2 MiB each / 256 MiB aggregate by default, including interrupted revisions
Active web reads/selections32 owners / 64 MiB retained capacity across the repository

The asset ceiling reserves four of the package's 256 layers for metadata, SBOM and its input receipt. See the static-site inventory profile for the adapter's matching count and byte limits.

These are explicit ownership ceilings, not total RSS or allocated filesystem blocks. One bounded control operation may additionally hold its input, recovery or replacement snapshot. Head replacement reserves old and next-file exposure. Selection and blob reads charge their metadata and buffer before allocation; copied eligibility tokens retain the lease until the last consumer releases it. Asset reads anchor the stored header to the retained lifecycle completion and verify the selected blob. Admission and restart verify the full package.

The tests cover real publisher/builder/SBOM admission, browser and public async SSR packages, cross-tenant/package coexistence, private-layer denial, shared quotas, retained read leases, preflight rejection, exact retries, evidence renewal and collection, revocation, corrupt/missing history, and failures on both sides of the durable head commit. The tiny public SSR fixture checks compatibility; it does not claim Angular runtime integration or a performance benchmark.

CI also pushes both package kinds and all three evidence kinds through the owned TLS/authenticated OCI registry, discovers and retrieves digest-pinned evidence, admits under real approved policy, moves a mutable tag and reopens the catalog. Registry retention tags keep the test's remote manifests available; a digest alone does not compel a registry to retain untagged content. Admitted catalog content remains locally retained independently of registry tag changes.

After tools/validate_contracts.sh builds the public fixture, run:

python3 tools/run_oci_registry_tests.py \
--web-admission-component target/capsules/web-contract/component.wasm

The runner uses a disposable bounded registry and fresh public test credentials. The schema fixtures are deliberately synthetic; Rust/OCI tests verify actual signatures and the built public web component. No load benchmark is required.