# Types and compatibility

The shared type system, media representation, semantic roles, and safe conversions.

## Supported types
- `text`
- `number`
- `integer`
- `boolean`
- `json`
- `object`
- `image`
- `video`
- `audio`
- `asset`
- `script`
- `transcript`
- `brief`
- `bible`
- `shot-plan`
- `panel-document`
- `board-document`
- `scene-document`
- `timeline`
- `timeline-edit`
- `file-bundle`
- `workspace-edit`
- `timeline-range`
- `research`
- `character`
- `location`
- `style`
- `pipeline`
- `approval`
- `scene`
- `shot`
- `mask`
- `depth`
- `pose`
- `metadata`
- `project-context`
- `file`
- `document`
- `table`
- `url`
- `date`
- `color`
Any listed type can use an array suffix, for example `shot[]` or `image[]`.

## Runtime values
| Type family | Value |
| --- | --- |
| text | A string up to 1,000,000 characters |
| number / integer | A finite number, with integer and bounds checks as declared |
| boolean | true or false |
| json | Safe finite JSON data |
| image | Portable media reference or bounded RGBA image |
| video / audio / asset | Portable media reference |
| file-bundle | Validated file or ZIP download data; media requires host authorization |
| Production documents | JSON object with schemaVersion: 1 |
| Arrays | Array whose members match the base type |

A media reference is `{assetId, versionId, kind}`. A decoded image is `{width, height, data}`, with width × height × 4 integer RGBA channels. The current pixel ceiling is 1,048,576.

## Compatibility rules
The raw `compatible` check requires exact type matching except **integer → number**. Arrays match their full declared type. If an input declares a semantic role, its producer must declare the same role. The host connection registry can additionally apply the guarded conversions below.
```js
compatible({type: 'image', role: 'source_image'},
           {type: 'image', role: 'source_image'});
// { compatible: true, adapter: null }
```

## Primary ports
Declare one primary input and output for automatic insertion. A sole port is inferred for older packages. Multiple candidates without a declared primary do not produce an arbitrary connection.

## Safe connection conversions
StillMade can insert a versioned, deterministic conversion between compatible
Steps. This is part of the host connection contract; it does not add a visible
Step or ask the user to wire ports. Advanced connection controls show the
conversion and preserve its version when the Project Type is saved.

`compatible(outputPort, inputPort)` remains the strict raw-value check: exact
types and matching required roles, with the existing integer-to-number widening.
Use `connectionCompatibility(outputPort, inputPort)` to discover a supported
connection conversion. Pass the complete port objects, including `role`.
`validateConnectionAdapter(outputPort, inputPort, {permissions, adapter})` also
checks the receiving Block's `manifest.permissions.project` grants and the saved
adapter pin. Static compatibility is a possibility, not a promise that any value
can run: the actual output and authorized context must pass runtime validation.

| Conversion | What the host accepts | Receiving Block permissions |
| --- | --- | --- |
| integer → number | A valid whole number, preserving its value | None |
| image / video / audio → asset | A saved media reference of that kind | None |
| asset → image / video / audio | A media reference whose actual `kind` matches the requested type | None |
| scene → shot[] | The scene's authorized shots, in its recorded project order | `context.scenes.read`, `context.shots.read` |
| shot → image | That shot's unambiguous, current, host-selected image, present in authorized project assets | `context.shots.read`, `context.assets.read` |

The conversion IDs are `integer-to-number`, `image-to-asset`, `video-to-asset`,
`audio-to-asset`, `asset-to-image`, `asset-to-video`, `asset-to-audio`,
`scene-to-shots`, and `shot-to-selected-image`. Each is currently version `1`.
New cross-type Project Type connections must include the exact pin:

```json
{
  "from": { "stage": "source", "port": "asset" },
  "to": { "stage": "process", "port": "image" },
  "adapter": { "id": "asset-to-image", "version": 1 }
}
```

The builder adds this metadata automatically. Existing direct and
integer-to-number connections remain valid without an explicit pin. Changing a
pin changes the workflow identity and invalidates connected-run and automation
reviews. The host uses `adaptConnectionValue(outputPort, inputPort, value,
{context, permissions, adapter})` before validating the receiving input; a
conversion returns copied data and cannot modify the project.

Roles are preserved, never invented: a `character_reference` image cannot become
a `source_image`. Scene and shot conversions use scoped host context rather than
trusting a Block's embedded shot list or selected-image claim. Missing,
ambiguous, stale or unauthorized selections stop the run. StillMade does not
choose the first image or fall back to a historical version. Inline RGBA pixels
must be saved as a media reference before a media-to-asset conversion. No
conversion here downloads, decodes, generates, or uploads media. Video-to-audio,
video-to-frames, model inference and other semantic transformations still require
an explicit supported capability and its own execution permissions.

