# AMW v1 wire format

Standalone file/folder WAV and MP4 exchange, plus experimental pixel, soundtrack, and frame transports, are specified in [MEDIA-TRANSPORT.md](MEDIA-TRANSPORT.md). PNG pixels, WAV tones, and sparse PNG frames are also website carrier modes. Standalone exchange parts use a separate media key and archive format; they are not `webApp` resource parts.

AMW integers are unsigned little endian; PNG chunk lengths/CRCs and MP4 box sizes retain their native big-endian representation. All offsets are absolute within the `.amw` payload unless described as file offsets. JSON uses UTF-8 with sorted object keys, no whitespace, and no non-finite numbers. Readers reject unsupported major versions; no implicit format guessing is used.

## Payload

The first 4096 bytes are the fixed header. Reserved bytes must be zero.

| Offset | Size | Meaning |
| ---: | ---: | --- |
| 0 | 8 | ASCII `AMW1DOC\0` |
| 8 | 2 | version `1` |
| 10 | 2 | header length `4096` |
| 12 | 4 | reserved |
| 16 | 8 | total payload length |
| 24 | 8 | manifest offset, currently `4096` |
| 32 | 8 | manifest length, max 1 MiB |
| 40 | 8 | bootstrap section offset |
| 48 | 8 | bootstrap section length |
| 56 | 32 | SHA-256 of manifest bytes |
| 88 | 32 | SHA-256 of bootstrap bytes |
| 120 | 3976 | reserved |

The manifest follows the header. It is `{"format":"amw.sections.v1","sections":[...]}`. Each section entry has `id`, `type`, `offset`, `length`, and lowercase hex `sha256`. v1 requires exactly two contiguous sections, in this order: `bootstrap`, then `document`; both have type `amw.document.v1`. The bootstrap JSON is an authored intro document when the source defines `bootstrap`; older sources compile the title and first block as a fallback. The full document section contains the source's complete document. The extension viewer and the site-owned boot page can display the bootstrap during progressive loading. The site boot uses its title, text, and theme as an automatic loading screen and opens the app after the worker verifies its initial resources. The limited viewer can still show an authored launch action for its document or app. The amoosed.com side panel does not render site-authored document content. There is no padding between sections. A reader verifies the manifest and bootstrap digests and, for a footer v2 carrier, the footer-anchored bootstrap prefix digest before displaying the intro. It then verifies the document section and complete payload digest. With a legacy footer v1 carrier or a bare `.amw` file, the reader waits until the complete document is checked before displaying the intro. Max payload size is 1 GiB; the Pages full-download fallback is separately capped at 16 MiB.

The v1 source schema is `{ "title": string, "blocks": Block[], "bootstrap"?: Intro, "theme"?: Theme, "webApp"?: WebApp }`, where `Intro` is a document with `title`, `blocks`, and optional `theme` but no nested bootstrap. `Block` is a `heading` or `paragraph` with `text`, a `link` with `text` and `href`, or a bootstrap-only `launch` button with `text` and optional `style`. HTTPS URLs open as ordinary external links. `amw://host/path.amw` links and same-origin relative paths such as `/chapter.amw` navigate to another verified AMW intro inside the current panel or viewer tab. A relative `/` returns to the site's `/favicon.ico` document. Remote AMW links map to HTTPS; localhost links map to local HTTP. Credentials and unsafe schemes are rejected, and a cross-origin AMW link asks for that origin's permission. In the limited extension viewer, `launch` opens the site's `/amoosed.com/boot.html` when `webApp` is present, or its full document otherwise. The site boot omits `launch` because it proceeds automatically. `Theme` has optional six-digit hex `background`, `foreground`, and `accent` colors. Launch `style` has optional six-digit hex `background` and `foreground` colors, plus integer `radius` from 0 to 32. These are applied as properties, not executable CSS. The compiler limits the title to 200 characters, each text to 10,000 characters, and blocks to 10,000. Renderers use text nodes. The extension document renderer does not execute HTML, CSS, JavaScript, images, or WASM. `WebApp` describes resources appended to existing binary carriers and served by an optional site-origin runtime; see [WEBAPP.md](WEBAPP.md).

An optional `webApp.identity` object carries a bounded application title and the fixed virtual favicon path `/amoosed.com/app/favicon.ico`. That public ICO resource is stored in the existing data carriers. Its own appended AMW payload is a small document with the same title, allowing offline verification of identity metadata against the root index. The side panel uses only the verified root index for discovery and title text; it does not trust an arbitrary page favicon or pathname as proof of an app. Older v1 manifests without `identity` remain readable. Readers that validate manifest keys must be updated to accept this additive profile.

