# Copyright 2026 Guildhouse Dev # SPDX-License-Identifier: Apache-2.0 """Bastion template manifest schema. A template is a Git repository with a bastion.toml at the root. It contains policies, accords, harnesses, scripts, HBOM profiles, and optionally framework-specific automation (Ansible, Terraform, etc). The manifest declares: - What the template provides (contents) - What it depends on (dependencies) - What the deployer must customize (variables) - What Bastion version and connectors it requires (compatibility) """ from __future__ import annotations import tomllib from pathlib import Path from typing import Any, Optional from pydantic import BaseModel, Field class TemplateMetadata(BaseModel): """Template identification and categorization.""" name: str version: str description: str = "" authors: list[str] = [] license: str = "Apache-2.0" repository: Optional[str] = None vertical: Optional[str] = None sub_vertical: Optional[str] = None compliance_frameworks: list[str] = [] target_fleet_size: Optional[str] = None requires_vdi: bool = False requires_tpm: bool = False class TemplateCompatibility(BaseModel): """What Bastion version and connectors this template needs.""" bastion_min: str = "0.5.0" connectors_required: list[str] = [] connectors_optional: list[str] = [] class TemplateDependency(BaseModel): """A dependency on another template repository.""" git: str tag: Optional[str] = None branch: Optional[str] = None class TemplateVariable(BaseModel): """A variable the deployer must or may customize.""" type: str = "string" required: bool = False default: Optional[str] = None description: str = "" sensitive: bool = False class TemplateContents(BaseModel): """Directory mapping for template content.""" policies: str = "policies/" accords: str = "accords/" harnesses: str = "harnesses/" scripts: str = "scripts/" hbom_profiles: str = "hbom/" dashboards: str = "dashboards/" class TemplateManifest(BaseModel): """Root schema for bastion.toml.""" template: TemplateMetadata compatibility: TemplateCompatibility = TemplateCompatibility() dependencies: dict[str, TemplateDependency] = {} variables: dict[str, TemplateVariable] = {} contents: TemplateContents = TemplateContents() @classmethod def from_toml(cls, path: str | Path) -> TemplateManifest: """Parse a bastion.toml file.""" with open(path, "rb") as f: data = tomllib.load(f) return cls.model_validate(data) def validate_variables(self, provided: dict[str, Any]) -> list[str]: """Check that all required variables are provided. Returns list of error messages (empty = valid).""" errors = [] for name, var in self.variables.items(): if var.required and name not in provided and var.default is None: errors.append(f"Required variable '{name}' not provided") return errors