feat(workout): record runtime asset integrity
Write and verify asset-integrity.tsv for the Workout mobile release kit using the actual hemx runtime path and SHA-256 digest, so mobile shell review has inspectable runtime integrity evidence. req: examples/006
This commit is contained in:
Generated
+3
@@ -630,6 +630,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "hemx-xtask"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hemx-js",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
|
||||
@@ -56,7 +56,8 @@ The generated manifest records:
|
||||
- app identity and version;
|
||||
- the production HTTPS origin used by Android and iOS shells;
|
||||
- `target/release/hemx-workout-example` as the server artifact;
|
||||
- runtime assets served by the same release through `hemx_axum::runtime_js_path()`;
|
||||
- the exact runtime asset path and SHA-256 digest served by the same release;
|
||||
- `asset-integrity.tsv` as a plain-text integrity receipt for mobile shell review;
|
||||
- cache policy: release-scoped HTML/CSS/runtime assets only;
|
||||
- offline truth policy: app-owned command/event/projection records, never DOM
|
||||
patches or UI effect payloads;
|
||||
|
||||
@@ -7,3 +7,6 @@ publish = false
|
||||
[[bin]]
|
||||
name = "hemx-ci"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
hemx-js = { path = "../hemx-js" }
|
||||
|
||||
+35
-4
@@ -373,6 +373,10 @@ fn write_workout_mobile_release(
|
||||
config.out_dir.join("release-manifest.json"),
|
||||
workout_mobile_manifest(config, blockers),
|
||||
)?;
|
||||
fs::write(
|
||||
config.out_dir.join("asset-integrity.tsv"),
|
||||
workout_asset_integrity(),
|
||||
)?;
|
||||
fs::write(
|
||||
config.out_dir.join("BLOCKERS.md"),
|
||||
workout_mobile_blockers_md(blockers),
|
||||
@@ -409,7 +413,14 @@ fn verify_workout_mobile_release(
|
||||
let android_path = config.out_dir.join("android/twa-release.json");
|
||||
let ios_path = config.out_dir.join("ios/webview-release.json");
|
||||
let blockers_path = config.out_dir.join("BLOCKERS.md");
|
||||
for path in [&manifest_path, &android_path, &ios_path, &blockers_path] {
|
||||
let integrity_path = config.out_dir.join("asset-integrity.tsv");
|
||||
for path in [
|
||||
&manifest_path,
|
||||
&android_path,
|
||||
&ios_path,
|
||||
&blockers_path,
|
||||
&integrity_path,
|
||||
] {
|
||||
if !path.exists() {
|
||||
failures.push(format!("{} is missing", path.display()));
|
||||
}
|
||||
@@ -421,7 +432,9 @@ fn verify_workout_mobile_release(
|
||||
&config.app_id,
|
||||
&config.version,
|
||||
&config.origin,
|
||||
"hemx_axum::runtime_js_path()",
|
||||
hemx_js::RUNTIME_JS_PATH,
|
||||
hemx_js::RUNTIME_JS_HASH,
|
||||
"asset-integrity.tsv",
|
||||
"app-owned command/event/projection records",
|
||||
"secrets and signing credentials stay outside the repo",
|
||||
"rollback",
|
||||
@@ -466,6 +479,12 @@ fn verify_workout_mobile_release(
|
||||
&mut failures,
|
||||
);
|
||||
|
||||
check_file_contains(
|
||||
&integrity_path,
|
||||
&[hemx_js::RUNTIME_JS_PATH, hemx_js::RUNTIME_JS_HASH, "sha256"],
|
||||
&mut failures,
|
||||
);
|
||||
|
||||
for blocker in mobile_external_blockers(config) {
|
||||
check_file_contains(&manifest_path, &[&blocker], &mut failures);
|
||||
check_file_contains(&blockers_path, &[&blocker], &mut failures);
|
||||
@@ -496,13 +515,23 @@ fn check_file_contains(path: &Path, needles: &[&str], failures: &mut Vec<String>
|
||||
}
|
||||
}
|
||||
|
||||
fn workout_asset_integrity() -> String {
|
||||
format!(
|
||||
"path\talgorithm\tdigest\n{}\tsha256\t{}\n",
|
||||
hemx_js::RUNTIME_JS_PATH,
|
||||
hemx_js::RUNTIME_JS_HASH
|
||||
)
|
||||
}
|
||||
|
||||
fn workout_mobile_manifest(config: &WorkoutMobileConfig, blockers: &[String]) -> String {
|
||||
format!(
|
||||
"{{\n \"app_id\": \"{}\",\n \"name\": \"{}\",\n \"version\": \"{}\",\n \"origin\": \"{}\",\n \"server_binary\": \"target/release/hemx-workout-example\",\n \"runtime_asset_path\": \"served by hemx_axum::runtime_js_path() from the same release\",\n \"cache_policy\": \"cache only release-scoped HTML/CSS/runtime assets; never store DOM patches or UI effects as truth\",\n \"state_policy\": \"app-owned command/event/projection records\",\n \"host_result_kinds\": [\"denied\", \"timeout\", \"unavailable\", \"error\"],\n \"environment_boundary\": \"public mobile shell config lives here; secrets and signing credentials stay outside the repo\",\n \"rollback\": \"redeploy the previous server binary and matching mobile shell metadata; rebuild store artifacts with the previous version/signing inputs\",\n \"android\": \"android/twa-release.json\",\n \"ios\": \"ios/webview-release.json\",\n \"external_blockers\": [{}]\n}}\n",
|
||||
"{{\n \"app_id\": \"{}\",\n \"name\": \"{}\",\n \"version\": \"{}\",\n \"origin\": \"{}\",\n \"server_binary\": \"target/release/hemx-workout-example\",\n \"runtime_asset_path\": \"{}\",\n \"runtime_asset_sha256\": \"{}\",\n \"asset_integrity\": \"asset-integrity.tsv\",\n \"cache_policy\": \"cache only release-scoped HTML/CSS/runtime assets; never store DOM patches or UI effects as truth\",\n \"state_policy\": \"app-owned command/event/projection records\",\n \"host_result_kinds\": [\"denied\", \"timeout\", \"unavailable\", \"error\"],\n \"environment_boundary\": \"public mobile shell config lives here; secrets and signing credentials stay outside the repo\",\n \"rollback\": \"redeploy the previous server binary and matching mobile shell metadata; rebuild store artifacts with the previous version/signing inputs\",\n \"android\": \"android/twa-release.json\",\n \"ios\": \"ios/webview-release.json\",\n \"external_blockers\": [{}]\n}}\n",
|
||||
json_escape(&config.app_id),
|
||||
json_escape(&config.app_name),
|
||||
json_escape(&config.version),
|
||||
json_escape(&config.origin),
|
||||
json_escape(hemx_js::RUNTIME_JS_PATH),
|
||||
json_escape(hemx_js::RUNTIME_JS_HASH),
|
||||
json_string_list(blockers),
|
||||
)
|
||||
}
|
||||
@@ -936,7 +965,9 @@ mod tests {
|
||||
let manifest = workout_mobile_manifest(&config, &blockers);
|
||||
|
||||
assert!(manifest.contains("target/release/hemx-workout-example"));
|
||||
assert!(manifest.contains("hemx_axum::runtime_js_path()"));
|
||||
assert!(manifest.contains(hemx_js::RUNTIME_JS_PATH));
|
||||
assert!(manifest.contains(hemx_js::RUNTIME_JS_HASH));
|
||||
assert!(manifest.contains("asset-integrity.tsv"));
|
||||
assert!(manifest.contains("app-owned command/event/projection records"));
|
||||
assert!(manifest.contains(
|
||||
"\"host_result_kinds\": [\"denied\", \"timeout\", \"unavailable\", \"error\"]"
|
||||
|
||||
Reference in New Issue
Block a user