## Appended universal carrier

`packed file = original opaque carrier || complete AMW payload || 128-byte footer`. The original carrier is unmodified. Footer offsets are absolute within the packed file. A packed file is detected only by reading the last 128 bytes and validating its footer; the carrier's MIME type or filename is irrelevant. Some file formats or signing schemes reject trailing bytes, so validate the particular carrier with its usual consumer before deployment.

| Footer offset | Size | Meaning |
| ---: | ---: | --- |
| 0 | 8 | ASCII `AMW1TAIL` |
| 8 | 2 | carrier footer version `1` or `2`; new packs write `2` |
| 10 | 2 | footer length `128` |
| 12 | 4 | reserved |
| 16 | 8 | original carrier length |
| 24 | 8 | payload file offset, equal to carrier length |
| 32 | 8 | payload length |
| 40 | 32 | SHA-256 of complete AMW payload |
| 72 | 32 | SHA-256 of original carrier |
| 104 | 24 | v1: zero; v2: first 24 bytes of SHA-256 of the payload prefix through the bootstrap section |

The payload header remains version 1; only the carrier footer gained an independently checkable bootstrap prefix digest. The prefix covers the complete 4096-byte header, manifest, and bootstrap section, and the 192-bit truncated SHA-256 is checked before the progressive callback. The complete payload hash is still checked before accepting the full document. Footer v1 remains readable, but its intro is deferred until the complete payload check. This is integrity against accidental or partial modification, not publisher authentication: an attacker able to replace both carrier contents and footer can recompute all hashes. The CLI checks both complete hashes. The range reader does not fetch the unrelated original carrier prefix, so it does not check the original-carrier hash during progressive loading. The favicon default is a convention: when given a site root, the extension requests that origin's `/favicon.ico`; explicit carrier URLs work independently of this convention.

## App data carrier

For the default append mode, `packed file = original opaque carrier || assigned app bytes || 128-byte data footer`. The original prefix is byte-for-byte preserved. Container-native modes insert a media chunk or box as described below. Each selected existing media path may hold parts of several resources. The `webApp` manifest records absolute file offsets, lengths, and hashes. The offline verifier also checks this footer:

| Footer offset | Size | Meaning |
| ---: | ---: | --- |
| 0 | 8 | ASCII `AMW1DATA` |
| 8 | 2 | version `1` for append; `2` for container-native media |
| 10 | 2 | footer length `128` |
| 12 | 4 | v1: zero; v2: mode byte (`1` PNG, `2` MP4) then three zero bytes |
| 16 | 8 | original carrier length |
| 24 | 8 | appended app-data length |
| 32 | 32 | SHA-256 of original carrier |
| 64 | 32 | SHA-256 of appended data |
| 96 | 32 | v1: zero; v2: unsigned 64-bit insertion offset followed by 24 zero bytes |

All offsets in a `webApp` part refer to the complete packed data carrier. The network reader checks individual part hashes and complete resource hashes from the verified index. It does not fetch unrelated original carrier bytes just to validate the data footer. A media decoder, optimizer, or CDN may reject or strip trailing bytes even though the AMW format itself is independent of the carrier MIME type.

### Container-native media modes

`bundle.json` carrier entries may be strings (the default universal append mode) or `{ "path": "relative/media.png", "mode": "png-chunk" | "mp4-uuid" | "png-pixels" | "wav-tones" | "frame-luma" | "append" }`. Container modes use the same `webApp` resource and part schema; the absolute part offsets point inside the inserted chunk or box, so browser range loaders need no decoding change. The 128-byte `AMW1DATA` footer remains at EOF for compiler discovery and offline verification in those modes.

`png-pixels` requires an opaque-capable PNG and embeds already encoded app shards in the low bit of its opaque RGB channels. The compiler re-encodes the PNG and does not append a data footer. A `webApp` part from this carrier includes `"codec":"png-lsb1"`; its `offset` is relative to the decoded pixel payload, not a file byte range. The payload begins with an 8-byte `AMWPX1` marker, 32-bit little-endian payload length, and SHA-256 of the encoded shards. The browser fetches the whole PNG, decodes pixels with no color conversion, verifies the payload hash, then verifies the shard hash before website decoding and optional member decryption. The PNG file is bounded to 64 MiB and the image to 16 Mi pixels. Capacity is `floor(opaque_pixels * 3 / 8) - 44` bytes; the compiler splits chunks to fit and rejects exhausted capacity. PNG optimizers, resizers, color conversion, or lossy formats can destroy the signal. For an external private index, the first carrier must use a byte-addressable mode.

