# Application artifact references

Keep exact application source and scoped drafts behind bounded immutable references.

# Browser application artifact references

`packages/block-sdk/browser-application-artifacts.js` defines immutable source and draft references, with verification when a trusted host loads their bytes. These are additive contracts. They do not install the browser application runtime, implement storage or grant access. Generic package admission and execution remain unavailable for `browser-application`.

An experimental host can represent source with the small descriptor below. Existing saved-project validation must be explicitly extended before it accepts this form. The full application remains an immutable, separately stored artifact; hydrated source is ephemeral and should not be copied back into the saved project or collaboration operations.

```js
const {descriptor, bytes} = await createBrowserApplicationSourceArtifact(source, {
  entityId: exactSourceEntityId,
});
// descriptor = {
//   manifest,
//   sourceRef: {
//     schemaVersion: 1, storage: 'block-source-v1', entityId,
//     version, digest, byteLength,
//   },
// }
```

`bytes` is a `Uint8Array` containing `stableStringify(source)` encoded as UTF-8. Its SHA-256 equals the existing SDK `digest(source)`; `sourceRef.digest` always identifies the full source, including the application assets and optional fixtures. The version must equal the manifest version. No URL, bucket key, alternate storage type or floating `latest` reference is accepted. Store exactly these bytes, without reformatting JSON. Creation fully validates the application bundle and every asset. `defineBrowserApplicationSourceDescriptor` validates and freezes a detached descriptor without downloading source.

```js
const source = await hydrateBrowserApplicationSource(descriptor, {
  authorize, fetchBytes, signal, timeoutMs: 30_000,
});
```

The host supplies both callbacks; neither callback is available to guest application code:

```ts
authorize(binding, {signal, phase}) => true | false | Promise<boolean>
fetchBytes(reference, {signal, maxBytes})
  => Uint8Array | AsyncIterable<Uint8Array> | Promise<either>
```

`authorize` runs before fetching and after all validation. Only literal `true` grants access. For source, its binding is `{kind: 'source', descriptor}`. The trusted host must check the current actor, accessible entity, exact published or retained version, digest, licensing, team scope and revocation, as applicable. It must resolve storage keys itself from this authorized identity. Every hydration repeats these checks; there is no cache. A returned source grants no continuing permission to execute later: execution must check access again at its own acceptance boundary.

The helper verifies the declared byte count, strict UTF-8, canonical JSON, full-source SHA-256, exact manifest equality, full browser application contract and every bundled asset. It rejects malformed JSON, duplicate/prototype keys, BOMs, extra descriptor fields and corruption. Returned descriptors and hydrated values are detached and deeply frozen. The original inline source is unchanged.

## Project application draft state

The host assigns the artifact identity and revision. Scope comes from an authenticated project placement, never from guest claims.

```js
const {draft, bytes} = await createBrowserApplicationStateArtifact(state, {
  scope: {projectId, placementId},
  packageDigest, revision, id,
});
// draft = {
//   schemaVersion: 1, packageDigest, revision,
//   stateRef: {
//     storage: 'project-application-state-v1', id, sha256,
//     byteLength, mime: 'application/json',
//   },
// }
```

The exact canonical artifact payload is `{schemaVersion: 1, id, scope: {projectId, placementId}, packageDigest, revision, state}`. Its SHA-256 equals the SDK digest of that full payload. State must be a plain JSON object. The opaque identifiers accept 1–128 ASCII letters, digits, dots, underscores and hyphens, starting with a letter or digit; reserved prototype names and `latest` are rejected. Revisions are bounded nonempty strings. `defineBrowserApplicationDraft` validates and freezes the small reference without loading state.

```js
const state = await hydrateBrowserApplicationState(draft, {
  scope: {projectId, placementId},
  authorize, fetchBytes, signal,
});
```

State authorization receives `{kind: 'state', scope, draft}` with the same two phases. Hydration verifies byte length/hash and every binding inside the stored payload: project, placement, package digest, revision and artifact identity. It returns only frozen state. A missing, corrupted, stale, revoked or cancelled load does not mutate the saved reference; the caller should preserve the recoverable draft and report the failure.

## Bounds and host responsibilities

| Boundary | Limit |
| --- | --- |
| Encoded source artifact | 180 MiB |
| Source descriptor | 64 KiB |
| Complete encoded state artifact, including its envelope | 4 MiB |
| Draft descriptor | 4 KiB |
| Hydration timeout | 30 seconds default, 120 seconds maximum |

The existing bundle limits still apply: at most 2,048 regular-file assets, 64 MiB per asset and 128 MiB decoded in aggregate. The guest's 4 MiB message cap remains separate. Existing inline package, custom-view, Step-state and collaboration limits are unchanged; a guest state near its message cap may exceed the artifact cap once its envelope is encoded.

Prefer an asynchronous byte stream for large source reads. The helper rejects excess bytes before retaining the excess chunk, limits streams to 65,536 chunks, and attempts to close a failed iterator. A buffered callback must enforce `maxBytes` before allocating/downloading the entire response; checking an already allocated response cannot prevent that allocation. The host must propagate the supplied signal to its underlying I/O. Abort and timeout reject pending callbacks even if those callbacks ignore cancellation, and late results are discarded. Timers and JSON/asset verification are cooperative bounds, not CPU or process isolation.

The host still owns immutable writes, authoritative project/package/placement checks, atomic revision acceptance, permission checks at storage and execution boundaries, quotas, retention, export/backup, deletion and garbage collection. This helper supplies no actor authentication, database transaction, encryption, worker containment, durable state route or execution receipt. It does not make an application production-ready.

Focused tests include the real pinned Method Draw package: its multi-megabyte original source becomes a descriptor below 8 KiB, hydrates byte-equivalently, and remains rejected by ordinary package admission. Other tests cover exact state size boundaries, stream overflow, cancellation, timeout, authorization revocation, wrong identities, manifest/assets corruption and malformed JSON. Run `npx vitest run packages/block-sdk/browser-application-artifacts.test.js` without network or paid services.
