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.