`wav-tones` requires an existing 16/44.1/48 kHz PCM WAV cover. The compiler mixes already encoded app shards into it with four audible tones per two-bit symbol, three copies of each shard, and a short gap between packets. It writes a new 16 kHz mono 8-bit PCM WAV at the same URL, preserving the cover's duration but not its original bytes or fidelity. A `webApp` part includes `"codec":"wav-4fsk1"` and a virtual offset in the recovered ciphertext payload. `webApp.audioCarriers[url]` holds the packet hashes, complete payload hash, and start times for recovery. The browser fetches the whole WAV (at most 128 MiB), demodulates repeated packets, verifies the complete payload and per-shard hashes, then applies the existing website encoding and optional member decryption. One WAV holds at most 32 shards of 128 ciphertext bytes (4 KiB total); optional integer `"maxChunks"` on a `wav-tones` carrier limits it to 1–32 shards so a short soundtrack can carry a small demonstration. The cover must be long enough for the three copies. Put the app favicon on another carrier. The tones may be intentionally audible. This WAV profile has only local PCM tests; MP3/AAC conversion, radio, cassette, and YouTube playback need separate recovery tests. An external private index needs a byte-addressable first carrier.

`frame-luma` requires an existing PNG at least 256×128 pixels with 768 opaque pixels available for the packet. New builds emit `"codec":"frame-luma2"`. The compiler analyzes local image contrast after masking RGB low bits, chooses one opaque point from each deterministic group of 16 candidate pixels, and writes each of the 256 packet bits to the red low bit at three scattered points. Each selected channel changes by at most one 8-bit level. The public point formula depends on dimensions and cover pixels with the low bits masked; it is reproducible by the browser and provides no secrecy. A 32-byte `AWF2` packet contains one already encoded shard of at most 21 bytes, a payload length, and truncated SHA-256. The browser fetches the whole lossless PNG (at most 64 MiB), votes across three bit copies, checks the packet, then checks the full shard hash before website decoding. Protected shards reserve 16 bytes for member GCM, leaving at most 5 plaintext bytes in that frame. This repetition repairs one flipped copy of a bit, but not resizing, color conversion, or lossy video transcoding. Earlier `frame-luma1` / `AWF1` files remain readable; they used conspicuous luminance tiles. Each website frame is a separate PNG, not a muxed video. A future muxed-video profile needs frame timing, extraction, repeat placement, and transcode tests. Keep a byte-addressable first carrier for an external private index and another carrier for the app favicon.

In `png-chunk` mode, the compiler requires a PNG signature, IHDR first, and an IEND chunk ending the file. It inserts one private, ancillary, safe-to-copy `amWd` chunk immediately before IEND. The chunk's big-endian 32-bit length is the encoded data length; its CRC-32 covers ASCII `amWd` and the data. Footer v2 records the original file length, inserted data length, original SHA-256, data SHA-256, and original IEND offset. The original PNG is reconstructed exactly by removing the `amWd` chunk and footer. Offline verification checks the reconstructed original hash, data hash, chunk type, length, and CRC. The packed PNG has a trailing AMW footer, which many image decoders ignore but some tools reject or strip.

In `mp4-uuid` mode, the compiler requires an ISO BMFF file with `ftyp` as its first top-level box and a complete sequence of finite-size top-level boxes. It appends one `uuid` top-level box with 16-byte user type `416d6f6f736564576562446174613031` (ASCII `AmoosedWebData01`) and the encoded data. The box size is a big-endian 32-bit total size (24 bytes of header plus data). Footer v2 records insertion at the original EOF; unpacking removes the box and footer. Offline verification checks the UUID and box size as well as the original and data hashes. The compiler does not rewrite media sample tables or remux video. Media players and hosting pipelines may reject or transform the extra top-level box or trailing footer; test the specific MP4 and delivery path.

Only original unmodified media should be used as input for a new embedded build. The compiler rejects container-packed input rather than accidentally nesting its inserted chunk or box. The append mode continues to accept legacy append-packed input. The per-carrier data section remains contiguous in the append and container modes, preserving HTTP range and chunk integrity behavior. Carrier modes do not make public data private; keys and permissions work as described below.

