wrap spec v0.0.1

WRAP Format Specification

Version: 0.0.1 (Draft)

Status: Pre-release

Date: 2026-03-12

Abstract

WRAP defines a convention for self-contained, interactive HTML documents with a machine-readable manifest. A conforming wrap is a valid HTML file that any browser can open. The manifest declares metadata, capability requirements, security tier, and extension data — making the document self-describing for humans, tools, and AI.

1. Conventions

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

2. Design Principles

  1. Valid HTML first. A wrap MUST be a valid HTML document that renders in any modern browser without special tooling.
  2. Deny by default. Capabilities not declared in the manifest are assumed absent. Security tiers not understood by a viewer are treated as restrictive.
  3. Graceful degradation. Unknown manifest fields are ignored. Unknown module types are skipped. Unknown security tiers refuse to render. The wrap degrades, never crashes.
  4. AI-native. The format is designed to be generated, read, and modified by LLMs as easily as by humans.

2.1 Viewer Environments

WRAP defines an artifact format first. A conforming wrap is still a valid HTML document that can be opened directly in a browser.

Different viewers MAY provide different runtime behavior around the same artifact. For example, a hosted viewer MAY add compatibility layers for navigation, networking, or embedded-resource resolution that are not available when the same file is opened directly from disk.

This specification intentionally does not standardize product-specific compatibility tiers, export modes, or runtime analysis labels in v0.0.1. Viewers MAY derive such metadata under extensions, but that metadata is not part of the WRAP core contract unless standardized in a future spec revision.

3. File Identity

3.1 File Extension

The canonical file extension is .html. The extension .wrap is RESERVED for use with dedicated viewers and tooling.

A .wrap file MUST be a valid HTML document that can be renamed to .html and opened in any conforming browser.

3.2 MIME Type

The registered MIME type is application/vnd.wrap (IANA vendor tree). When served over HTTP, servers SHOULD use this MIME type. When opening local files, browsers will treat the file as text/html, which is correct — the file IS valid HTML.

4. File Structure

A conforming wrap MUST follow this structure:

<!DOCTYPE html>
<html lang="{language}">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{title}</title>

  <!--  1. Manifest (REQUIRED) -->
  <script type="application/json" id="wrap-manifest">
  { ... }
  </script>

  <!-- 2. Content Security Policy (RECOMMENDED) -->
  <meta http-equiv="Content-Security-Policy" content="...">

  <!-- 3. Styles (OPTIONAL) -->
  <style id="wrap-styles">...</style>
</head>
<body>
  <!-- 4. Static fallback (RECOMMENDED) -->
  <noscript>...</noscript>

  <!-- 5. Content root (REQUIRED) -->
  <div id="wrap-root">
    ...
  </div>

  <!-- 6. Embedded data (OPTIONAL, repeatable) -->
  <script type="application/json" class="wrap-data" data-id="{id}">
  { ... }
  </script>

  <!-- 7. Bootloader (REQUIRED) -->
  <script id="wrap-bootloader">
    ...
  </script>
</body>
</html>

4.1 Manifest Element

The manifest MUST be a <script> element with:

It MUST appear in the <head> element. It MUST contain valid JSON conforming to the manifest schema (Section 5).

The manifest MUST be parseable without executing JavaScript — tools can extract it by parsing the HTML and reading the script element's text content.

4.2 Content Security Policy

Conforming generators SHOULD include a CSP <meta> tag. The CSP SHOULD, at minimum:

If the wrap declares permissions.network: "none", the CSP SHOULD NOT include any external domain allowlists.

Limitations: Because the bootloader is an inline script, 'unsafe-inline' is required for script-src. This means the CSP does not prevent injected inline scripts from executing — its primary value is restricting external resource loading (no connections to undeclared domains, no external script includes). For open tier wraps this is acceptable — the file is plaintext HTML with no stronger guarantee. For signed tier wraps, the signature is the real integrity mechanism, not the CSP. The CSP is a defense-in-depth layer against network exfiltration, not a substitute for signing.

4.3 Static Fallback

A conforming wrap SHOULD include a <noscript> element providing a static representation of the content. This enables:

4.4 Content Root

The primary content container MUST be a <div> with id="wrap-root". The bootloader renders interactive content into this element.

4.5 Embedded Modules

Wraps embed module content directly in the HTML file. The embedding format depends on the data type:

Text formats (JSON, CSV) use <script> elements with the appropriate MIME type:

<script type="application/json" class="wrap-data" data-id="revenue">
[{"quarter": "Q1", "amount": 2400000}]
</script>

<script type="text/csv" class="wrap-data" data-id="customers">
name,email,plan
Acme Corp,acme@example.com,enterprise
</script>

Binary formats (SQLite, WASM, images, fonts) use base64-encoded <script> elements with data-encoding="base64":

<script type="application/wasm" class="wrap-data" data-id="analysis-engine"
        data-encoding="base64">
AGFzbQEAAAABBwFgAn9/AX8DAgEABwcBA2FkZAAA...
</script>

<script type="application/x-sqlite3" class="wrap-data" data-id="survey-db"
        data-encoding="base64">
U1FMaXRlIGZvcm1hdCAzABAAAQEAQCAgAAAA...
</script>

Compression: Binary content SHOULD be compressed before base64 encoding to reduce file size. Generators MAY use data-encoding="base64+gzip" to indicate that the base64 payload is gzip-compressed. Bootloaders SHOULD decompress via DecompressionStream where available, falling back to uncompressed base64 if the API is not supported.

Media assets (images, fonts, video) MAY use either:

Data URIs are simpler for small assets used directly in markup. <script> embedding is preferred for assets managed by the bootloader or referenced by multiple elements.

All embedded module elements share these attributes:

4.6 Bootloader

The bootloader MUST be a <script> element with id="wrap-bootloader". It MUST appear after the content root in document order (so the DOM is available when it executes).

The bootloader is responsible for:

  1. Parsing the manifest
  2. Validating the security tier
  3. Initializing declared modules (embedded and fetched via src)
  4. Verifying integrity hashes for fetched modules
  5. Resolving local module URLs for runtime resource loading
  6. Rendering interactive content into wrap-root
  7. Exposing declared tools (Section 5.11) as callable functions
  8. Firing lifecycle hooks

The bootloader SHOULD be as small as practical. For open tier wraps, a minimal bootloader that parses the manifest and initializes content is sufficient.

Reference bootloader: The canonical bootloader for WRAP v0.0.1 is provided at spec/v0.0.1/bootloader.js. A generated minified form may be provided at spec/v0.0.1/bootloader.min.js. Generators SHOULD embed the version-matched bootloader inside the <script id="wrap-bootloader"> element, then append application-specific code after the IIFE. The convenience path spec/bootloader.js MAY point to the latest bootloader, but versioned tooling and generated wraps MUST use the versioned copy for the declared wrap_version. The reference bootloader handles manifest parsing, version validation, security tier checking, module loading (embedded and src-fetched with integrity verification), secrets API setup, and the wrap:ready lifecycle event.

window.wrap is exposed immediately, but for wraps with src modules, application code MUST wait for module fetches to complete before using module data. Two mechanisms are provided:

For wraps with no src modules (pure offline), both resolve immediately — there is no behavioral change from the synchronous model.

