# 29 — Package integrity and canonicalization ## Purpose Digest behavior must be identical across Windows, Linux and macOS and must not depend on YAML formatting, archive order or local filesystem metadata. ## Text normalization For every declared text file: 1. reject invalid UTF-8; 2. remove a UTF-8 BOM; 3. normalize Unicode to NFC; 4. convert CRLF and CR to LF; 5. remove trailing spaces and tabs from every line; 6. preserve intentional internal blank lines; 7. end with exactly one LF. Binary resources are not text-normalized. ## Manifest canonicalization - Parse `playbook.yaml` using safe YAML parsing. - Reject duplicate mapping keys, custom tags, non-finite numbers and YAML values that cannot be represented as JSON. - Apply schema-defined semantic defaults in one versioned normalization function. - Convert the result to JSON-compatible values. - Serialize using RFC 8785 JSON Canonicalization Scheme. YAML comments and key order do not affect the digest. ## Package file inventory `package.files` is authoritative. - Every listed file must exist as a regular file below the package root. - Every package file other than `playbook.yaml` must be listed. - Directories, symlinks, hardlinks, device files and executables are rejected. - Paths are slash-separated, relative, normalized and unique. - The main template and all partials must have the appropriate declared role. - Evaluation and example IDs must match the package metadata and quality references. ## Package digest payload Construct this logical object: ```json { "algorithm": "devrunbook-package-v1", "manifest": "", "files": [ { "path": "CHANGELOG.md", "role": "changelog", "sizeBytes": 123, "sha256": "..." } ] } ``` Include only files with `digest: true`, sorted by UTF-8 path bytes. File SHA-256 is computed over normalized text bytes or original binary bytes. Serialize the payload with RFC 8785 and SHA-256 the resulting UTF-8 bytes. Changing any digested file changes the package digest and therefore requires a new published version. ## Render digest The render digest is SHA-256 over the exact final prompt bytes after platform composition: - UTF-8; - NFC; - LF endings; - stable headings and list formatting; - exactly one final LF; - no generation timestamp inside the prompt unless declared as an input. ## Repository Profile digest Parse and validate the profile, remove `metadata.contentDigest`, apply normalized ordering/defaults, serialize using RFC 8785 and SHA-256 the canonical bytes. The stored `contentDigest` must match on import. ## Run Pack manifest digest 1. Build `manifest.json` with every exported file except `manifest.json` itself. 2. Compute each file size and SHA-256 from the exact archive payload bytes. 3. Omit the `manifestDigest` property. 4. Serialize the remaining manifest with RFC 8785. 5. Compute SHA-256 and set the lowercase hex result as `manifestDigest`. 6. Write the final manifest as pretty JSON with LF endings. Pretty formatting does not define the digest; canonical JSON with the field omitted does. ## Archive construction - Paths sorted lexicographically by UTF-8 bytes. - Fixed permission bits for regular files. - Fixed archive timestamps, preferably the ZIP epoch supported by the library. - No extra fields containing local user, host or filesystem metadata. - Compression level may differ without affecting file or manifest digests. - Archive-level SHA-256 may be stored as artifact metadata but is not part of `manifest.json`. ## Verification Import verifies in this order: 1. archive limits and path safety; 2. manifest schema; 3. exact file set—no missing or undeclared files; 4. file sizes and hashes; 5. manifest digest; 6. package/profile schema and semantic validation; 7. historical render digest where a rendered prompt is present. Any failure rejects the import atomically with a path-specific error.