### Site-wide carrier encoding

New bundles include `"encoding":{"algorithm":"AES-256-CTR","key":"64 lowercase hex digits"}` in the `webApp` index carried by `/favicon.ico`. The compiler derives this 32-byte website key deterministically from the sorted paths and SHA-256 hashes of all app resources. Every appended part is transformed with AES-CTR after optional member encryption. Its 16-byte initial counter is the first 16 bytes of SHA-256 of UTF-8 `AMW1-SITE-CTR\0` || resource path || `\0` || decimal zero-based part index. This transform preserves part length, offsets, and deterministic builds. Part SHA-256 covers the encoded bytes; the complete resource length and hash cover decoded plaintext. The reader checks the part hash, decodes with the favicon key, then authenticates member content when applicable, and finally checks the assembled resource hash. Older manifests without `encoding` still use raw parts.

The hosted bundle config may add `"encodingSalt":"64 lowercase hex digits"` to rotate this public encoding key without changing app files. The compiler feeds the 32 decoded salt bytes into the SHA-256 derivation after `AMW1-SITE-ENCODING\0` and before sorted resource paths/hashes. Without the field, v0.12 and earlier deterministic output remains unchanged. External-index builds reject `encodingSalt`; their data key is derived from the private index key instead. `rotate-project ... site` sets a new hosted salt or rotates the external index key. The carrier header/footer and reader algorithm do not change. See [ROTATION.md](ROTATION.md).

The website key is intentionally public. It prevents appended chunks from containing readily searchable plaintext and makes detached carrier files less useful without the favicon index. A changed app resource changes the derived website key and re-encodes every part. An old carrier mixed with a new index fails its stored-part SHA-256 check. An old index served together with all matching old carriers can still present the complete old snapshot; the key alone is not a cache invalidation guarantee. Anyone who obtains the favicon, a cached copy of the key, or the compiled app can decode public content. This encoding is not access control or a substitute for the separate members key and AES-GCM protection.

### External favicon key and encrypted index

`bundle.json` may set `"indexDelivery":"external"`. The ordinary site's `/favicon.ico` is then left unchanged. The compiler encrypts the **complete compiled index document**, including the `webApp` resource map and its site-wide encoding key, with AES-256-GCM and appends that ciphertext to the first selected data carrier before its footer. It writes a separate packed ICO key file outside the publish output. This small file contains an `amw.document.v1` document with `privateIndex`: `{algorithm,url,offset,length,sha256,nonce,key}`. `url` is a path on the currently visited site, never an origin or remote URL. The locator points to the encrypted index's exact byte range and SHA-256; `key` is a random 256-bit value represented as 64 lowercase hex digits. The locator and key are readable to anyone holding the file. Do not publish it with the ordinary site when using the private profile.

The packed ICO or a dedicated `.amwkey` text file may be sent as an email attachment with other customer material, including a design document or question. Both are bearer keys for the matching carrier release. The compiler prints their private output paths; the website contains only the encrypted index at the relative carrier location specified by either file. An email or other delivery channel does not authenticate a customer to the app and does not stop forwarding of the attachment.

The AES-GCM nonce is the first 12 bytes of HMAC-SHA-256(index key, `AMW1-INDEX-NONCE\0` || carrier URL || `\0` || decimal offset || `\0` || SHA-256(index payload)). Its AAD is UTF-8 `AMW1-PRIVATE-INDEX\0` || carrier URL || `\0` || decimal offset. The appended ciphertext includes the 16-byte authentication tag. The private profile derives the per-bundle AES-CTR data key with HMAC-SHA-256 using the index key, the sorted resource paths, and their plaintext hashes; the derived key appears only inside the encrypted index. The compiler reuses the saved random index key for deterministic rebuilds. Use a distinct index key for each site or edition and rotate it when access should change.

The browser verifies the local key file's AMW footer and sections, fetches the indicated byte range from the **current site's** carrier, checks its hash, decrypts and authenticates the index, then verifies app chunks against that index. A key file from another release fails when the current carrier bytes differ. Because no origin is embedded, an identical copy of all required carrier bytes and runtime on another origin can also be opened with the same key file. An old key file plus a complete still-hosted old carrier snapshot remains usable; strict rollback prevention requires a fresh trusted version signal or removal of old bytes. The site-owned runtime and same-origin scripts can read an imported key stored in that origin's browser storage; this is not protection against the site operator or other scripts running at that origin.