The reference bootloader also intercepts local fetch() requests that target declared modules, and SHOULD expose a helper such as wrap.resolveURL(path) for non-fetch consumers that need a URL string (for example audio players or 3D model loaders).

When permissions.storage is true, the bootloader MUST also initialize wrap.store (Section 7.1) with a functional implementation. The reference bootloader uses localStorage as the backing store. Viewer implementations MAY override wrap.store with enhanced backends (shared state, cloud sync, real-time collaboration) while preserving the same API contract.

The bootloader MUST also initialize wrap.runtime (Section 7.2). If no host runtime is present, wrap.runtime MUST default to a stub that reports available: false, returns an empty capability list, and rejects capability calls with a structured RUNTIME_UNAVAILABLE error.

Features that require platform infrastructure (Ed25519 signature verification, encrypted tier decryption, user prompts for secrets) are deferred to viewer implementations.

5. Manifest Schema

The manifest is a JSON object with the following top-level fields:

FieldTypeRequiredDefaultDescription
wrap_versionstringREQUIREDSpec version (semver)
metaobjectREQUIREDDocument metadata
layoutstringOPTIONAL"document"Layout mode
connectivitystringREQUIREDNetwork requirements
connectivity_detailsobjectOPTIONALHybrid degradation spec
permissionsobjectOPTIONAL(all denied)Capability declarations
securityobjectOPTIONAL{"tier":"open"}Security configuration
modulesarrayOPTIONAL[]Declared dependencies
dataobjectOPTIONALEmbedded data schemas
secretsarrayOPTIONAL[]Required credentials/API keys
toolsarrayOPTIONAL[]AI-callable operations
runtimeobjectOPTIONAL{}Runtime capability requirements
annotationsarrayOPTIONAL[]Structured notes
extensionsobjectOPTIONAL{}Third-party metadata

5.1 wrap_version

REQUIRED. A semantic version string identifying which version of this spec the wrap conforms to. For this version: "0.0.1".

Viewers MUST parse this field before processing any other manifest content. If the major version is unrecognized, the viewer MUST refuse to render and display a message directing the user to update.

5.2 meta

REQUIRED. Document metadata.

FieldTypeRequiredDescription
idstringOPTIONALUnique identifier (UUID v4 recommended)
titlestringREQUIREDDocument title
descriptionstringOPTIONALHuman-readable summary
authorobjectOPTIONALAuthor information
author.namestringOPTIONALAuthor name
author.emailstringOPTIONALAuthor email
author.urlstringOPTIONALAuthor URL
createdstringOPTIONALISO 8601 datetime
modifiedstringOPTIONALISO 8601 datetime
languagestringOPTIONALBCP 47 language tag (e.g. "en", "ja")
licensestringOPTIONALSPDX license identifier
intentstringOPTIONALAI-readable description of the wrap's purpose
tagsarrayOPTIONALCategorization tags (array of strings)
generatorstringOPTIONALTool/platform that created this wrap (e.g. "openwrap-cli/0.1.0")

The intent field is designed for LLM consumption. It SHOULD describe what the wrap does, who it's for, and what data it contains, in plain language. Example: "A weekly engineering metrics dashboard showing deploy frequency, incident rate, and MTTR, filterable by team."

5.3 layout

OPTIONAL. Declares the intended layout mode. Default: "document".

ValueDescription
"document"Scrollable content (like a web page or article)
"presentation"Fixed-aspect slides (default 16:9)
"dashboard"Grid-based layout with multiple panels
"freeform"Canvas — no layout constraints

Viewers MAY use this to adjust chrome, scrolling behavior, and navigation controls.

5.4 connectivity

REQUIRED. Declares the wrap's network requirements. There is no default — generators MUST set this explicitly.

ValueDescription
"offline"Fully self-contained. Works with no network.
"online"Requires network access to function.
"hybrid"Works offline with degraded functionality. Enhanced when online.

5.5 connectivity_details

OPTIONAL. Additional context for "hybrid" connectivity. Ignored for other modes.

FieldTypeDescription
fallback_behaviorstringHuman-readable description of what works offline vs. what requires network

5.6 permissions

OPTIONAL. Declares what capabilities the wrap requires. All capabilities default to denied. A basic wrap needs no permissions.

FieldTypeDefaultValues
networkstring"none""none", "fetch", "websocket"
storagebooleanfalsePersistent state via Web Storage API. Backing implementation is runtime-defined.
camerabooleanfalse
microphonebooleanfalse
geolocationbooleanfalse
clipboardstring"none""none", "read", "write"

Permission values are hierarchical where applicable: "websocket" implies "fetch" implies "none". "write" implies "read" implies "none".

Viewers with enforcement capabilities SHOULD block API calls not covered by declared permissions. In standard browsers without a dedicated viewer, these declarations are advisory — the CSP <meta> tag provides the actual enforcement layer.

5.7 security

OPTIONAL. Declares the security tier and tier-specific configuration. Default: { "tier": "open" }.

FieldTypeTierDescription
tierstringall"open", "signed", "encrypted", "remote"
signatureobjectsignedDigital signature data
certificatestringsignedPublic key or certificate URL
encryptionobjectencryptedRESERVED
authobjectencrypted, remoteRESERVED
check_inobjectencrypted, remoteRESERVED
expirystringanyRESERVED — ISO 8601 expiry datetime
revocation_liststringencrypted, remoteRESERVED
watermarkobjectencrypted, remoteRESERVED
content_sourcestringremoteRESERVED
render_modestringremoteRESERVED
sessionobjectremoteRESERVED

See Section 6 for tier definitions.

Forward-compatibility rule: Viewers that encounter security fields they do not recognize MUST treat them as restrictive. Unknown fields in the security object are never ignored — they cause the viewer to refuse to render. This ensures that future security features fail closed, not open.

5.8 modules

OPTIONAL. Declares the wrap's dependencies — runtimes, data sources, and asset bundles.

Each module is an object:

FieldTypeRequiredDescription
typestringREQUIREDModule type identifier
idstringREQUIREDUnique ID within this wrap
descriptionstringOPTIONALHuman/AI readable description
versionstringOPTIONALModule version (semver)
requiredbooleanOPTIONALDefault: true. If false, the wrap functions without this module.
srcstring (URL)OPTIONALURL to fetch the module from if not embedded or to use as primary source. Enables CDN-hosted runtimes and shared libraries.
hashstringOPTIONALSubresource integrity hash of the expected content at src (e.g. "sha256:e3b0c44298fc..."). Format: {algorithm}:{base64-hash}. REQUIRED if src is present. The bootloader MUST reject fetched content that does not match.
fallbackstringOPTIONALWhat to display if the module fails to load

Defined module types