Additional shared types include `scene`, `shot`, `shot[]`, `mask`, `depth`, `pose`,
`metadata` and `project-context`. Their current values are versioned JSON documents
(`schemaVersion: 1`); declaring a context port does not grant project access.
Scoped context and conditional/batch execution are available for supported SDK
Blocks. ComfyUI uses the explicit account connection and review described below;
unattended remote automation and additional media adapters remain separate work.

## Context bindings
| Field | Exact type |
| --- | --- |
| script | script |
| transcript | transcript |
| shots | shot[] |
| shotPlan | shot-plan |
| scenes | scene[] |
| characters | character[] |
| locations | location[] |
| styles | style[] |
| assets | asset[] |
| files | file[] |
| generations | metadata[] |
| versions | metadata[] |
| timeline | timeline |
| editor | timeline |
| canvas | pipeline |
| blueprint | bible |
| panels | panel-document |
| board | board-document |
| animation | scene-document |
| metadata | metadata |

## Downloadable Block results
Declare an output with `type: "file-bundle"` (or `file-bundle[]`). The shared Block
preview automatically offers Download, including when the Block has a custom
view. A file result is inert data: it grants no filesystem access and never mounts
HTML, runs code or triggers a download before the user clicks. This output type
is separate from the `.stillmade-block` archive used to install the Block itself.

```json
{
  "schemaVersion": 1,
  "format": "file",
  "name": "notes.txt",
  "files": [{"path": "notes.txt", "text": "My notes"}]
}
```

For ZIP output use `format: "zip"`, a `.zip` name, and multiple files. Each member
contains exactly `{path,text}` or `{path,source}`. A `source` retains the complete
selected media reference (`kind`, `assetId`, `versionId`, `url`, and its existing
role/metadata); never invent a reference or substitute a URL. Media entries need
`asset.read` in `manifest.permissions.project` and an authorized selection from
the host's existing media inputs or declared project context. Declaration alone
is not permission. The host checks the full reference identity and current
account/selection before transferring bytes. Detached history views can download
text-only results; media ZIPs need the Block's current authorized media context.
Unavailable, unselected or unsupported media aborts the ZIP instead of returning
an archive with missing members. The host does not install dependencies.

File downloads contain exactly one text member whose path equals `name`.
Supported text suffixes: `.txt`, `.md`, `.json`, `.csv`, `.xml`, `.fcpxml`, `.srt`,
`.vtt`. ZIP media suffixes: `.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`, `.mp4`,
`.webm`, `.mov`, `.mp3`, `.wav`, `.ogg`, `.m4a`, `.flac`, `.bin`.
Use safe relative ASCII paths, at most 180 characters, with no traversal,
backslashes, empty segments, trailing dots, reserved device names or case
collisions. Include 1–256 files and at most 3,000,000 UTF-8 text bytes total;
the existing runtime JSON/memory limits also apply. Media transfers use the
host limits above. Files are delivered as downloads with a non-executable MIME
type; no archive member becomes a host filesystem path.

`isFileBundle(value)` and the normal `validate`/`test`/`pack` commands enforce this
contract. Include normal and edge fixtures containing the complete expected
file data. The [File notes example](/block-sdk/examples/file-notes.stillmade-block)
is a complete portable package using this output. Import it, change the text,
run it and click Download. No custom download code is needed in the Block view.

## Original media file admission
The SDK exports `MEDIA_IMPORT_FORMATS`, `MEDIA_IMPORT_MAX_BYTES` and
`inspectMediaImportFile({name,size,type?})` for trusted hosts handling files the
user selected. The inspector returns `{kind,extension}` and rejects unsupported
extensions, conflicting MIME categories, empty/nonintegral sizes and files above
50 MiB. It does not read file bytes, grant filesystem access or authorize an upload.

Supported originals are PNG/JPG/JPEG/WebP/GIF images; MP4/MOV/WebM videos; and
MP3/WAV/M4A/AAC/OGG audio. Each format specifies the canonical upload MIME type.
An absent MIME type or `application/octet-stream` may use the recognized extension.
The existing authenticated media upload endpoint uses the same table and limit,
preserves original bytes through its signed PUT, and returns a durable media URL.
No image downscaling is applied to audio or video originals.

The host's existing Canvas upload adapter accepts optional `signal` and
`assertCurrent` controls. The callback must throw when the owning session is no
longer current; checks run before upload and before caching or returning results.
Isolated previews retain local files without a cloud request. A file selected in
a generic Block preview is still temporary unless a supported host persistence
flow explicitly saves it; declaring `asset.create` alone does not upload it.

These utilities extend the shared upload boundary. They are not a new guest file
API or proof that the native Import media workspace is a portable sandbox Block.