A complete ICO key file has a 4 KiB AMW header plus manifest and footer, so it is generally too large for one QR code. The `AMQ1` compact token carries only the validated private-index locator and key. Its binary layout is four ASCII bytes `AMQ1`, one unsigned path-byte length, an unsigned 64-bit big-endian offset, an unsigned 32-bit big-endian ciphertext length, 32 SHA-256 bytes, 12 AES-GCM nonce bytes, 32 key bytes, and the ASCII relative carrier path. Encode those bytes as unpadded base64url. The path is limited to 255 bytes; the decoder rejects malformed or noncanonical tokens and applies the same locator validation as an ICO import. The token has no origin and no app source. A dedicated `.amwkey` file is UTF-8 text `AMWKEY1:` followed by the token and one optional line ending. A project build produces both key-file forms, while a standalone bundle can output only `.amwkey` and use it for offline verification or unpacking. `amw key-link KEY.amwkey SITE_URL [QR.svg]` also accepts a packed ICO, constructs `https://site.example/amoosed.com/boot.html#amw-index=TOKEN`, and can write an SVG QR with medium error correction and a four-module quiet zone.

The site boot route removes the fragment with `history.replaceState`, asks its same-origin worker to fetch, hash, and authenticate the indicated carrier range, verifies the app identity icon, and only then saves the token in that origin's IndexedDB. File import accepts either `.amwkey` or the older packed ICO. The extension reads the worker's verified document and token on an active Amoosed tab; it does not register a custom URL scheme or store a separate cross-origin key. A QR code, its URL, `.amwkey`, and ICO grant identical access to a matching release. The fragment is omitted from the HTTP request but can be copied, forwarded, logged by scanner software, or read by same-origin scripts.

## Optional encrypted web-app parts

Member key delivery uses a separate `AMM1` compact credential. Its binary layout is four ASCII bytes `AMM1`, one unsigned key-ID byte length (1–64), 32 raw content-key bytes, and that many ASCII key-ID bytes, encoded as unpadded base64url. The decoder requires canonical encoding and the same key-ID grammar as `webApp.encryption.keyId`. A `.amwmember` file is UTF-8 text `AMWMEMBER1:` followed by that token and one optional line ending. `amw member-file KEY_ID KEY.hex OUT.amwmember` exports one; `build-project` also writes it under private project `.amw/keys/`. `amw member-link KEY.amwmember SITE_URL /amoosed.com/app/members/index.html [QR.svg]` creates a secure-origin boot URL with a protected `return` query and `#amw-member=TOKEN` fragment. The boot page strips the fragment, matches the key ID against the verified current index, and uses the site-origin worker to authenticate a protected chunk before saving the key. The key file, link, and QR are equivalent transferable bearer secrets. This delivery format does not change the carrier format or member ciphertext.

The `webApp` object may include `"encryption":{"algorithm":"AES-256-GCM","keyId":"..."}`. When present, each resource may use `"access":"public"` or `"access":"members"`. Omitted `access` means members for compatibility with the initial all-encrypted profile. Public resources have no `nonce` or `plainLength`; member parts have both fields, in addition to their carrier URL, offset, stored `length`, and stored SHA-256. Member ciphertext includes a trailing 16-byte GCM tag before the site-wide encoding is applied. Each resource's `length` and SHA-256 describe assembled plaintext. At least one member resource is required when encryption is declared. The public document and bootstrap sections are unchanged and readable without a key. This additive manifest profile requires a matching updated runtime even though the v1 header and footers are unchanged.

The member encoder derives the nonce deterministically as the first 12 bytes of HMAC-SHA-256(content key, `AMW1-NONCE\0` || AAD || SHA-256(plaintext chunk)). AAD is UTF-8 `AMW1-SEALED\0` || resource path || `\0` || MIME type || `\0` || decimal zero-based part index || `\0` || decimal plaintext chunk length. Resource paths, MIME types, key IDs, nonce uniqueness, lengths, and all allowed fields are validated. A 32-byte random members key must be unique to an edition or rotation period. Reusing an identical key and identical content produces identical ciphertext, preserving deterministic builds and revealing that equality. The browser verifies the stored-byte hash, removes the site-wide encoding, authenticates/decrypts each member part, then verifies the full plaintext resource hash. Offline verification can check stored bytes without the members key; unpacking member plaintext requires it.