TypeEmbeddingMIME type in <script>
runtime/jsInline script or <script> elementtext/javascript
runtime/pythonBase64-encoded WASM binaryapplication/wasm
runtime/wasmBase64-encoded WASM binaryapplication/wasm
data/sqliteBase64-encoded database fileapplication/x-sqlite3
data/csvPlaintext in <script>text/csv
data/jsonJSON in <script>application/json
data/parquetBase64-encoded binaryapplication/vnd.apache.parquet
media/imagesBase64 <script> or data URIsimage/*
media/audioBase64 <script> or blob/data URLsaudio/*
media/fontsBase64 <script>font/*
media/modelsBase64 <script> or blob URLsmodel/* or application/octet-stream
media/videoBase64 <script> or data URIsvideo/*
ui/componentsInline HTML/JStext/javascript

Module resolution

When a module declares both an embedded <script class="wrap-data"> element and a src URL, the bootloader resolves the content using this priority:

  1. If the wrap is offline, use the embedded content. src is ignored at runtime.
  2. If the wrap is online or hybrid, fetch from src. Verify the response against hash — reject on mismatch.
  3. If the fetch fails or is rejected, fall back to the embedded content.
  4. If neither src nor embedded content is available, the module fails to load.

For offline wraps, modules MUST be embedded. src MAY be present as a hint for tooling (e.g. a viewer pre-caching shared runtimes), but the bootloader MUST NOT fetch at runtime.

For online wraps, modules MAY omit the embedded content and rely on src alone. This produces smaller files but means the wrap breaks if the CDN is unreachable.

For hybrid wraps, embedding with src gives the best tradeoff — small when online (fetch from CDN), functional when offline (use embedded copy).

Local resource resolution

Bootloaders MUST treat declared embedded modules as addressable local resources at runtime. This is separate from application navigation or router state.

For local resource loading:

  1. Bootloaders MUST intercept fetch() requests whose target resolves to a declared module ID.
  2. A "local" target includes:

- relative URLs such as ./data.json

- root-relative URLs such as /assets/sounds/boost.mp3

- same-document-origin absolute URLs that point back into the wrap

  1. Bootloaders MUST resolve module identity from the URL pathname. By default, the pathname without its file extension maps to the module id.
  2. Query strings MAY be preserved for author code and helper APIs, but they SHOULD NOT be required to participate in module identity matching.
  3. Fragment identifiers (#...) MUST NOT affect module identity matching.
  4. When a local resource request matches a declared embedded module, the bootloader MUST serve the embedded content without a network request.
  5. For non-fetch consumers that need a URL string, bootloaders SHOULD expose a helper such as wrap.resolveURL(path) that returns a blob: URL for matching embedded modules.

Examples:

{
  "type": "runtime/wasm",
  "id": "sql-engine",
  "src": "https://cdn.example.com/sql.js@1.8.0/sql-wasm.wasm",
  "hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "description": "SQLite WASM runtime",
  "required": true
}

Generators MAY use custom module types (e.g. runtime/lua, data/arrow). Viewers that encounter unknown module types MUST skip them and render the rest of the wrap. If the module is required: true and the viewer cannot load it, the viewer SHOULD display the module's fallback message or a generic error for that section.

5.9 data

OPTIONAL. Documents the schema of embedded data sources, enabling AI and tools to understand the data without parsing the binary.

"data": {
  "schemas": {
    "{module_id}": {
      "type": "sqlite",
      "description": "Quarterly revenue data 2024-2026",
      "tables": {
        "revenue": {
          "columns": {
            "quarter": { "type": "TEXT", "description": "e.g. 2025-Q3" },
            "region": { "type": "TEXT", "description": "Sales region" },
            "amount": { "type": "REAL", "description": "Revenue in USD" }
          }
        }
      }
    }
  }
}

The data.schemas keys MUST correspond to module id values in the modules array.

Schema detail is intentionally not enforced — a data/sqlite entry with no tables key is valid but unhelpful. Generators SHOULD include as much schema detail as practical: tables and columns for SQLite, fields for CSV/JSON. The purpose of data.schemas is AI and tooling discoverability — a schema entry without structure defeats that purpose. Omitting schema detail is permitted (the data still works), but the wrap loses the "AI-native" benefit of self-describing data.

5.10 secrets

OPTIONAL. Declares credentials the wrap requires at runtime. Secrets MUST NOT be stored in plaintext in the wrap file. The manifest declares what the wrap needs; the runtime provides the values.

"secrets": [
  {
    "name": "OPENAI_API_KEY",
    "description": "OpenAI API key for AI-powered summaries",
    "required": true
  },
  {
    "name": "MAPBOX_TOKEN",
    "description": "Mapbox token for interactive maps",
    "required": false
  }
]
FieldTypeRequiredDescription
namestringREQUIREDSecret identifier. Convention: UPPER_SNAKE_CASE.
descriptionstringREQUIREDHuman/AI readable explanation of what this secret is for
requiredbooleanOPTIONALDefault: true. If false, the wrap functions without this secret (with degraded features).
scopestringOPTIONALDefault: "user". Who provides the secret.

Secret scopes:

ScopeMeaning
userEach viewer provides their own key (e.g. "bring your own OpenAI key")
orgThe organization provides it once, all members inherit (e.g. company API key managed by IT)
authorThe creator's credential, proxied through a platform (e.g. author pays for API usage)

Scope is advisory in v0.0.1 — it tells the runtime and the user who is expected to provide the secret. A viewer or platform MAY use scope to determine where to look (e.g. org secrets from Vault, user secrets from browser storage). In a plain browser, all scopes resolve through the same priority chain.

Runtimes MAY satisfy a declared secret in two ways:

  1. Direct injection — the raw value is exposed to wrap code and readable via wrap.secrets.get(name)
  2. Proxied fulfillment — the runtime or platform uses the secret on the wrap's behalf without exposing the raw value to wrap code

In practice, user secrets are often directly injected, while org and author secrets are often better fulfilled through a proxy. These are deployment choices, not format requirements.

Runtime API: The bootloader MUST expose wrap.secrets.get(name) which returns the secret value or null for secrets the runtime chooses to expose directly. Declaring a secret in the manifest does NOT guarantee the wrap can read the raw value.

Resolution priority for directly exposed secrets: The bootloader SHOULD resolve secrets in this order:

  1. Viewer/platform injection — if a viewer or platform has pre-provisioned the secret (e.g. from Vault, AWS Secrets Manager, enterprise SSO), use it
  2. Browser storage — if the user previously entered this secret, retrieve from localStorage under the standardized namespace wrap:secrets:{name}
  3. User prompt — ask the user to provide the secret, then store to browser storage for next time

Relationship to other spec features:

5.11 tools

OPTIONAL. Declares operations that AI agents or tooling can perform on the wrap. Tools turn wraps from documents AI can read into interfaces AI can use.

Each tool declaration follows the same pattern as MCP tools and LLM function/tool calling (OpenAI, Anthropic, etc.):

"tools": [
  {
    "name": "add_entry",
    "type": "runtime",
    "mutating": true,
    "description": "Record an inspection result for a checklist item",
    "parameters": {
      "type": "object",
      "properties": {
        "item_id": { "type": "string", "description": "Checklist item ID" },
        "status": { "type": "string", "enum": ["pass", "fail", "na"] },
        "notes": { "type": "string", "description": "Optional inspector notes" }
      },
      "required": ["item_id", "status"]
    },
    "returns": {
      "type": "object",
      "description": "The created inspection entry with timestamp"
    }
  },
  {
    "name": "spending_by_category",
    "type": "sql",
    "module": "db-export",
    "query": "SELECT category, SUM(amount) as total FROM transactions GROUP BY category ORDER BY total DESC",
    "description": "Returns spending totals grouped by category"
  },
  {
    "name": "summarize",
    "type": "sql",
    "module": "db-export",
    "queries": {
      "summary": "SELECT COUNT(*) as count, SUM(amount) as total FROM transactions",
      "byCategory": "SELECT category, SUM(amount) as total FROM transactions GROUP BY category ORDER BY total DESC",
      "recent": "SELECT * FROM transactions ORDER BY date DESC LIMIT 10"
    },
    "description": "Returns a compact summary of this wrap's structure, data, and current state"
  },
  {
    "name": "refresh",
    "type": "prompt",
    "mutating": true,
    "secrets": ["GITHUB_TOKEN"],
    "description": "Fetch the latest commit data from GitHub and update the wrap",
    "instruction": "1. Call the GitHub REST API at GET /repos/{owner}/{repo}/commits?per_page=20 using the GITHUB_TOKEN secret.\n2. Parse the JSON response and extract: sha, commit.message, commit.author.name, commit.author.date.\n3. Replace the contents of the 'commits' data module with the new JSON array.\n4. Update the element with id 'last-updated' to show the current timestamp.",
    "parameters": {
      "type": "object",
      "properties": {
        "owner": { "type": "string", "description": "Repository owner" },
        "repo": { "type": "string", "description": "Repository name" }
      },
      "required": ["owner", "repo"]
    }
  }
]
FieldTypeRequiredDescription
namestringREQUIREDTool name. MUST be unique within this wrap.
typestringOPTIONALExecution type: "sql", "json", "document", "runtime", or "prompt". Default: "runtime".
descriptionstringREQUIREDHuman/AI readable description of what the tool does
mutatingbooleanOPTIONALDefault: false. If true, this tool modifies data (inserts, updates, deletes). If false, the tool is read-only. Viewers SHOULD prompt the user before executing mutating tools. AI agents SHOULD treat mutating tools with higher caution and confirm intent before calling them.
secretsarray[string]OPTIONALNames of top-level secrets declarations this tool depends on. Each entry SHOULD match a manifest.secrets[].name value.
parametersobjectOPTIONALJSON Schema describing the tool's input parameters
returnsobjectOPTIONALJSON Schema or description of the return value
modulestringOPTIONALFor data tools: the module ID containing the data to operate on
querystringOPTIONALFor sql type: the SQL query to execute
queriesobjectOPTIONALFor sql type: named set of SQL queries (for tools that return multiple result sets)
instructionstringOPTIONALFor prompt type: natural language instructions for an AI agent to follow when executing this tool

Tool types:

A tool's type tells consumers how to execute it:

The sql, json, and document types are data tools — they can be fulfilled by reading the file directly, without executing JavaScript. The prompt type is an AI tool — it can be fulfilled by any AI agent that understands natural language. The runtime type requires a JavaScript environment (browser, viewer, or headless runtime). This enables CLI tools, MCP servers, and AI agents to interact with wraps instantly, without spinning up a browser.

Implementation: Tool declarations define operation contracts. In self-contained runtimes, application code commonly exposes declared tools as callable functions on a global wrap.tools object (or equivalent). Runtimes and platforms MAY also fulfill declared tools through equivalent host-provided implementations, as long as the declared name, parameters, and returns contract is preserved. For data tools, external tooling MAY fulfill the tool by executing the declared query directly against the embedded data module, bypassing the JavaScript implementation.

Discovery: An AI agent scanning a directory of wraps reads each manifest to discover available tools without executing the wrap. The name, type, description, and parameters fields provide everything needed to call the tool — the same contract as MCP/LLM tool definitions.

Tool naming convention:

Tool names SHOULD follow verb or verb_noun format in snake_case. The verb describes the action; the optional noun scopes it. Examples: summarize, refresh, add_entry, export_csv. Avoid names that describe the caller or the output destination — the name should describe what the tool does, not who calls it or where the result goes.

Reserved tool names:

The following tool names are RESERVED. A wrap MAY implement them, but if it does, it MUST follow the defined semantics. A wrap without these tools is still valid — the manifest itself provides baseline AI discoverability.

NameDescription
summarizeReturns a compact, LLM-optimized summary of the wrap's structure, data schemas, available tools, and current state. The recommended first call for any AI agent encountering an unknown wrap.
refreshRe-fetches data from external sources and updates wrap-visible state with fresh data. MUST be mutating: true. Requires appropriate permissions.network and any declared secrets. Viewers SHOULD display a staleness indicator when the wrap supports refresh and the data is older than a viewer-determined threshold.

Relationship to other spec features:

5.12 runtime

OPTIONAL. Declares host/runtime capability requirements. Runtime capabilities are host-provided operations exposed through wrap.runtime. They are distinct from wrap-authored tools declared in tools.

"runtime": {
  "required_capabilities": ["openwrap.store.private.set"],
  "optional_capabilities": ["openwrap.store.private.get"]
}
FieldTypeRequiredDescription
required_capabilitiesarray[string]OPTIONALRuntime capabilities the wrap requires for full functionality
optional_capabilitiesarray[string]OPTIONALRuntime capabilities the wrap can use when present but can function without

Rules:

  1. Runtime capability names MUST be namespaced strings (for example openwrap.project.publish or acme.inventory.update)
  2. Runtimes SHOULD preflight required_capabilities before interactive execution and surface missing capabilities clearly
  3. Wraps SHOULD degrade gracefully when optional_capabilities are unavailable
  4. Runtime capability declarations are advisory. Capability behavior, authorization, and data semantics are runtime-defined

5.13 Inline Comments

Generators SHOULD annotate wrap content with prefixed HTML comments to provide provenance, rationale, and context for AI and human readers. These comments use the wrap: prefix to distinguish them from ordinary HTML comments.

Defined prefixes:

PrefixPurposeExample
wrap:noteGeneral context, caveats, assumptions<!-- wrap:note Dollar amounts are nominal, not inflation-adjusted -->
wrap:sourceData provenance — where the data came from or the query that produced it<!-- wrap:source SELECT region, SUM(amount) FROM revenue GROUP BY region -->
wrap:decisionDesign or implementation rationale<!-- wrap:decision Bar chart instead of pie — more than 8 categories -->

Prefixes are not enforced — a generator MAY use any wrap: prefix that is descriptive. The three above are conventions, not a closed set.

Rules:

  1. Inline comments MUST use the format <!-- wrap:{prefix} {content} -->
  2. Comments are ignored by browsers and viewers — they have no effect on rendering or behavior
  3. Comments SHOULD be placed directly above the content they describe
  4. Generators SHOULD include at least a wrap:source comment for any section backed by a data query or external data pull
  5. A wrap without inline comments is valid — comments are RECOMMENDED, not REQUIRED

Relationship to manifest annotations: Inline comments are free-form, contextual, and live next to the code they describe. Manifest annotations (Section 5.14) are structured, targeted at specific elements via CSS selectors, and live in the manifest. Both are useful for different purposes — inline comments for provenance and rationale during generation, manifest annotations for structured metadata that tools can query programmatically.

5.14 Annotations

OPTIONAL. Structured notes attached to specific parts of the wrap.

"annotations": [
  {
    "target": "#revenue-chart",
    "note": "Uses trailing 12-month average per CFO request"
  }
]
FieldTypeRequiredDescription
targetstringREQUIREDCSS selector or component ID
notestringREQUIREDHuman/AI readable annotation

5.15 extensions

OPTIONAL. Namespaced third-party metadata.

"extensions": {
  "com.acme.compliance": {
    "classification": "internal",
    "retention_days": 90
  },
  "com.tableau.integration": {
    "datasource_id": "ds-abc123"
  }
}

Rules:

  1. Top-level keys under extensions MUST be reverse-domain namespaced (e.g. com.acme.compliance). No bare keys.
  2. Viewers MUST ignore extension namespaces they do not recognize. Never error, never warn.
  3. Extensions are inert data unless a viewer or plugin explicitly understands the namespace.
  4. The namespace dev.wrap.* is RESERVED for first-party extensions that may be promoted to the core spec.
  5. Extension values MUST be valid JSON. The core spec does not validate extension schemas.

6. Security Tiers

Every wrap operates at a declared security tier. The tier determines what protections are in place and what infrastructure is required.

6.1 Open Tier

The default tier. No security mechanisms. The wrap is a plain HTML file.

"security": { "tier": "open" }

6.2 Signed Tier

The wrap is signed to prove authorship and detect tampering. Content remains plaintext — signing is about trust and integrity, not access control.

"security": {
  "tier": "signed",
  "signature": {
    "algorithm": "ed25519",
    "value": "{base64-encoded signature}",
    "signed_at": "{ISO 8601 datetime}"
  },
  "certificate": "{URL to public key or inline base64 public key}"
}

Signing scope:

The signature covers the entire document except mutable data. Specifically:

The signature covers the author's entire document — code, data, and structure. Runtime user state lives in wrap.store, which is persisted outside the HTML by the platform. When a wrap is exported, store data is serialized into a <script id="wrap-store-snapshot"> element that is always excluded from the signature. This allows a signed wrap (e.g. an inspection form) to carry user-entered data without breaking the author's signature.

Signing process:

  1. Author creates the wrap with all content finalized
  2. security.signature.value is set to the empty string ""
  3. If a <script id="wrap-store-snapshot"> element exists, its content is replaced with the empty string
  4. The SHA-256 hash of the resulting document bytes is computed
  5. The hash is signed with the author's Ed25519 private key
  6. The base64-encoded signature is placed in security.signature.value in the original document

Verification process:

  1. Viewer reads security.signature and security.certificate
  2. Viewer creates a copy of the document with:

- security.signature.value set to ""

- Content of <script id="wrap-store-snapshot"> (if present) replaced with the empty string

  1. Viewer computes SHA-256 of the resulting document bytes
  2. Viewer fetches or reads the public key from certificate
  3. Viewer verifies the signature against the hash
  4. Viewer displays verification status:

- Verified: "Signed by {author} — signature valid"

- Failed: "WARNING: This wrap has been modified since it was signed"

- Unverifiable: "Signed, but the certificate could not be verified"

Store snapshot:

When a viewer or platform exports a wrap that has wrap.store data, it serializes the store contents into a dedicated element:

<script type="application/json" id="wrap-store-snapshot">
{"responses":[{"question":"Safety check","answer":"Pass"}],"completed":true}
</script>

The bootloader reads this element on load and populates wrap.store with its contents (if the store is empty). This allows user state to travel with the file across exports. Because the store snapshot is always excluded from the signature hash, a signed wrap remains valid even after the user adds data.

Byte-level signing (no canonicalization):

The signature covers the exact byte content of the file. Any byte-level modification — including whitespace changes, re-encoding, trailing newlines, or attribute reordering — invalidates the signature. This is intentional. Signed wraps are sealed artifacts, like signed binaries or notarized documents.

This avoids the canonicalization complexity that plagued XML signatures (C14N). The tradeoff is simple: don't edit signed files in a text editor. To modify a signed wrap's content, create a new wrap and re-sign it.

Canonicalization and the reference bootloader: Signing operates on exact file bytes — no normalization or canonicalization is applied. Verification requires access to the original file bytes, not a DOM re-serialization. The reference bootloader detects the presence of a signature and sets wrap.signature.status = "present", but does not perform verification. Full Ed25519 verification requires a viewer or platform that can read the file as raw bytes, reconstruct the signing input (zeroing signature.value and store snapshot content), and verify against the public key.

Trust model:

This version does not mandate a certificate authority. Implementations MAY use:

6.3 Encrypted Tier

The encrypted tier protects wrap content so that the file is useless without authorization. This version specifies passphrase-based encryption — content is encrypted with a key derived from a user-provided passphrase. No server infrastructure required.

"security": {
  "tier": "encrypted",
  "encryption": {
    "algorithm": "aes-256-gcm",
    "key_derivation": "pbkdf2",
    "iterations": 600000,
    "salt": "{base64-encoded random salt}",
    "iv": "{base64-encoded initialization vector}"
  }
}

File structure for encrypted wraps:

The wrap-root contains no readable content. Instead, the encrypted content is stored in a dedicated element:

<div id="wrap-root"></div>

<script type="application/octet-stream" id="wrap-encrypted-content"
        data-encoding="base64">
base64-encoded-ciphertext...
</script>

The noscript fallback SHOULD display a message like "This wrap is password-protected. Enable JavaScript to unlock."

Encryption process (author):

  1. Author creates the wrap with all content finalized in the wrap-root
  2. Author provides a passphrase
  3. Bootloader derives a 256-bit key using PBKDF2 (SHA-256, 600k+ iterations, random salt)
  4. Content is encrypted with AES-256-GCM (random IV)
  5. Ciphertext replaces the wrap-root content, stored in wrap-encrypted-content
  6. Salt and IV are stored in the manifest (they are not secret)

Decryption process (viewer):

  1. Bootloader reads security.tier"encrypted"
  2. Checks for security.auth field:

- If auth is absent → passphrase mode (this version)

- If auth is present → server-managed mode (RESERVED — refuse to render)

  1. Prompts user for passphrase
  2. Derives key using PBKDF2 with the manifest's salt and iteration count
  3. Decrypts ciphertext with AES-256-GCM using the manifest's IV
  4. On success: injects decrypted HTML into wrap-root, initializes bootloader normally
  5. On failure: displays "Incorrect passphrase" — content never renders

All cryptographic operations use the browser's native Web Crypto API.

What's RESERVED for future versions:

When the security.auth field is present, the encrypted tier operates in server-managed mode — the decryption key comes from a key server that requires authentication (JWT, SSO, etc.), not from a user-provided passphrase. The manifest fields for this mode (auth, check_in, watermark, expiry, revocation_list) are defined in the schema but their behavior is not specified in this version.

A viewer that encounters "tier": "encrypted" with a security.auth field MUST NOT attempt to render the wrap. It MUST display a message: "This wrap requires a viewer that supports authenticated encryption."

Passphrase mode vs server-managed mode:

Passphrase (v0.0.1)Server-managed (future)
Infrastructure neededNoneAuth server + key server
Works offlineYesNo
Key sourceUser enters passphraseServer provides after auth
RevocableNo — passphrase is shared knowledgeYes — server controls access
Use caseShare with specific people who know the passwordEnterprise access control

6.4 Remote Tier (RESERVED)

The remote tier is defined in the manifest schema but its behavior is not specified in this version. The manifest fields (content_source, render_mode, session, auth) are reserved to ensure forward compatibility.

A viewer that encounters "tier": "remote" MUST NOT attempt to render the wrap. It MUST display a message: "This wrap requires a viewer that supports remote content."

6.5 Forward Compatibility

The security tier enum is explicitly extensible. Future versions MAY add new tiers. A viewer that encounters an unknown tier value MUST refuse to render and display an upgrade message. Security tiers always fail closed.

The reserved fields in Section 5.7 define the schema shape that future tiers will use. This ensures that:

Note on schema validation: The JSON Schema (spec/v0.0.1/manifest.schema.json) is version-locked — it validates v0.0.1 manifests only. A manifest using fields introduced in a later spec version should be validated against that version's schema, not this one. The additionalProperties: false constraint on the security object means the schema will reject unknown fields at validation time, which is correct: a v0.0.2 manifest is not a v0.0.1 manifest. The "fail closed" rule in Section 5.7 governs viewer runtime behavior; the schema governs authoring-time validation.

6.6 Threat Model

A wrap is executable code. This section describes what a malicious or buggy wrap can and cannot do in each execution context, so that users, security reviewers, and viewer implementors can make informed trust decisions.

6.6.1 Execution Contexts

A wrap can be opened in three contexts, each with a different trust boundary:

ContextEnforcementTrust level
Standard browser (file opened directly)Browser sandbox + CSP meta tagEquivalent to opening any .html file
Web viewer (wrap loaded in a hosted viewer app)Browser sandbox + CSP + iframe sandbox + viewer-enforced permissionsHigher — viewer mediates all capability access
Native viewer (dedicated desktop/mobile app)OS-level sandbox + viewer-enforced permissions + manifest validationHighest — full control over execution environment

6.6.2 Standard Browser (No Viewer)

When a user opens a .html wrap directly in a browser, the security posture is:

What the browser enforces:

What is NOT enforced:

What a malicious open-tier wrap CAN do in a plain browser (with no CSP):

What a malicious open-tier wrap CANNOT do in a plain browser:

Implication: For open-tier wraps, the CSP meta tag is the primary security mechanism in standard browsers. Generators MUST include a restrictive CSP (Section 4.2). A wrap with permissions.network: "none" and a CSP of default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: is genuinely locked down — the browser will block all outbound network requests regardless of what the JavaScript attempts.

6.6.3 Web Viewer

A web viewer (e.g., Wrap Collab) loads wraps inside a sandboxed iframe with viewer-controlled attributes:

Additional protections beyond standard browser:

What a malicious wrap CANNOT do in a web viewer (beyond standard browser restrictions):

6.6.4 Native Viewer

A native viewer has full control over the execution environment:

Additional protections beyond web viewer:

6.6.5 Recommendations

For users: Treat an open-tier wrap from an untrusted source the same way you'd treat any .html file — it's executable code. If the wrap includes a restrictive CSP, it is meaningfully sandboxed even in a plain browser. For higher assurance, open untrusted wraps in a viewer.

For generators: Always include a CSP meta tag scoped to the minimum capabilities the wrap needs. Never include a CSP more permissive than what permissions declares. The CSP is the enforcement; the manifest is the contract.

For viewer implementors: Enforce all manifest permissions at the viewer level. Do not rely on the wrap's embedded CSP — inject your own. Prompt users before executing mutating tools. Display signature verification status prominently for signed wraps.

7. State, Persistence & Runtime

Wrap files are read-only at the file system level — browsers cannot modify a .html file on disk. At runtime, wraps have two mechanisms for managing state:

MechanismPurposeAPI
localStorage / sessionStoragePersonal preferences, UI stateStandard Web Storage API
wrap.storeApplication data — anything the wrap creates, modifies, or sharesSee §7.1

Both require permissions.storage: true in the manifest. The backing implementation is runtime-defined — a standalone browser uses localStorage, a platform viewer may provide shared or cloud-synced storage.

Export remains the primary mechanism for state portability. On export, wrap.store data is serialized into a <script id="wrap-store-snapshot"> element in the HTML. This produces a new, self-contained wrap with the user's data included.

Export and signing:

7.1 wrap.store

wrap.store is the primary data persistence API for wraps. It provides key-value storage for single values and collection operations for lists of records. Available when permissions.storage is true.

Design principles:

Security considerations:

Terminology note:

Single-value operations:

MethodReturnsDescription
get(key)`value \null`Read a value by key
set(key, value)voidWrite or replace a value
delete(key)voidRemove a key and its value

Collection operations:

MethodReturnsDescription
append(key, item)objectAdd an item to a collection. Returns the item with _id assigned. Creates the collection if it doesn't exist.
update(key, match, patch)numberUpdate items matching match with fields from patch. Returns count of updated items. match is a partial object — items where all specified fields are equal are matched.
remove(key, match)numberRemove items matching match. Returns count of removed items.
pop(key)`object \null`Remove and return the last item in a collection.
count(key)numberReturn the number of items in a collection, without loading the full data.

Observation:

MethodReturnsDescription
keys()string[]List all keys in the store
subscribe(key, fn)functionRegister a callback that fires when the value at key changes. Returns an unsubscribe function. In single-user mode, fires on local writes. In multi-user runtimes, fires on any user's writes.

Example — shared guestbook:

// Add a message
const entry = await wrap.store.append('messages', { name: 'Andrew', text: 'Hello from Wrap' });
// entry = { _id: 'x7f2k', name: 'Andrew', text: 'Hello from Wrap' }

// Read all messages
const messages = await wrap.store.get('messages');
// [{ _id: 'x7f2k', name: 'Andrew', text: 'Hello from Wrap' }, ...]

// Live updates from other participants
wrap.store.subscribe('messages', (messages) => {
  renderGuestbook(messages);
});

// Remove a specific entry
await wrap.store.remove('messages', { _id: 'x7f2k' });

Example — todo list with status updates:

const todo = await wrap.store.append('todos', { text: 'Buy milk', done: false });
// Later: mark as done
await wrap.store.update('todos', { _id: todo._id }, { done: true });

Bootloader default implementation:

The reference bootloader provides a localStorage-backed wrap.store. Collections are stored as JSON arrays. append reads the array, pushes the item with a generated _id, writes back. This is suitable for single-user standalone wraps. Runtimes that provide multi-user or real-time storage override wrap.store with their own implementation — the API contract remains the same.

7.2 wrap.runtime

wrap.runtime is the optional host/runtime bridge. It exposes runtime-specific capabilities that are not part of the portable wrap contract. A wrap running in a host environment such as OpenWrap or another embed platform can use wrap.runtime to call host-provided operations. In a standalone browser or downloaded-file context, the bootloader provides a no-runtime stub.

Design principles:

Terminology note:

Surface:

Field / MethodReturnsDescription
availablebooleanWhether a host runtime is present
listCapabilities()CapabilityDescriptor[]Enumerate runtime capabilities currently available
call(name, params?)anyInvoke a runtime capability by name
addEventListener(type, listener)voidSubscribe to runtime lifecycle or capability events
removeEventListener(type, listener)voidRemove a previously registered listener

Capability descriptor shape:

FieldTypeRequiredDescription
namestringREQUIREDNamespaced capability name (for example openwrap.project.publish)
descriptionstringOPTIONALHuman-readable description
parametersobjectOPTIONALJSON Schema describing accepted parameters
returnsobjectOPTIONALJSON Schema or description of the return value
mutatingbooleanOPTIONALWhether calling the capability may modify observable or authoritative state

Standard events:

EventMeaning
capabilitieschangeCapability availability changed
authchangeAuthentication or authorization state changed
contextchangeRuntime context changed (e.g. active project, record, or tenant)
connectRuntime bridge became available
disconnectRuntime bridge became unavailable

Listeners receive an event-like object with { type, detail }. The shape of detail is runtime-defined.

Bootloader default stub:

The reference bootloader MUST expose a stub with this behavior when no runtime is present:

wrap.runtime = {
  available: false,
  listCapabilities: async () => [],
  call: async () => {
    throw Object.assign(new Error('No runtime available.'), {
      code: 'RUNTIME_UNAVAILABLE'
    });
  },
  addEventListener: () => {},
  removeEventListener: () => {}
};

Rules:

  1. Runtime capability names MUST be namespaced strings (for example openwrap.store.private.set or acme.inventory.update)
  2. wrap.runtime.call() MUST reject with a structured runtime error when a capability cannot be fulfilled
  3. Runtime implementations MUST keep host-specific capabilities separate from wrap.tools
  4. Wraps SHOULD check wrap.runtime.available or handle RUNTIME_UNAVAILABLE gracefully before depending on host-only behavior
  5. Wraps MAY compose higher-level wrap-authored tools from one or more runtime capability calls

Example — calling a host capability:

if (!wrap.runtime.available) {
  showOfflineBanner();
} else {
  await wrap.runtime.call('acme.inventory.update', {
    item_id: 'sku-4021',
    quantity: 12
  });
}

Example — wrap-authored tool using runtime capabilities:

wrap.tools.restock_check = async ({ warehouse_id }) => {
  const items = await wrap.runtime.call('acme.inventory.list', { warehouse_id });
  for (const item of items) {
    if (item.quantity < item.reorder_threshold) {
      await wrap.runtime.call('acme.inventory.flag', {
        item_id: item.id,
        reason: 'low_stock'
      });
    }
  }
  return { checked: items.length };
};

7.3 wrap.resolveURL

wrap.resolveURL(path) is a bootloader helper for consumers that require a URL string instead of calling fetch() directly.

Behavior:

Examples:

const audio = new Audio(wrap.resolveURL('/assets/sounds/boost.mp3'));
loader.load(wrap.resolveURL('/assets/models/f-15.glb'));

8. Error Handling

8.1 Error Codes

CodeMeaning
MANIFEST_MISSINGNo wrap-manifest script element found
MANIFEST_INVALIDManifest is not valid JSON or fails schema validation
VERSION_UNSUPPORTEDwrap_version major version not recognized
TIER_UNSUPPORTEDSecurity tier not supported by this viewer
MODULE_MISSINGDeclared module not found in the document
MODULE_LOAD_FAILEDModule failed to initialize (WASM compile error, etc.)
RUNTIME_ERRORUnhandled exception during wrap execution
PERMISSION_DENIEDCode attempted an undeclared capability
SIGNATURE_INVALIDSignature verification failed
INTEGRITY_FAILEDFetched module content does not match declared hash
DATA_CORRUPTEmbedded data failed integrity check
RUNTIME_UNAVAILABLEwrap.runtime.call() was attempted without a host runtime
CAPABILITY_UNAVAILABLENamed runtime capability is not available in this runtime
AUTH_REQUIREDRuntime capability requires authentication
FORBIDDENRuntime capability denied by authorization or policy
INVALID_PARAMSRuntime capability received invalid parameters
FAILEDRuntime capability failed for an implementation-specific reason

8.2 Graceful Degradation

Modules MUST fail independently. If one module fails to load:

If the manifest is missing or invalid, the file is not a wrap — the browser renders it as normal HTML.

9. Versioning

9.1 Spec Versioning

The spec uses semantic versioning:

BumpWhat changed
Patch (0.0.x)Clarifications, no behavioral change
Minor (0.x.0)New optional fields, new module types, new security tier implementations. Old wraps remain valid. Old viewers degrade gracefully.
Major (x.0.0)Breaking changes — required fields added, field semantics changed

9.2 Forward Compatibility (new wrap, old viewer)

9.3 Backward Compatibility (old wrap, new viewer)

9.4 Size Guidance

This version does not impose hard limits on document size, module count, or annotation count. Implementers SHOULD apply reasonable limits appropriate to their context. For reference, practical considerations include:

Future versions MAY introduce normative size constraints based on implementation experience.

10. Conformance

10.1 Conforming Document

A conforming wrap document:

  1. MUST be a valid HTML document
  2. MUST contain a manifest element per Section 4.1
  3. MUST have a valid wrap_version field
  4. MUST have a valid meta.title field
  5. MUST contain a content root element per Section 4.4
  6. MUST contain a bootloader element per Section 4.6
  7. SHOULD include a Content Security Policy per Section 4.2
  8. SHOULD include a static fallback per Section 4.3

10.2 Conforming Viewer

A conforming viewer:

  1. MUST parse the manifest before rendering interactive content
  2. MUST check the security tier and refuse to render unsupported tiers
  3. MUST ignore unknown manifest fields (except in security)
  4. MUST skip unknown module types gracefully
  5. MUST ignore unknown extension namespaces
  6. SHOULD enforce declared permissions when capable
  7. SHOULD display signature verification status for signed wraps
  8. If it implements runtime capabilities, MUST expose them via wrap.runtime
  9. If it implements runtime capabilities, MUST keep host-specific capabilities separate from wrap.tools

10.3 Conforming Generator

A conforming generator:

  1. MUST produce valid HTML documents
  2. MUST include a valid manifest with wrap_version and meta.title
  3. MUST include a content root and bootloader
  4. MUST declare all capabilities the wrap uses in permissions
  5. MUST declare all dependencies in modules
  6. SHOULD include a Content Security Policy
  7. SHOULD include a static fallback
  8. SHOULD include an intent field for AI discoverability
  9. SHOULD declare runtime.required_capabilities and runtime.optional_capabilities when the wrap depends on host/runtime capabilities

10.4 Conformance Test Suite

A suite of self-verifying test wraps is provided at spec/tests/. Each test is a standalone HTML file that embeds the reference bootloader and displays PASS/FAIL results when opened in a browser.

Implementers SHOULD verify their bootloader and viewer against this suite. All valid tests should show PASS; all invalid tests should show the appropriate error code rendered in #wrap-root.

Appendix A: Minimal Example

The smallest valid wrap:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello</title>
  <script type="application/json" id="wrap-manifest">
  {
    "wrap_version": "0.0.1",
    "meta": { "title": "Hello" }
  }
  </script>
</head>
<body>
  <div id="wrap-root">
    <h1>Hello, World</h1>
  </div>
  <script id="wrap-bootloader">
  (() => {
    const m = JSON.parse(document.getElementById('wrap-manifest').textContent);
    document.title = m.meta.title;
  })();
  </script>
</body>
</html>

Appendix B: Dashboard Example

A wrap with embedded data, modules, and permissions:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Q3 Revenue</title>

  <script type="application/json" id="wrap-manifest">
  {
    "wrap_version": "0.0.1",
    "meta": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "title": "Q3 Revenue Report",
      "description": "Interactive quarterly revenue with drill-down by region",
      "author": { "name": "Finance Team", "email": "finance@acme.com" },
      "created": "2026-03-12T10:00:00Z",
      "language": "en",
      "intent": "A quarterly revenue dashboard. Revenue data is in a JSON dataset, broken down by region and product line. Users can filter by region and see trends over time."
    },
    "layout": "dashboard",
    "connectivity": "offline",
    "permissions": {
      "network": "none",
      "storage": true
    },
    "security": { "tier": "open" },
    "modules": [
      {
        "type": "data/json",
        "id": "revenue-data",
        "description": "Quarterly revenue by region and product, 2024-2026",
        "required": true
      },
      {
        "type": "runtime/js",
        "id": "chart-engine",
        "description": "Lightweight charting library for revenue visualizations"
      }
    ],
    "data": {
      "schemas": {
        "revenue-data": {
          "type": "json",
          "description": "Array of revenue records",
          "fields": {
            "quarter": { "type": "string", "description": "e.g. 2025-Q3" },
            "region": { "type": "string", "description": "Sales region" },
            "product": { "type": "string", "description": "Product line" },
            "amount": { "type": "number", "description": "Revenue in USD" }
          }
        }
      }
    },
    "annotations": [
      {
        "target": "#revenue-chart",
        "note": "Uses trailing 12-month average per CFO request"
      }
    ]
  }
  </script>

  <meta http-equiv="Content-Security-Policy"
    content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:;">

  <style id="wrap-styles">
    /* Dashboard styles */
  </style>
