1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// SPDX-License-Identifier: GPL-3.0-only
#![allow(
clippy::same_name_method,
reason = "rust-embed derive generates conflicting method names"
)]
//! Neutral leaf owner of the embedded-asset byte-read seam (D3, REQ-376).
//!
//! Owned by neither projection (`install`) nor publication — both read bytes
//! *through* this module so publication is not born coupled to install's
//! projection path. This phase relocates the `install/` embed here and exposes
//! a binary-safe byte read plus the UTF-8 convenience install already relied on.
//! The `PublicationAssets` embed + `publication_manifest_bytes` arrive in
//! PHASE-02; the embed types stay private so they never leak across the seam.
use rust_embed::RustEmbed;
/// Embedded install assets — everything under `install/`. Private: the embed
/// type does not cross the seam; callers use the free functions below.
#[derive(RustEmbed)]
#[folder = "install/"]
struct InstallAssets;
/// Embedded publication assets — everything under `publication/` (the manifest).
/// A SEPARATE root from `InstallAssets` so install's projection (`build_plan`,
/// which walks `InstallAssets` only) never touches it: publication is not
/// projection (SL-223 D-C / REQ-380). Private — the embed type stays behind the seam.
#[derive(RustEmbed)]
#[folder = "publication/"]
struct PublicationAssets;
/// Read one `install/`-relative asset's bytes (`None` if absent). Binary-safe —
/// the byte-level seam the whole extraction exists to provide (REQ-376).
pub(crate) fn read_bytes(key: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
InstallAssets::get(key).map(|f| f.data)
}
/// Read one `install/`-relative asset as UTF-8 text. Error text preserved from
/// install's prior `asset_text` so its behaviour gate stays green unchanged.
pub(crate) fn read_text(key: &str) -> anyhow::Result<String> {
use anyhow::Context;
let file =
InstallAssets::get(key).with_context(|| format!("Embedded asset '{key}' is missing"))?;
let text = std::str::from_utf8(&file.data)
.with_context(|| format!("Embedded asset '{key}' is not valid UTF-8"))?;
Ok(text.to_string())
}
/// Iterate every embedded `install/`-relative asset name.
pub(crate) fn iter() -> impl Iterator<Item = std::borrow::Cow<'static, str>> {
InstallAssets::iter()
}
/// Read the shipped publication manifest's bytes (`publication/manifest.toml`),
/// `None` if the embed is hollow. The `publication` engine's `load()` admits
/// these; the disk-source admission gate (VT-3) reads the same file from
/// `CARGO_MANIFEST_DIR` so embed staleness cannot mask a defect.
pub(crate) fn publication_manifest_bytes() -> Option<std::borrow::Cow<'static, [u8]>> {
PublicationAssets::get("manifest.toml").map(|f| f.data)
}
#[cfg(test)]
mod tests {
use super::*;
/// VT-1: `read_bytes` returns exact bytes for a known text asset and
/// `read_text` agrees on the same asset — proves the seam is
/// `Cow<[u8]>`-clean (byte read is not routed through `from_utf8`).
#[test]
fn read_bytes_and_read_text_agree_on_text_asset() {
let key = "manifest.toml";
let bytes = read_bytes(key).expect("install/manifest.toml is embedded");
assert!(!bytes.is_empty(), "manifest.toml should not be empty");
let text = read_text(key).expect("install/manifest.toml is valid UTF-8");
assert_eq!(
text.as_bytes(),
bytes.as_ref(),
"read_text must reproduce read_bytes byte-for-byte on a UTF-8 asset"
);
}
/// The `InstallAssets` embed is reachable through `iter()` and includes the
/// root manifest — the enumeration `embedded_filenames`/hymn discovery rides.
#[test]
fn iter_enumerates_install_assets() {
assert!(
iter().any(|name| name == "manifest.toml"),
"iter() should enumerate the InstallAssets embed"
);
}
}