use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
pub const FORGE_SCHEMA_VERSION: u16 = 1;
pub const CANONICAL_DEFINITION_FORMAT: &str = "pkl";
pub const CANONICAL_REQUEST_EXTENSION: &str = "pkl";
pub const CANONICAL_MANIFEST_EXTENSION: &str = "pkl";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgeRequest {
#[serde(default = "default_schema_version")]
pub schema_version: u16,
pub operation: ForgeOperation,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
pub hostname: String,
pub arch: ForgeArch,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_dir: Option<PathBuf>,
pub target: ForgeTarget,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub polymerize_defaults: Option<PolymerizeDefaults>,
#[serde(default)]
pub network: NetworkPolicy,
#[serde(default)]
pub safety: SafetyPolicy,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub labels: Vec<String>,
}
impl ForgeRequest {
pub fn new(
operation: ForgeOperation,
hostname: impl Into<String>,
arch: ForgeArch,
target: ForgeTarget,
) -> Self {
Self {
schema_version: FORGE_SCHEMA_VERSION,
operation,
profile: None,
hostname: hostname.into(),
arch,
output_dir: None,
target,
polymerize_defaults: None,
network: NetworkPolicy::default(),
safety: SafetyPolicy::default(),
labels: Vec::new(),
}
}
pub fn profile(mut self, profile: impl Into<String>) -> Self {
self.profile = Some(profile.into());
self
}
pub fn output_dir(mut self, output_dir: impl Into<PathBuf>) -> Self {
self.output_dir = Some(output_dir.into());
self
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum ForgeOperation {
Bundle,
UsbInstall,
Image,
Netboot,
RemotePolymerize,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ForgeArch {
X86_64,
Aarch64,
}
impl ForgeArch {
pub fn label(self) -> &'static str {
match self {
Self::X86_64 => "x86_64",
Self::Aarch64 => "aarch64",
}
}
pub fn iso_url(self) -> &'static str {
match self {
Self::X86_64 => {
"https://channels.nixos.org/nixos-24.11/latest-nixos-minimal-x86_64-linux.iso"
}
Self::Aarch64 => {
"https://channels.nixos.org/nixos-24.11/latest-nixos-minimal-aarch64-linux.iso"
}
}
}
pub fn iso_filename(self) -> String {
format!("nixos-minimal-{}.iso", self.label())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgeTarget {
pub kind: TargetKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disk: Option<String>,
}
impl ForgeTarget {
pub fn bundle() -> Self {
Self {
kind: TargetKind::Bundle,
disk: None,
}
}
pub fn usb(disk: Option<impl Into<String>>) -> Self {
Self {
kind: TargetKind::Usb,
disk: disk.map(Into::into),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum TargetKind {
Bundle,
Usb,
Image,
Netboot,
Remote,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PolymerizeDefaults {
pub username: String,
pub timezone: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub install_mode: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ssh_authorized_keys: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct NetworkPolicy {
#[serde(default)]
pub require_wired: bool,
#[serde(default = "default_wifi_allowed")]
pub wifi_allowed: bool,
}
impl Default for NetworkPolicy {
fn default() -> Self {
Self {
require_wired: false,
wifi_allowed: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SafetyPolicy {
#[serde(default)]
pub allow_destructive_flash: bool,
#[serde(default)]
pub allow_internal_disk_selection: bool,
#[serde(default = "default_require_operator_confirmation")]
pub require_operator_confirmation: bool,
#[serde(default)]
pub requires_target_attestation: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target_attestation: Option<TargetAttestation>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_targets: Vec<TargetAttestation>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub forbidden_targets: Vec<TargetAttestation>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum TargetAttestation {
ExternalUsbSsd,
ExternalThunderboltSsd,
InternalAppleNvme,
InternalAppleStorage,
}
impl TargetAttestation {
fn is_internal_apple_storage(self) -> bool {
matches!(self, Self::InternalAppleNvme | Self::InternalAppleStorage)
}
}
impl Default for SafetyPolicy {
fn default() -> Self {
Self {
allow_destructive_flash: false,
allow_internal_disk_selection: false,
require_operator_confirmation: true,
requires_target_attestation: false,
target_attestation: None,
allowed_targets: Vec::new(),
forbidden_targets: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgePlan {
pub schema_version: u16,
pub operation: ForgeOperation,
pub hostname: String,
pub arch: ForgeArch,
pub output_dir: PathBuf,
pub iso: Option<ForgeIsoPlan>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub destructive_actions: Vec<DestructiveAction>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<ForgeDiagnostic>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blockers: Vec<ForgeDiagnostic>,
}
impl ForgePlan {
pub fn is_blocked(&self) -> bool {
!self.blockers.is_empty()
}
}
pub fn load_request(path: impl Into<PathBuf>) -> Result<ForgeRequest> {
let path = path.into();
match path.extension().and_then(|ext| ext.to_str()) {
Some("json") => {
let content = std::fs::read_to_string(&path)
.with_context(|| format!("reading {}", path.display()))?;
serde_json::from_str(&content)
.with_context(|| format!("decoding JSON forge request {}", path.display()))
}
Some("pkl") => evaluate_pkl_request(&path)
.with_context(|| format!("evaluating Pkl forge request {}", path.display())),
Some(other) => {
bail!("unsupported forge request extension .{other}; canonical requests use .pkl")
}
None => bail!("forge request path must have an extension; canonical requests use .pkl"),
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgeIsoPlan {
pub url: String,
pub filename: String,
pub cache_path: PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DestructiveAction {
pub code: String,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgeDiagnostic {
pub code: String,
pub message: String,
}
impl ForgeDiagnostic {
pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
code: code.into(),
message: message.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgePreflightReport {
pub schema_version: u16,
pub valid: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<ForgeDiagnostic>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub errors: Vec<ForgeDiagnostic>,
}
impl ForgePreflightReport {
pub fn from_diagnostics(warnings: Vec<ForgeDiagnostic>, errors: Vec<ForgeDiagnostic>) -> Self {
Self {
schema_version: FORGE_SCHEMA_VERSION,
valid: errors.is_empty(),
warnings,
errors,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ForgeEvent {
PhaseStarted {
schema_version: u16,
phase: String,
},
PhaseCompleted {
schema_version: u16,
phase: String,
},
Warning {
schema_version: u16,
code: String,
message: String,
},
Blocker {
schema_version: u16,
code: String,
message: String,
},
ArtifactCreated {
schema_version: u16,
path: PathBuf,
},
RunCompleted {
schema_version: u16,
},
RunFailed {
schema_version: u16,
message: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgeCheckReport {
pub schema: String,
pub evaluator: String,
pub valid: bool,
pub template: ForgeCheckTemplate,
pub capabilities: ForgeCheckCapabilities,
pub inputs: Vec<ForgeCheckInput>,
pub outputs: Vec<ForgeCheckOutput>,
pub warnings: Vec<ForgeDiagnostic>,
pub errors: Vec<ForgeDiagnostic>,
}
impl ForgeCheckReport {
pub fn exit_code(&self) -> i32 {
if self.valid {
return 0;
}
if self.errors.iter().any(|error| {
matches!(
error.code.as_str(),
"UNSAFE_RAW_DISK_TARGET"
| "UNSAFE_CLUSTER_INIT"
| "UNSAFE_JOIN_TOKEN"
| "UNSAFE_EXECUTION_HOOK"
| "UNSAFE_PRIVATE_IMPORT"
| "UNDECLARED_DESTRUCTIVE_CAPABILITY"
| "PLAN_ONLY_EXECUTE_CAPABLE"
)
}) {
3
} else {
1
}
}
pub fn evaluator_error(message: String) -> Self {
Self {
schema: FORGE_TEMPLATE_SCHEMA.to_string(),
evaluator: CANONICAL_DEFINITION_FORMAT.to_string(),
valid: false,
template: ForgeCheckTemplate::default(),
capabilities: ForgeCheckCapabilities::default(),
inputs: Vec::new(),
outputs: Vec::new(),
warnings: Vec::new(),
errors: vec![ForgeDiagnostic::new("EVALUATOR_ERROR", message)],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgeCheckTemplate {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(rename = "evaluationMode")]
pub evaluation_mode: String,
#[serde(rename = "safetyClass")]
pub safety_class: String,
#[serde(rename = "canonicalFormat")]
pub canonical_format: String,
}
impl Default for ForgeCheckTemplate {
fn default() -> Self {
Self {
id: "unknown".to_string(),
version: None,
evaluation_mode: "plan-only".to_string(),
safety_class: "unknown".to_string(),
canonical_format: CANONICAL_DEFINITION_FORMAT.to_string(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgeCheckCapabilities {
pub destructive: Vec<String>,
pub network: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgeCheckInput {
pub name: String,
pub kind: String,
pub required: bool,
pub sensitive: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForgeCheckOutput {
pub kind: String,
}
const FORGE_TEMPLATE_SCHEMA: &str = "dev.styrene.nex.forge-template.v1";
pub fn check_template(
template_path: &Path,
metadata_path: Option<&Path>,
_no_execute: bool,
) -> Result<ForgeCheckReport> {
if template_path.extension().and_then(|ext| ext.to_str()) != Some("pkl") {
bail!("forge template check requires a canonical .pkl payload");
}
let source = std::fs::read_to_string(template_path)
.with_context(|| format!("reading {}", template_path.display()))?;
let metadata = metadata_path
.map(load_forge_template_metadata)
.transpose()?;
let public_by_metadata = metadata
.as_ref()
.and_then(|metadata| metadata.visibility.as_deref())
== Some("public");
let mut source_errors = Vec::new();
if public_by_metadata {
validate_public_template_source_safety(&source, &mut source_errors);
}
let evaluated = crate::pkl::evaluate_json(template_path)?;
let request = request_from_evaluated_pkl(evaluated.clone())?;
if !public_by_metadata && evaluated_visibility_is_public(&evaluated) {
validate_public_template_source_safety(&source, &mut source_errors);
}
let mut report = build_check_report(&evaluated, &request, metadata.as_ref());
report.errors.extend(source_errors);
report.valid = report.errors.is_empty();
Ok(report)
}
fn build_check_report(
evaluated: &serde_json::Value,
request: &ForgeRequest,
metadata: Option<&ForgeTemplateMetadata>,
) -> ForgeCheckReport {
let object = evaluated.as_object();
let template_id = object
.and_then(|object| value_string(object, &["id", "name"]))
.or_else(|| metadata.and_then(|metadata| metadata.id.clone()))
.unwrap_or_else(|| "unknown".to_string());
let evaluation_mode = metadata
.and_then(|metadata| metadata.evaluation_mode.clone())
.unwrap_or_else(|| "plan-only".to_string());
let safety_class = metadata
.and_then(|metadata| metadata.safety_class.clone())
.unwrap_or_else(|| safety_class_for_operation(request.operation).to_string());
let destructive = destructive_capabilities(request);
let network = network_capabilities(evaluated, request);
let mut warnings = Vec::new();
let mut errors = Vec::new();
let template = ForgeCheckTemplate {
id: template_id,
version: metadata.and_then(|metadata| metadata.version.clone()),
evaluation_mode,
safety_class,
canonical_format: CANONICAL_DEFINITION_FORMAT.to_string(),
};
if let Some(metadata) = metadata {
validate_metadata_agreement(
metadata,
evaluated,
&template,
&destructive,
&network,
&mut errors,
);
} else {
warnings.push(ForgeDiagnostic::new(
"METADATA_NOT_PROVIDED",
"No forge.toml metadata was provided; metadata agreement checks were skipped.",
));
}
let evaluated_visibility = object.and_then(|object| value_string(object, &["visibility"]));
let is_public = metadata
.and_then(|metadata| metadata.visibility.as_deref())
.or(evaluated_visibility.as_deref())
== Some("public");
if is_public {
validate_public_template_safety(evaluated, &mut errors);
}
validate_evaluation_mode(&template, request, &destructive, &mut errors);
ForgeCheckReport {
schema: metadata
.and_then(|metadata| metadata.schema.clone())
.unwrap_or_else(|| FORGE_TEMPLATE_SCHEMA.to_string()),
evaluator: metadata
.and_then(|metadata| metadata.evaluator.clone())
.unwrap_or_else(|| CANONICAL_DEFINITION_FORMAT.to_string()),
valid: errors.is_empty(),
template,
capabilities: ForgeCheckCapabilities {
destructive,
network,
},
inputs: check_inputs(evaluated),
outputs: check_outputs(request),
warnings,
errors,
}
}
fn evaluated_visibility_is_public(evaluated: &serde_json::Value) -> bool {
evaluated
.as_object()
.and_then(|object| value_string(object, &["visibility"]))
.as_deref()
== Some("public")
}
#[derive(Debug, Clone, Default, Deserialize)]
struct ForgeTemplateMetadataFile {
#[serde(default)]
forge_template: ForgeTemplateMetadata,
}
#[derive(Debug, Clone, Default, Deserialize)]
struct ForgeTemplateMetadata {
id: Option<String>,
version: Option<String>,
schema: Option<String>,
evaluator: Option<String>,
evaluation_mode: Option<String>,
safety_class: Option<String>,
canonical_format: Option<String>,
visibility: Option<String>,
profile_class: Option<String>,
destructive_capabilities: Option<Vec<String>>,
network_requirements: Option<Vec<String>>,
}
fn load_forge_template_metadata(path: &Path) -> Result<ForgeTemplateMetadata> {
let content =
std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
let parsed: ForgeTemplateMetadataFile = toml::from_str(&content)
.with_context(|| format!("decoding forge template metadata {}", path.display()))?;
Ok(parsed.forge_template)
}
fn validate_metadata_agreement(
metadata: &ForgeTemplateMetadata,
evaluated: &serde_json::Value,
template: &ForgeCheckTemplate,
destructive: &[String],
network: &[String],
errors: &mut Vec<ForgeDiagnostic>,
) {
let object = evaluated.as_object();
compare_optional(
"id",
metadata.id.as_deref(),
Some(template.id.as_str()),
"METADATA_ID_MISMATCH",
errors,
);
compare_optional(
"canonical_format",
metadata.canonical_format.as_deref(),
Some(CANONICAL_DEFINITION_FORMAT),
"METADATA_CANONICAL_FORMAT_MISMATCH",
errors,
);
compare_optional(
"evaluator",
metadata.evaluator.as_deref(),
Some(CANONICAL_DEFINITION_FORMAT),
"METADATA_EVALUATOR_MISMATCH",
errors,
);
compare_optional(
"evaluation_mode",
metadata.evaluation_mode.as_deref(),
Some(template.evaluation_mode.as_str()),
"METADATA_EVALUATION_MODE_MISMATCH",
errors,
);
compare_optional(
"safety_class",
metadata.safety_class.as_deref(),
Some(template.safety_class.as_str()),
"METADATA_SAFETY_CLASS_MISMATCH",
errors,
);
if let Some(profile_class) = metadata.profile_class.as_deref() {
let evaluated_profile_class =
object.and_then(|object| value_string(object, &["profileClass", "profile_class"]));
compare_optional(
"profile_class",
Some(profile_class),
evaluated_profile_class.as_deref(),
"METADATA_PROFILE_CLASS_MISMATCH",
errors,
);
}
compare_string_sets(
"destructive_capabilities",
metadata.destructive_capabilities.as_deref(),
destructive,
"METADATA_DESTRUCTIVE_CAPABILITIES_MISMATCH",
errors,
);
compare_string_sets(
"network_requirements",
metadata.network_requirements.as_deref(),
network,
"METADATA_NETWORK_REQUIREMENTS_MISMATCH",
errors,
);
}
fn compare_optional(
field: &str,
declared: Option<&str>,
detected: Option<&str>,
code: &str,
errors: &mut Vec<ForgeDiagnostic>,
) {
let Some(declared) = declared else {
return;
};
if detected != Some(declared) {
errors.push(ForgeDiagnostic::new(
code,
format!(
"Metadata field {field} declares {declared:?}, but Nex detected {:?}.",
detected.unwrap_or("<missing>")
),
));
}
}
fn compare_string_sets(
field: &str,
declared: Option<&[String]>,
detected: &[String],
code: &str,
errors: &mut Vec<ForgeDiagnostic>,
) {
let Some(declared) = declared else {
return;
};
let declared: BTreeSet<&str> = declared.iter().map(String::as_str).collect();
let detected: BTreeSet<&str> = detected.iter().map(String::as_str).collect();
if declared != detected {
errors.push(ForgeDiagnostic::new(
code,
format!(
"Metadata field {field} declares {:?}, but Nex detected {:?}.",
declared, detected
),
));
}
}
fn validate_evaluation_mode(
template: &ForgeCheckTemplate,
request: &ForgeRequest,
destructive: &[String],
errors: &mut Vec<ForgeDiagnostic>,
) {
if template.evaluation_mode != "plan-only" {
errors.push(ForgeDiagnostic::new(
"UNSUPPORTED_EVALUATION_MODE",
format!(
"Evaluation mode {:?} is not supported by forge check.",
template.evaluation_mode
),
));
}
if template.evaluation_mode == "plan-only"
&& (request.operation == ForgeOperation::UsbInstall
|| destructive.iter().any(|cap| cap == "usb-flash"))
{
errors.push(ForgeDiagnostic::new(
"PLAN_ONLY_EXECUTE_CAPABLE",
"Plan-only templates cannot imply USB flashing or direct execution.",
));
}
for capability in destructive {
if template.safety_class != *capability {
errors.push(ForgeDiagnostic::new(
"UNDECLARED_DESTRUCTIVE_CAPABILITY",
format!(
"Safety class {:?} does not match detected capability {:?}.",
template.safety_class, capability
),
));
}
}
}
fn validate_public_template_safety(value: &serde_json::Value, errors: &mut Vec<ForgeDiagnostic>) {
walk_json(value, &mut Vec::new(), &mut |path, value| {
let key = path.last().map(String::as_str).unwrap_or_default();
match value {
serde_json::Value::String(text) => {
if is_raw_disk_target(text) {
errors.push(ForgeDiagnostic::new(
"UNSAFE_RAW_DISK_TARGET",
format!(
"Public forge template contains fixed raw disk target at {path:?}."
),
));
}
if is_private_import(text) {
errors.push(ForgeDiagnostic::new(
"UNSAFE_PRIVATE_IMPORT",
format!("Public forge template contains private/local import at {path:?}."),
));
}
if key.to_ascii_lowercase().contains("token") || text.contains("join_token") {
errors.push(ForgeDiagnostic::new(
"UNSAFE_JOIN_TOKEN",
format!("Public forge template contains token-like field at {path:?}."),
));
}
}
serde_json::Value::Bool(true) => {
let key = key.to_ascii_lowercase();
if key == "clusterinit" || key == "cluster_init" {
errors.push(ForgeDiagnostic::new(
"UNSAFE_CLUSTER_INIT",
format!(
"Public forge template enables cluster initialization at {path:?}."
),
));
}
}
_ => {}
}
let key = key.to_ascii_lowercase();
if matches!(
key.as_str(),
"hook" | "hooks" | "command" | "commands" | "script" | "scripts"
) || key.ends_with("hook")
|| key.ends_with("command")
|| key.ends_with("script")
{
errors.push(ForgeDiagnostic::new(
"UNSAFE_EXECUTION_HOOK",
format!("Public forge template contains execution hook field at {path:?}."),
));
}
});
}
fn validate_public_template_source_safety(source: &str, errors: &mut Vec<ForgeDiagnostic>) {
for (line_index, line) in source.lines().enumerate() {
let line_no = line_index + 1;
let trimmed = line.trim();
if trimmed.starts_with("import") && contains_private_import_text(trimmed) {
errors.push(ForgeDiagnostic::new(
"UNSAFE_PRIVATE_IMPORT",
format!("Public forge template contains private/local import on line {line_no}."),
));
}
if contains_token_text(trimmed) {
errors.push(ForgeDiagnostic::new(
"UNSAFE_JOIN_TOKEN",
format!("Public forge template contains token-like source text on line {line_no}."),
));
}
if contains_cluster_init_text(trimmed) {
errors.push(ForgeDiagnostic::new(
"UNSAFE_CLUSTER_INIT",
format!(
"Public forge template contains cluster-init source text on line {line_no}."
),
));
}
if contains_execution_hook_text(trimmed) {
errors.push(ForgeDiagnostic::new(
"UNSAFE_EXECUTION_HOOK",
format!(
"Public forge template contains execution-hook source text on line {line_no}."
),
));
}
if contains_raw_disk_text(trimmed) {
errors.push(ForgeDiagnostic::new(
"UNSAFE_RAW_DISK_TARGET",
format!("Public forge template contains fixed raw disk target on line {line_no}."),
));
}
}
}
fn contains_private_import_text(line: &str) -> bool {
line.contains("file:")
|| line.contains("import(\"/")
|| line.contains("import(\"../")
|| line.contains("import(\"./private")
|| line.contains("ssh://")
}
fn contains_token_text(line: &str) -> bool {
let line = line.to_ascii_lowercase();
line.contains("join_token") || line.contains("bootstrap_token") || line.contains("token_file")
}
fn contains_cluster_init_text(line: &str) -> bool {
let line = line.to_ascii_lowercase();
line.contains("clusterinit") || line.contains("cluster_init")
}
fn contains_execution_hook_text(line: &str) -> bool {
let line = line.to_ascii_lowercase();
line.contains("postinstall")
|| line.contains("preinstall")
|| line.contains("exechook")
|| line.contains("exec_hook")
|| line.contains("shellhook")
|| line.contains("shell_hook")
}
fn contains_raw_disk_text(line: &str) -> bool {
line.contains("/dev/sda")
|| line.contains("/dev/vda")
|| line.contains("/dev/xvda")
|| line.contains("/dev/nvme")
|| line.contains("/dev/disk/")
}
fn walk_json<F>(value: &serde_json::Value, path: &mut Vec<String>, visit: &mut F)
where
F: FnMut(&[String], &serde_json::Value),
{
visit(path, value);
match value {
serde_json::Value::Object(object) => {
for (key, child) in object {
path.push(key.clone());
walk_json(child, path, visit);
path.pop();
}
}
serde_json::Value::Array(values) => {
for (index, child) in values.iter().enumerate() {
path.push(index.to_string());
walk_json(child, path, visit);
path.pop();
}
}
_ => {}
}
}
fn is_raw_disk_target(value: &str) -> bool {
value == "/dev/sda"
|| value == "/dev/vda"
|| value == "/dev/xvda"
|| value.starts_with("/dev/nvme")
|| value.starts_with("/dev/disk/")
}
fn is_private_import(value: &str) -> bool {
value.starts_with("file:")
|| (value.starts_with('/') && !value.starts_with("/dev/"))
|| value.starts_with("../")
|| value.starts_with("./private")
|| value.contains("ssh://")
}
fn destructive_capabilities(request: &ForgeRequest) -> Vec<String> {
match request.operation {
ForgeOperation::Image => vec!["image-build".to_string()],
ForgeOperation::UsbInstall => vec!["usb-flash".to_string()],
_ => Vec::new(),
}
}
fn network_capabilities(evaluated: &serde_json::Value, request: &ForgeRequest) -> Vec<String> {
let mut network = BTreeSet::new();
if request.network.require_wired {
network.insert("wired-network".to_string());
}
if evaluated
.pointer("/plan/requiresNetwork")
.and_then(|value| value.as_bool())
== Some(true)
{
network.insert("package-download".to_string());
}
network.into_iter().collect()
}
fn safety_class_for_operation(operation: ForgeOperation) -> &'static str {
match operation {
ForgeOperation::Image => "image-build",
ForgeOperation::UsbInstall => "usb-flash",
ForgeOperation::Bundle => "bundle",
ForgeOperation::Netboot => "netboot",
ForgeOperation::RemotePolymerize => "remote-polymerize",
}
}
fn check_inputs(evaluated: &serde_json::Value) -> Vec<ForgeCheckInput> {
if evaluated
.pointer("/plan/target")
.and_then(|value| value.as_str())
== Some("operator-selected")
{
return vec![ForgeCheckInput {
name: "target".to_string(),
kind: "operator-selected".to_string(),
required: true,
sensitive: true,
}];
}
Vec::new()
}
fn check_outputs(request: &ForgeRequest) -> Vec<ForgeCheckOutput> {
let kind = match request.operation {
ForgeOperation::Image => "image-plan",
ForgeOperation::UsbInstall => "usb-install-plan",
ForgeOperation::Bundle => "bundle-plan",
ForgeOperation::Netboot => "netboot-plan",
ForgeOperation::RemotePolymerize => "remote-polymerize-plan",
};
vec![ForgeCheckOutput {
kind: kind.to_string(),
}]
}
pub fn plan_request(request: &ForgeRequest) -> Result<ForgePlan> {
if request.schema_version != FORGE_SCHEMA_VERSION {
bail!(
"unsupported forge request schema_version {}",
request.schema_version
);
}
let mut warnings = Vec::new();
let mut blockers = Vec::new();
validate_hostname(&request.hostname, &mut blockers);
validate_operation_target(request, &mut blockers);
if request.network.require_wired {
warnings.push(ForgeDiagnostic::new(
"WIRED_NETWORK_REQUIRED",
"This request requires wired network during install/polymerize.",
));
}
if request.operation == ForgeOperation::RemotePolymerize {
blockers.push(ForgeDiagnostic::new(
"REMOTE_POLYMERIZE_NOT_IMPLEMENTED",
"Remote polymerize is a later forge target and is not implemented yet.",
));
}
let output_dir = request.output_dir.clone().unwrap_or_else(|| {
std::env::temp_dir()
.join("nex-forge")
.join(bundle_name(request.profile.as_deref()))
});
let iso = uses_installer_iso(request.operation).then(|| ForgeIsoPlan {
url: request.arch.iso_url().to_string(),
filename: request.arch.iso_filename(),
cache_path: output_dir.join(request.arch.iso_filename()),
});
let destructive_actions = destructive_actions(request);
if !destructive_actions.is_empty() && !request.safety.allow_destructive_flash {
blockers.push(ForgeDiagnostic::new(
"DESTRUCTIVE_FLASH_NOT_ALLOWED",
"Request selects a destructive USB flash but safety.allow_destructive_flash is false.",
));
}
validate_target_attestation(request, &destructive_actions, &mut blockers);
Ok(ForgePlan {
schema_version: FORGE_SCHEMA_VERSION,
operation: request.operation,
hostname: request.hostname.clone(),
arch: request.arch,
output_dir,
iso,
destructive_actions,
warnings,
blockers,
})
}
fn default_schema_version() -> u16 {
FORGE_SCHEMA_VERSION
}
fn default_wifi_allowed() -> bool {
true
}
fn default_require_operator_confirmation() -> bool {
true
}
fn validate_target_attestation(
request: &ForgeRequest,
destructive_actions: &[DestructiveAction],
blockers: &mut Vec<ForgeDiagnostic>,
) {
if destructive_actions.is_empty() {
return;
}
let Some(attestation) = request.safety.target_attestation else {
if request.safety.requires_target_attestation {
blockers.push(ForgeDiagnostic::new(
"TARGET_ATTESTATION_REQUIRED",
"Request requires target attestation for destructive actions, but safety.target_attestation is not set.",
));
}
return;
};
if !request.safety.allowed_targets.is_empty()
&& !request.safety.allowed_targets.contains(&attestation)
{
blockers.push(ForgeDiagnostic::new(
"TARGET_ATTESTATION_NOT_ALLOWED",
format!("Target attestation {attestation:?} is not listed in safety.allowed_targets."),
));
}
if request.safety.forbidden_targets.contains(&attestation) {
blockers.push(ForgeDiagnostic::new(
"TARGET_ATTESTATION_FORBIDDEN",
format!("Target attestation {attestation:?} is listed in safety.forbidden_targets."),
));
}
if attestation.is_internal_apple_storage() && !request.safety.allow_internal_disk_selection {
blockers.push(ForgeDiagnostic::new(
"INTERNAL_APPLE_STORAGE_FORBIDDEN",
"Internal Apple storage targets require safety.allow_internal_disk_selection = true.",
));
}
}
fn uses_installer_iso(operation: ForgeOperation) -> bool {
matches!(
operation,
ForgeOperation::Bundle | ForgeOperation::UsbInstall
)
}
fn bundle_name(profile: Option<&str>) -> String {
profile
.map(|r| r.replace('/', "_"))
.unwrap_or_else(|| "styx".to_string())
}
fn validate_hostname(hostname: &str, blockers: &mut Vec<ForgeDiagnostic>) {
if hostname.is_empty() {
blockers.push(ForgeDiagnostic::new(
"INVALID_HOSTNAME",
"Hostname cannot be empty.",
));
return;
}
if hostname.starts_with('-') || hostname.ends_with('-') {
blockers.push(ForgeDiagnostic::new(
"INVALID_HOSTNAME",
"Hostname cannot start or end with a hyphen.",
));
}
if !hostname
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-')
{
blockers.push(ForgeDiagnostic::new(
"INVALID_HOSTNAME",
"Hostname must be alphanumeric with hyphens only.",
));
}
}
fn validate_operation_target(request: &ForgeRequest, blockers: &mut Vec<ForgeDiagnostic>) {
let valid = matches!(
(request.operation, request.target.kind),
(ForgeOperation::Bundle, TargetKind::Bundle)
| (ForgeOperation::UsbInstall, TargetKind::Usb)
| (ForgeOperation::Image, TargetKind::Image)
| (ForgeOperation::Netboot, TargetKind::Netboot)
| (ForgeOperation::RemotePolymerize, TargetKind::Remote)
);
if !valid {
blockers.push(ForgeDiagnostic::new(
"TARGET_OPERATION_MISMATCH",
"Forge operation does not match target kind.",
));
}
if request.operation == ForgeOperation::UsbInstall && request.target.disk.is_none() {
blockers.push(ForgeDiagnostic::new(
"USB_TARGET_DISK_REQUIRED",
"USB install requests must specify target.disk.",
));
}
}
fn destructive_actions(request: &ForgeRequest) -> Vec<DestructiveAction> {
if request.operation != ForgeOperation::UsbInstall {
return Vec::new();
}
let Some(disk) = request.target.disk.as_ref() else {
return Vec::new();
};
vec![DestructiveAction {
code: "FLASH_USB".to_string(),
message: format!("Flash installer media to {disk}."),
target: Some(disk.clone()),
}]
}
#[derive(Default)]
struct PklRequestBuilder {
schema_version: Option<u16>,
operation: Option<ForgeOperation>,
profile: Option<String>,
hostname: Option<String>,
arch: Option<ForgeArch>,
output_dir: Option<PathBuf>,
target_kind: Option<TargetKind>,
disk: Option<String>,
polymerize_defaults: Option<PolymerizeDefaults>,
network: NetworkPolicy,
safety: SafetyPolicy,
labels: Vec<String>,
}
impl PklRequestBuilder {
fn finish(mut self) -> Result<ForgeRequest> {
let operation = self.operation.unwrap_or(ForgeOperation::Bundle);
let target_kind = self
.target_kind
.unwrap_or_else(|| default_target_kind(operation));
let mut request = ForgeRequest::new(
operation,
self.hostname.unwrap_or_else(|| "nixos".to_string()),
self.arch.unwrap_or(ForgeArch::X86_64),
ForgeTarget {
kind: target_kind,
disk: self.disk.take(),
},
);
request.schema_version = self.schema_version.unwrap_or(FORGE_SCHEMA_VERSION);
request.profile = self.profile.take();
request.output_dir = self.output_dir.take();
request.polymerize_defaults = self.polymerize_defaults.take().map(|mut defaults| {
if defaults.username.is_empty() {
defaults.username = "user".to_string();
}
if defaults.timezone.is_empty() {
defaults.timezone = "UTC".to_string();
}
defaults
});
request.network = self.network;
request.safety = self.safety;
request.labels = self.labels;
Ok(request)
}
}
fn evaluate_pkl_request(path: &Path) -> Result<ForgeRequest> {
let value = crate::pkl::evaluate_json(path)?;
request_from_evaluated_pkl(value)
}
fn request_from_evaluated_pkl(value: serde_json::Value) -> Result<ForgeRequest> {
let object = value
.as_object()
.context("evaluated Pkl forge definition must be an object")?;
let mut builder = PklRequestBuilder {
schema_version: value_u16(object, &["schemaVersion", "schema_version"])?,
operation: value_string(object, &["operation"])
.map(|operation| parse_operation(&operation))
.transpose()?,
profile: value_string(object, &["profile"]),
hostname: value_string(object, &["hostname"]),
arch: value_string(object, &["arch"])
.map(|arch| parse_arch(&arch))
.transpose()?,
output_dir: value_string(object, &["outputDir", "output_dir"]).map(PathBuf::from),
labels: value_string_array(object, &["labels"]),
..Default::default()
};
if let Some(target) = object.get("target").and_then(|value| value.as_object()) {
builder.target_kind = value_string(target, &["kind"])
.map(|kind| parse_target_kind(&kind))
.transpose()?;
builder.disk = value_string(target, &["disk"]);
}
if let Some(network) = object.get("network").and_then(|value| value.as_object()) {
if let Some(require_wired) = value_bool(network, &["requireWired", "require_wired"]) {
builder.network.require_wired = require_wired;
}
if let Some(wifi_allowed) = value_bool(network, &["wifiAllowed", "wifi_allowed"]) {
builder.network.wifi_allowed = wifi_allowed;
}
}
if let Some(safety) = object.get("safety").and_then(|value| value.as_object()) {
if let Some(allow) = value_bool(
safety,
&["allowDestructiveFlash", "allow_destructive_flash"],
) {
builder.safety.allow_destructive_flash = allow;
}
if let Some(allow) = value_bool(
safety,
&[
"allowInternalDiskSelection",
"allow_internal_disk_selection",
],
) {
builder.safety.allow_internal_disk_selection = allow;
}
if let Some(require) = value_bool(
safety,
&[
"requireOperatorConfirmation",
"require_operator_confirmation",
],
) {
builder.safety.require_operator_confirmation = require;
}
if let Some(require) = value_bool(
safety,
&["requiresTargetAttestation", "requires_target_attestation"],
) {
builder.safety.requires_target_attestation = require;
}
builder.safety.target_attestation =
value_string(safety, &["targetAttestation", "target_attestation"])
.map(|value| parse_target_attestation(&value))
.transpose()?;
builder.safety.allowed_targets =
value_string_array(safety, &["allowedTargets", "allowed_targets"])
.into_iter()
.map(|value| parse_target_attestation(&value))
.collect::<Result<Vec<_>>>()?;
builder.safety.forbidden_targets =
value_string_array(safety, &["forbiddenTargets", "forbidden_targets"])
.into_iter()
.map(|value| parse_target_attestation(&value))
.collect::<Result<Vec<_>>>()?;
}
if let Some(defaults) = object
.get("polymerizeDefaults")
.or_else(|| object.get("polymerize_defaults"))
.and_then(|value| value.as_object())
{
builder.polymerize_defaults = Some(PolymerizeDefaults {
username: value_string(defaults, &["username"]).unwrap_or_default(),
timezone: value_string(defaults, &["timezone"]).unwrap_or_default(),
install_mode: value_string(defaults, &["installMode", "install_mode"]),
ssh_authorized_keys: value_string_array(
defaults,
&["sshAuthorizedKeys", "ssh_authorized_keys"],
),
});
}
if let Some(plan) = object.get("plan").and_then(|value| value.as_object()) {
if let Some(mode) = value_string(plan, &["mode"]) {
if mode == "image-build" {
builder.operation = Some(ForgeOperation::Image);
builder.target_kind.get_or_insert(TargetKind::Image);
} else if builder.operation.is_none() {
builder.operation = Some(parse_operation(&mode)?);
}
}
}
builder.finish()
}
fn value_string(
object: &serde_json::Map<String, serde_json::Value>,
keys: &[&str],
) -> Option<String> {
keys.iter()
.find_map(|key| object.get(*key))
.and_then(|value| value.as_str())
.map(ToString::to_string)
}
fn value_bool(object: &serde_json::Map<String, serde_json::Value>, keys: &[&str]) -> Option<bool> {
keys.iter()
.find_map(|key| object.get(*key))
.and_then(|value| value.as_bool())
}
fn value_u16(
object: &serde_json::Map<String, serde_json::Value>,
keys: &[&str],
) -> Result<Option<u16>> {
let Some(value) = keys.iter().find_map(|key| object.get(*key)) else {
return Ok(None);
};
let Some(value) = value.as_u64() else {
bail!("expected integer for {}", keys[0]);
};
Ok(Some(
u16::try_from(value).context("schema version does not fit in u16")?,
))
}
fn value_string_array(
object: &serde_json::Map<String, serde_json::Value>,
keys: &[&str],
) -> Vec<String> {
keys.iter()
.find_map(|key| object.get(*key))
.and_then(|value| value.as_array())
.map(|values| {
values
.iter()
.filter_map(|value| value.as_str())
.map(ToString::to_string)
.collect()
})
.unwrap_or_default()
}
fn default_target_kind(operation: ForgeOperation) -> TargetKind {
match operation {
ForgeOperation::Bundle => TargetKind::Bundle,
ForgeOperation::UsbInstall => TargetKind::Usb,
ForgeOperation::Image => TargetKind::Image,
ForgeOperation::Netboot => TargetKind::Netboot,
ForgeOperation::RemotePolymerize => TargetKind::Remote,
}
}
fn parse_operation(value: &str) -> Result<ForgeOperation> {
match value {
"bundle" => Ok(ForgeOperation::Bundle),
"usb-install" => Ok(ForgeOperation::UsbInstall),
"image" | "image-build" => Ok(ForgeOperation::Image),
"netboot" => Ok(ForgeOperation::Netboot),
"remote-polymerize" => Ok(ForgeOperation::RemotePolymerize),
other => bail!("unsupported forge operation {other}"),
}
}
fn parse_arch(value: &str) -> Result<ForgeArch> {
match value {
"x86_64" | "x86" | "amd64" => Ok(ForgeArch::X86_64),
"aarch64" | "arm64" | "arm" => Ok(ForgeArch::Aarch64),
other => bail!("unsupported forge arch {other}"),
}
}
fn parse_target_attestation(value: &str) -> Result<TargetAttestation> {
match value {
"external-usb-ssd" | "external_usb_ssd" => Ok(TargetAttestation::ExternalUsbSsd),
"external-thunderbolt-ssd" | "external_thunderbolt_ssd" => {
Ok(TargetAttestation::ExternalThunderboltSsd)
}
"internal-apple-nvme" | "internal_apple_nvme" => Ok(TargetAttestation::InternalAppleNvme),
"internal-apple-storage" | "internal_apple_storage" => {
Ok(TargetAttestation::InternalAppleStorage)
}
other => anyhow::bail!("unsupported target attestation {other:?}"),
}
}
fn parse_target_kind(value: &str) -> Result<TargetKind> {
match value {
"bundle" => Ok(TargetKind::Bundle),
"usb" => Ok(TargetKind::Usb),
"image" | "image-build" => Ok(TargetKind::Image),
"netboot" => Ok(TargetKind::Netboot),
"remote" | "remote-polymerize" => Ok(TargetKind::Remote),
other => bail!("unsupported forge target kind {other}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plans_bundle_request_without_destructive_actions() -> Result<()> {
let request = ForgeRequest::new(
ForgeOperation::Bundle,
"seed",
ForgeArch::X86_64,
ForgeTarget::bundle(),
)
.profile("/Users/wilson/workspace/pig/nex-seed-substrate")
.output_dir("/tmp/nex-forge/seed");
let plan = plan_request(&request)?;
assert!(!plan.is_blocked());
assert_eq!(plan.output_dir, PathBuf::from("/tmp/nex-forge/seed"));
assert_eq!(
plan.iso.as_ref().map(|iso| iso.filename.as_str()),
Some("nixos-minimal-x86_64.iso")
);
assert!(plan.destructive_actions.is_empty());
Ok(())
}
#[test]
fn blocks_destructive_usb_flash_until_allowed() -> Result<()> {
let request = ForgeRequest::new(
ForgeOperation::UsbInstall,
"seed",
ForgeArch::X86_64,
ForgeTarget::usb(Some("/dev/disk9")),
);
let plan = plan_request(&request)?;
assert!(plan.is_blocked());
assert_eq!(plan.destructive_actions.len(), 1);
assert!(plan
.blockers
.iter()
.any(|diag| diag.code == "DESTRUCTIVE_FLASH_NOT_ALLOWED"));
Ok(())
}
#[test]
fn allows_destructive_usb_flash_when_policy_allows_it() -> Result<()> {
let mut request = ForgeRequest::new(
ForgeOperation::UsbInstall,
"seed",
ForgeArch::X86_64,
ForgeTarget::usb(Some("/dev/disk9")),
);
request.safety.allow_destructive_flash = true;
let plan = plan_request(&request)?;
assert!(!plan.is_blocked());
assert_eq!(
plan.destructive_actions[0].target.as_deref(),
Some("/dev/disk9")
);
Ok(())
}
#[test]
fn blocks_usb_install_without_target_disk() -> Result<()> {
let request = ForgeRequest::new(
ForgeOperation::UsbInstall,
"seed",
ForgeArch::X86_64,
ForgeTarget::usb(None::<String>),
);
let plan = plan_request(&request)?;
assert!(plan.is_blocked());
assert!(plan
.blockers
.iter()
.any(|diag| diag.code == "USB_TARGET_DISK_REQUIRED"));
Ok(())
}
#[test]
fn blocks_invalid_hostname() -> Result<()> {
let request = ForgeRequest::new(
ForgeOperation::Bundle,
"bad host",
ForgeArch::X86_64,
ForgeTarget::bundle(),
);
let plan = plan_request(&request)?;
assert!(plan.is_blocked());
assert!(plan
.blockers
.iter()
.any(|diag| diag.code == "INVALID_HOSTNAME"));
Ok(())
}
#[test]
fn names_pkl_as_canonical_definition_format() {
assert_eq!(CANONICAL_DEFINITION_FORMAT, "pkl");
assert_eq!(CANONICAL_REQUEST_EXTENSION, "pkl");
assert_eq!(CANONICAL_MANIFEST_EXTENSION, "pkl");
}
#[test]
fn serializes_request_as_json_transport_not_canonical_definition() -> Result<()> {
let request = ForgeRequest::new(
ForgeOperation::UsbInstall,
"seed",
ForgeArch::X86_64,
ForgeTarget::usb(Some("/dev/disk9")),
)
.profile("/Users/wilson/workspace/pig/nex-seed-substrate");
let encoded = serde_json::to_string(&request)?;
let decoded: ForgeRequest = serde_json::from_str(&encoded)?;
assert_eq!(decoded.operation, ForgeOperation::UsbInstall);
assert_eq!(decoded.target.kind, TargetKind::Usb);
assert_eq!(decoded.profile.as_deref(), request.profile.as_deref());
Ok(())
}
#[test]
fn normalizes_evaluated_pkl_usb_request() -> Result<()> {
let request = request_from_evaluated_pkl(serde_json::json!({
"schemaVersion": 1,
"operation": "usb-install",
"profile": "/Users/wilson/workspace/pig/nex-seed-substrate",
"hostname": "seed",
"arch": "x86_64",
"outputDir": "/tmp/nex-forge/seed",
"target": {
"kind": "usb",
"disk": "/dev/disk9"
},
"network": {
"requireWired": true,
"wifiAllowed": false
},
"safety": {
"allowDestructiveFlash": true,
"requireOperatorConfirmation": true
},
"polymerizeDefaults": {
"username": "wilson",
"timezone": "America/New_York",
"installMode": "desktop",
"sshAuthorizedKeys": [
"ssh-ed25519 AAAAC3Nza first",
"ssh-ed25519 AAAAC3Nza second"
]
}
}))?;
assert_eq!(request.operation, ForgeOperation::UsbInstall);
assert_eq!(request.target.kind, TargetKind::Usb);
assert_eq!(request.target.disk.as_deref(), Some("/dev/disk9"));
assert_eq!(request.hostname, "seed");
assert_eq!(request.arch, ForgeArch::X86_64);
assert_eq!(
request.profile.as_deref(),
Some("/Users/wilson/workspace/pig/nex-seed-substrate")
);
assert_eq!(
request.output_dir,
Some(PathBuf::from("/tmp/nex-forge/seed"))
);
assert!(request.network.require_wired);
assert!(!request.network.wifi_allowed);
assert!(request.safety.allow_destructive_flash);
let defaults = request
.polymerize_defaults
.as_ref()
.expect("polymerize defaults");
assert_eq!(defaults.username, "wilson");
assert_eq!(defaults.timezone, "America/New_York");
assert_eq!(defaults.install_mode.as_deref(), Some("desktop"));
assert_eq!(
defaults.ssh_authorized_keys,
vec![
"ssh-ed25519 AAAAC3Nza first".to_string(),
"ssh-ed25519 AAAAC3Nza second".to_string(),
]
);
Ok(())
}
#[test]
fn normalizes_armory_style_template_as_image_plan() -> Result<()> {
let request = request_from_evaluated_pkl(serde_json::json!({
"name": "minimal-workstation",
"description": "Build a non-destructive workstation image plan for operator review.",
"profileClass": "desktop",
"visibility": "public",
"plan": {
"mode": "image-build",
"target": "operator-selected",
"requiresNetwork": true
}
}))?;
assert_eq!(request.operation, ForgeOperation::Image);
assert_eq!(request.target.kind, TargetKind::Image);
assert_eq!(request.hostname, "nixos");
assert_eq!(request.arch, ForgeArch::X86_64);
Ok(())
}
#[test]
fn check_report_validates_armory_metadata_agreement() -> Result<()> {
let evaluated = serde_json::json!({
"name": "minimal-workstation",
"profileClass": "desktop",
"visibility": "public",
"plan": {
"mode": "image-build",
"target": "operator-selected",
"requiresNetwork": true
}
});
let request = request_from_evaluated_pkl(evaluated.clone())?;
let metadata = ForgeTemplateMetadata {
id: Some("minimal-workstation".to_string()),
version: Some("1.0.0".to_string()),
canonical_format: Some("pkl".to_string()),
visibility: Some("public".to_string()),
profile_class: Some("desktop".to_string()),
destructive_capabilities: Some(vec!["image-build".to_string()]),
network_requirements: Some(vec!["package-download".to_string()]),
..ForgeTemplateMetadata::default()
};
let report = build_check_report(&evaluated, &request, Some(&metadata));
assert!(report.valid);
assert_eq!(report.exit_code(), 0);
assert_eq!(report.template.id, "minimal-workstation");
assert_eq!(report.capabilities.destructive, vec!["image-build"]);
assert_eq!(report.capabilities.network, vec!["package-download"]);
assert_eq!(report.inputs.len(), 1);
assert_eq!(report.outputs[0].kind, "image-plan");
Ok(())
}
#[test]
fn check_report_rejects_public_raw_disk_template() -> Result<()> {
let evaluated = serde_json::json!({
"name": "unsafe-usb",
"visibility": "public",
"operation": "usb-install",
"hostname": "seed",
"target": {
"kind": "usb",
"disk": "/dev/sda"
}
});
let request = request_from_evaluated_pkl(evaluated.clone())?;
let report = build_check_report(&evaluated, &request, None);
assert!(!report.valid);
assert_eq!(report.exit_code(), 3);
assert!(report
.errors
.iter()
.any(|error| error.code == "UNSAFE_RAW_DISK_TARGET"));
assert!(report
.errors
.iter()
.any(|error| error.code == "PLAN_ONLY_EXECUTE_CAPABLE"));
Ok(())
}
#[test]
fn source_safety_catches_hidden_public_template_risks() {
let mut errors = Vec::new();
validate_public_template_source_safety(
r#"
local join_token = "secret"
import("file:///private/site.pkl")
local clusterInit = true
local postInstall = "sh ./run"
local disk = "/dev/nvme0n1"
"#,
&mut errors,
);
for code in [
"UNSAFE_JOIN_TOKEN",
"UNSAFE_PRIVATE_IMPORT",
"UNSAFE_CLUSTER_INIT",
"UNSAFE_EXECUTION_HOOK",
"UNSAFE_RAW_DISK_TARGET",
] {
assert!(errors.iter().any(|error| error.code == code), "{code}");
}
}
#[test]
fn evaluates_pkl_with_real_evaluator_when_available() -> Result<()> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("request.pkl");
std::fs::write(
&path,
r#"
local computedHostname = "seed"
schemaVersion = 1
operation = "usb-install"
hostname = computedHostname
arch = "x86_64"
target {
kind = "usb"
disk = "/dev/disk9"
}
"#,
)?;
let request = match evaluate_pkl_request(&path) {
Ok(request) => request,
Err(error) if error.to_string().contains("Pkl evaluator unavailable") => return Ok(()),
Err(error) => return Err(error),
};
assert_eq!(request.hostname, "seed");
assert_eq!(request.operation, ForgeOperation::UsbInstall);
assert_eq!(request.target.disk.as_deref(), Some("/dev/disk9"));
Ok(())
}
}
#[cfg(test)]
mod target_attestation_tests {
use super::*;
#[test]
fn blocks_destructive_flash_when_target_attestation_required_but_missing() -> Result<()> {
let mut request = ForgeRequest::new(
ForgeOperation::UsbInstall,
"seed",
ForgeArch::X86_64,
ForgeTarget::usb(Some("TARGET")),
);
request.safety.allow_destructive_flash = true;
request.safety.requires_target_attestation = true;
let plan = plan_request(&request)?;
assert!(plan.is_blocked());
assert!(plan
.blockers
.iter()
.any(|diag| diag.code == "TARGET_ATTESTATION_REQUIRED"));
Ok(())
}
#[test]
fn allows_destructive_flash_with_allowed_external_attestation() -> Result<()> {
let mut request = ForgeRequest::new(
ForgeOperation::UsbInstall,
"seed",
ForgeArch::X86_64,
ForgeTarget::usb(Some("TARGET")),
);
request.safety.allow_destructive_flash = true;
request.safety.requires_target_attestation = true;
request.safety.target_attestation = Some(TargetAttestation::ExternalUsbSsd);
request.safety.allowed_targets = vec![
TargetAttestation::ExternalUsbSsd,
TargetAttestation::ExternalThunderboltSsd,
];
request.safety.forbidden_targets = vec![TargetAttestation::InternalAppleNvme];
let plan = plan_request(&request)?;
assert!(!plan.is_blocked());
Ok(())
}
#[test]
fn blocks_internal_apple_nvme_attestation_by_default() -> Result<()> {
let mut request = ForgeRequest::new(
ForgeOperation::UsbInstall,
"seed",
ForgeArch::X86_64,
ForgeTarget::usb(Some("TARGET")),
);
request.safety.allow_destructive_flash = true;
request.safety.requires_target_attestation = true;
request.safety.target_attestation = Some(TargetAttestation::InternalAppleNvme);
request.safety.allowed_targets = vec![
TargetAttestation::ExternalUsbSsd,
TargetAttestation::ExternalThunderboltSsd,
];
request.safety.forbidden_targets = vec![TargetAttestation::InternalAppleNvme];
let plan = plan_request(&request)?;
assert!(plan.is_blocked());
assert!(plan
.blockers
.iter()
.any(|diag| diag.code == "TARGET_ATTESTATION_NOT_ALLOWED"));
assert!(plan
.blockers
.iter()
.any(|diag| diag.code == "TARGET_ATTESTATION_FORBIDDEN"));
assert!(plan
.blockers
.iter()
.any(|diag| diag.code == "INTERNAL_APPLE_STORAGE_FORBIDDEN"));
Ok(())
}
#[test]
fn blocks_internal_apple_storage_attestation_by_default() -> Result<()> {
let mut request = ForgeRequest::new(
ForgeOperation::UsbInstall,
"seed",
ForgeArch::X86_64,
ForgeTarget::usb(Some("TARGET")),
);
request.safety.allow_destructive_flash = true;
request.safety.requires_target_attestation = true;
request.safety.target_attestation = Some(TargetAttestation::InternalAppleStorage);
request.safety.allowed_targets = vec![
TargetAttestation::ExternalUsbSsd,
TargetAttestation::ExternalThunderboltSsd,
];
request.safety.forbidden_targets = vec![TargetAttestation::InternalAppleStorage];
let plan = plan_request(&request)?;
assert!(plan.is_blocked());
assert!(plan
.blockers
.iter()
.any(|diag| diag.code == "TARGET_ATTESTATION_NOT_ALLOWED"));
assert!(plan
.blockers
.iter()
.any(|diag| diag.code == "TARGET_ATTESTATION_FORBIDDEN"));
assert!(plan
.blockers
.iter()
.any(|diag| diag.code == "INTERNAL_APPLE_STORAGE_FORBIDDEN"));
Ok(())
}
#[test]
fn parses_target_attestation_from_json_transport() -> Result<()> {
let value = serde_json::json!({
"operation": "usb-install",
"hostname": "seed",
"arch": "x86_64",
"target": { "kind": "usb", "disk": "TARGET" },
"safety": {
"allow_destructive_flash": true,
"requires_target_attestation": true,
"target_attestation": "external-usb-ssd",
"allowed_targets": ["external-usb-ssd", "external-thunderbolt-ssd"],
"forbidden_targets": ["internal-apple-storage"]
}
});
let request = request_from_evaluated_pkl(value)?;
assert_eq!(
request.safety.target_attestation,
Some(TargetAttestation::ExternalUsbSsd)
);
assert_eq!(
request.safety.allowed_targets,
vec![
TargetAttestation::ExternalUsbSsd,
TargetAttestation::ExternalThunderboltSsd
]
);
assert_eq!(
request.safety.forbidden_targets,
vec![TargetAttestation::InternalAppleStorage]
);
Ok(())
}
}