</head>
<body>
  <noscript>
    <h1>Q3 Revenue Report</h1>
    <table><!-- static data table fallback --></table>
  </noscript>

  <div id="wrap-root">
    <header data-wrap-role="title">
      <h1>Q3 Revenue Report</h1>
    </header>
    <section data-wrap-role="chart" id="revenue-chart"
             data-wrap-description="Revenue by quarter, filterable by region">
    </section>
    <section data-wrap-role="data-table" id="revenue-table"
             data-wrap-description="Detailed revenue breakdown">
    </section>
  </div>

  <script type="application/json" class="wrap-data" data-id="revenue-data">
  [
    {"quarter":"2026-Q1","region":"North America","product":"Enterprise","amount":2400000},
    {"quarter":"2026-Q1","region":"EMEA","product":"Enterprise","amount":1800000}
  ]
  </script>

  <script id="wrap-bootloader">
  (() => {
    const manifest = JSON.parse(
      document.getElementById('wrap-manifest').textContent
    );

    // Load embedded data
    const dataEl = document.querySelector('.wrap-data[data-id="revenue-data"]');
    const data = JSON.parse(dataEl.textContent);

    // Initialize interactive dashboard
    // ... chart rendering, filtering, etc.
  })();
  </script>
</body>
</html>

Appendix C: Stateful Form Example

