#!/usr/bin/env python3 """Strict, dependency-free rendering for deterministic harness templates.""" from __future__ import annotations import hashlib from pathlib import Path import re from typing import Mapping PLACEHOLDER_RE = re.compile(r"\{\{([a-z][a-z0-9_]*)\}\}") REGION_START = "" REGION_END = "" GENERATED_START = "" GENERATED_END = "" class TemplateRenderError(ValueError): def __init__(self, code: str, message: str) -> None: self.code = code super().__init__(message) def extract_region(text: str, start: str, end: str) -> str: """Return one ordered marker region, excluding the marker lines.""" if text.count(start) != 1 or text.count(end) != 1: raise TemplateRenderError("INVALID_TEMPLATE_REGION", f"expected one marker pair: {start} / {end}") start_at = text.index(start) + len(start) end_at = text.index(end, start_at) if start_at >= end_at: raise TemplateRenderError("INVALID_TEMPLATE_REGION", "template region markers are reversed") return text[start_at:end_at].strip("\n") + "\n" def generated_region(text: str) -> str: """Return the runtime-owned branch contract including its marker lines.""" if text.count(GENERATED_START) != 1 or text.count(GENERATED_END) != 1: raise TemplateRenderError("GENERATED_REGION_DRIFT", "generated branch-contract markers must occur exactly once") start_at = text.index(GENERATED_START) end_at = text.index(GENERATED_END, start_at) + len(GENERATED_END) if start_at >= end_at: raise TemplateRenderError("GENERATED_REGION_DRIFT", "generated branch-contract markers are reversed") return text[start_at:end_at] def generated_sha256(text: str) -> str: return hashlib.sha256(generated_region(text).encode("utf-8")).hexdigest() def render(text: str, values: Mapping[str, str], *, allowed: set[str]) -> str: """Render only allowlisted placeholders and reject missing or extra input.""" discovered = set(PLACEHOLDER_RE.findall(text)) unknown = sorted(discovered - allowed) if unknown: raise TemplateRenderError("UNKNOWN_PLACEHOLDER", f"template contains non-allowlisted placeholders: {unknown}") missing = sorted(discovered - set(values)) if missing: raise TemplateRenderError("UNRESOLVED_PLACEHOLDER", f"placeholder values are missing: {missing}") extra = sorted(set(values) - allowed) if extra: raise TemplateRenderError("UNEXPECTED_TEMPLATE_VALUE", f"values were supplied for unknown placeholders: {extra}") rendered = PLACEHOLDER_RE.sub(lambda match: values[match.group(1)], text) unresolved = sorted(set(PLACEHOLDER_RE.findall(rendered))) if unresolved: raise TemplateRenderError("UNRESOLVED_PLACEHOLDER", f"unresolved placeholders remain: {unresolved}") return rendered def render_branch_note(template_path: Path, values: Mapping[str, str]) -> str: """Render the deterministic branch-from-project region of the branch template.""" template = template_path.read_text(encoding="utf-8") body = extract_region(template, REGION_START, REGION_END) allowed = { "branch_slug", "branch_id", "project", "project_parent_link", "work_item", "inherits_yaml", "depends_on_yaml", "created", "contract_packet_sha256", "project_revision", "completion", "inherited_rows", "dependency_display", } return render(body, values, allowed=allowed)