SpinStream paper

Artifact Container

Technical specification for container structure, packaging, metadata, assets, and canonical export behavior.

SpinStream Artifact Container Spec

Artifact Container Definition

This definition is derived from the implemented export/mint pipeline in `script.js`, the mint proxy route in `stripe-checkout/index.js`, and ledger recording workers.

1) Container structure

The frontend export pipeline builds a multipart payload containing the artifact container components:

- `index.html` (canonical exported HTML snapshot)

- `html` (same HTML, backward-compatibility field)

- `meta.json` (attached under `file`)

- `metadata.json` (same JSON attached under `metadata`)

- `asset:<role>:<fileName>` for each uploaded/exported asset

- optional `ownerKey`

This is assembled in `freezeAndUpload()` and POSTed to `/api/mint`.

On-storage layout at finals

The expected minted layout is

```text

/finals/{slug}/

index.html

meta.json

assets/{...}

```

Notes from implementation

- `script.js` treats the returned URL as `finalUrl`/`mintedUrl` and derives cover at `/finals/{slug}/cover.png`.

- The repo’s `/api/mint` route is a proxy to an external mint worker (`MINT_WORKER_ORIGIN`), so exact backend write semantics for *all* files are external, but frontend and docs consistently operate on `index.html + meta.json + assets/*`.

2) Artifact identity mechanism

Artifact identity in this codebase has three distinct parts

1. **Mint ID (frontend identity token)**

- Primary source: `mintIdInput` (user-provided, normalized/validated).

- Fallback generation: `generateMintId()` uses timestamp components + `Math.random()` suffix.

- Persisted in DOM attributes (`body.dataset.mintId`) via `ensureMintMetadata()`.

2. **Final URL / slug (backend identity path)**

- Frontend receives minted URL from mint response fields (`finalUrl`, `mintedUrl`, etc.).

- Slug generation itself is **not** implemented in this repository; it occurs in the external mint worker behind `/api/mint`.

3. **Timestamp metadata**

- `ensureMintMetadata()` sets `mintedAt` from `mintDateInput` if valid, otherwise current time.

- `mintDate` is also captured in mint metadata fields and meta tags.

3) Deterministic export mechanism

Export is deterministic relative to finalized DOM state + selected files at mint time:

- `buildInlineExportHtml()` explicitly snapshots a cloned sanitized DOM (`document.documentElement.cloneNode(true)` then `outerHTML`).

- It injects explicit exported-state marker(s)

- `data-exported="true"`

- `data-mint-state="minted"`

- It applies sanitization guards (`assertNoHelperIdentifiers`, `verifyExportSanitization`) and aborts mint on sanitization failure.

- It hides editor-only controls and enforces exported view behavior via injected export-only CSS and metadata tags.

Determinism boundaries

- Deterministic container assembly is evident in frontend export code.

- Mint ID fallback uses randomness (`Math.random()`), so ID creation is not purely deterministic when auto-generated.

- Backend slug derivation and any server-side transforms are external to this repo.

4) Metadata schema

Metadata exists in three layers

1. **Runtime metadata object** from `getMintMetadata()`:

- `mintId`, `title`, `artist`, `description`, `mintDate`, `type`, `tags`, `tagsRaw`, `license`, `links`, `coverImageFile`, `coverImageName`.

2. **Embedded HTML metadata** in exported `index.html` via `addExportMetadataToClone()` meta tags:

- `spinstream:mint-id`

- `spinstream:mint-title`

- `spinstream:mint-artist`

- `spinstream:mint-date`

- `spinstream:mint-description`

- `spinstream:mint-type`

- `spinstream:mint-tags`

- `spinstream:mint-license`

- `spinstream:mint-links`

3. **Mint worker payload metadata JSON** (`meta.json` and `metadata.json` carry same structure):

```json

{

"artistName": "...",

"mintMetadata": { "...": "..." },

"assetRoles": { "fileName": "role" },

"exportAssetPaths": { "role": "assets/fileName" }

}

```

Additionally, exported `index.html` includes a lock-gate script that fetches `./meta.json` and checks `status`/`expiresAt` to determine lock-screen rendering.

5) Ledger integration

Ledger anchoring path expects

```json

{ "artifactHash": "0x...64hex", "mintedUrl": "https://..." }

```

Two worker surfaces implement record calls

- `workers/spinstream-ledger/src/index.js`

- accepts `/api/record-ledger`, `/record-ledger`, `/ledger/record-ledger`, `/`

- validates required fields

- executes `contract.record(artifactHash, mintedUrl)`

- returns tx hash, block number, and ledger timestamp from Polygon block data

- `stripe-checkout/index.js` (`/api/record-ledger`)

- validates hash/url format

- executes `contract.record(artifactHash, mintedUrl)` with timeout handling

- returns chain metadata and tx details

Important implementation fact

- Artifact hash **generation** is not present in `script.js` mint/export path in this repository snapshot. The ledger workers consume `artifactHash`; they do not derive it from artifact bytes locally.

6) Viewer structure (`index.html` as artifact viewer)

The finalized artifact viewer is `index.html` served from the minted finals URL.

Viewer behavior comes from export output + runtime script behaviors

- Export enforces artifact mode with `data-exported="true"` and hides creation UI.

- Export preserves/rewrites asset references to stable paths (e.g., `assets/...` for uploaded media, absolute paths for system assets as needed).

- Export keeps script wiring so the page remains interactive as a viewer, while export markers and CSS suppress editor controls.

- Lock-gate logic in exported HTML can replace body content with an expiration screen using metadata from `meta.json`.

Net effect

- `index.html` is both the artifact payload and the primary artifact viewer, with state controlled by explicit export markers and metadata, not editor-only mode assumptions.