A signed wrap with wrap.store — an inspection form that can be filled in without breaking the author's signature. User responses live in wrap.store, not in the data modules. On export, store data is serialized into a <script id="wrap-store-snapshot"> element that is excluded from the signature.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Site Inspection</title>

  <script type="application/json" id="wrap-manifest">
  {
    "wrap_version": "0.0.1",
    "meta": {
      "title": "Site Inspection Checklist",
      "author": { "name": "Safety Team", "email": "safety@acme.com" },
      "created": "2026-03-12T10:00:00Z",
      "intent": "A field inspection form. The checklist items and scoring criteria are fixed by the author. The inspection responses are filled in by the field inspector."
    },
    "layout": "document",
    "connectivity": "offline",
    "permissions": {
      "storage": true
    },
    "security": {
      "tier": "signed",
      "signature": {
        "algorithm": "ed25519",
        "value": "{base64-encoded signature}",
        "signed_at": "2026-03-12T10:00:00Z"
      },
      "certificate": "https://pki.acme.com/keys/safety-team"
    },
    "modules": [
      {
        "type": "data/json",
        "id": "checklist-items",
        "description": "The inspection checklist — questions, categories, scoring weights"
      }
    ]
  }
  </script>

  <meta http-equiv="Content-Security-Policy"
    content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:;">
</head>
<body>
  <noscript>
    <h1>Site Inspection Checklist</h1>
    <p>Enable JavaScript to fill out this form.</p>
  </noscript>

  <div id="wrap-root">
    <!-- Interactive form rendered by app code -->
  </div>

  <!-- Author's checklist template (covered by signature) -->
  <script type="application/json" class="wrap-data" data-id="checklist-items">
  [
    {"id": "fire-exits", "category": "Safety", "question": "Are fire exits clear and marked?", "weight": 3},
    {"id": "ppe", "category": "Safety", "question": "Is PPE available and in good condition?", "weight": 2},
    {"id": "electrical", "category": "Electrical", "question": "Are panel covers in place?", "weight": 2}
  ]
  </script>

  <!-- Store snapshot: user data from a previous export (excluded from signature) -->
  <script type="application/json" id="wrap-store-snapshot">
  {}
  </script>

  <script id="wrap-bootloader">
  // ... bootloader code ...
  </script>

  <script>
  (() => {
    const wrap = window.wrap;

    // Load checklist from embedded data
    fetch('./checklist-items.json').then(r => r.json()).then(items => {
      // Load previous responses from wrap.store (if any)
      wrap.store.get('responses').then(responses => {
        responses = responses || [];
        // Render form UI with items and existing responses
        // On submit: wrap.store.append('responses', { itemId, answer, timestamp })
        // On export: store snapshot is serialized into the HTML automatically
        //   — signature remains valid because wrap-store-snapshot is excluded
      });
    });
  })();
  </script>
</body>
</html>

When the inspector fills in the form and exports, the new file contains their responses in the inspection-responses data block. The signature remains valid — it was computed with that block excluded. The recipient sees: "Signed by safety@acme.com — template verified. Inspection data has been added."

Appendix D: AI Generation Contract

To generate a conforming wrap, an LLM SHOULD:

  1. Start from the file structure template in Section 4
  2. Populate the manifest with all relevant metadata, especially intent
  3. Declare all capabilities the generated code will use in permissions
  4. Declare all dependencies in modules
  5. Include a CSP <meta> tag scoped to only what the wrap needs
  6. Include a <noscript> fallback with a static representation of the content
  7. Use semantic HTML with data-wrap-role and data-wrap-description attributes on interactive sections
  8. Document embedded data schemas in data.schemas
  9. Declare tools the wrap supports in tools, including summarize when practical — use data tool types (sql, json, document) where possible so tools can be executed without a runtime
  10. Declare runtime.required_capabilities and runtime.optional_capabilities when the wrap depends on host/runtime capabilities
  11. Implement declared tools in a way the target runtime can fulfill — commonly via the wrap.tools global object in self-contained runtimes

The manifest is the contract between the generator and the viewer. A generator that declares accurate permissions and modules enables viewers to enforce security and manage resources. A generator that under-declares creates a wrap that may malfunction in enforcing viewers. A generator that over-declares wastes user trust.