feat(tf-config): implement template loading with format validation (Story 0-4)#15
Merged
EdouardZemb merged 39 commits intomainfrom Feb 6, 2026
Merged
Conversation
Prepare comprehensive dev context for story 0.4 (charger des templates CR/PPT/anomalies) and update sprint status to ready-for-dev. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add TemplateLoader API supporting CR (.md), PPT (.pptx) and Anomaly (.md) templates with extension and format validation, actionable error hints, and safe Debug impl that never exposes raw content. 28 unit tests covering all acceptance criteria for story 0-4. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mark story 0-4 as in-progress with all tasks completed, add code review follow-ups, and record automation summary (Playwright deferred — no external interface targets yet). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve all 10 review findings from adversarial code review: - Fix TOCTOU race: remove path.exists() pre-check, handle NotFound from fs::read() directly - Make validate_format public and export from lib.rs (spec alignment) - Make TemplateKind::all() public for external consumers - Add Serialize/Deserialize derives on TemplateKind - Document load_all() fail-fast semantics - Add no_run doc-tests for TemplateLoader::new() and load_template() - Add module-level Usage section with code snippet - Document MIN_PPTX_SIZE rationale
- Update story status from in-progress to review - Check off all 10 code review findings as resolved - Correct File List line count to 805 lines - Update sprint-status.yaml to reflect review state
- Add 6 new findings from adversarial code review round 2 (3 MEDIUM, 3 LOW — no blocking issues) - Revert story status to in-progress pending follow-up items - Update sprint-status.yaml accordingly
- Change TemplateLoader to borrow &TemplatesConfig instead of cloning - Refactor load_all() with single resolve_path() to eliminate duplicated is_configured() + get_configured_path() resolution - Fix content_as_str() hint for PPTX: "use content() for raw bytes" - Remove redundant size_bytes field, compute on-the-fly from content.len() - Make TemplateKind::expected_extension() public - Add MIN_PPTX_SIZE boundary tests (at min-1 and at min)
- Check off all 6 round 2 review findings as resolved - Update story status to review - Update sprint-status.yaml accordingly - Correct file list to 830 lines and 30 unit tests
- Add 5 new findings from adversarial code review round 3 (2 MEDIUM, 3 LOW — no blocking issues) - Revert story status to in-progress pending follow-up items - Update sprint-status.yaml accordingly
- Make validate_extension() case-insensitive with eq_ignore_ascii_case() - Avoid heap allocation on happy path: compare raw extension without dot - Add #[derive(Debug)] on TemplateLoader for consistency - Use HashMap::with_capacity() in load_all() since max kinds is known - Add context-aware ReadError hint when path is a directory - Add 4 new tests: 3 case-insensitive extension + 1 directory edge case
- Check off all 5 round 3 review findings as resolved - Update story status to review - Update sprint-status.yaml accordingly - Correct file list to 923 lines and 34 unit tests
- Add 5 new findings from adversarial code review round 4 (3 MEDIUM, 2 LOW — no blocking issues) - Revert story status to in-progress pending follow-up items - Update sprint-status.yaml accordingly
- Move tempfile = "3.10" to workspace dependencies for consistency - Add serde_json as workspace dev-dependency for serde roundtrip tests - Update Cargo.lock accordingly
- Add #[serde(rename_all = "lowercase")] on TemplateKind to align Serialize output with Display (cr/ppt/anomaly) - Add max file size guard via fs::metadata() pre-check before fs::read() (10MB for .md, 100MB for .pptx) to prevent unbounded allocation - Change validate_format public API from path: &str to path: &Path to follow Rust conventions - Change MIN_PPTX_SIZE from u64 to usize, eliminating all casts - Add 3 new tests: 2 oversized file rejection + 1 serde roundtrip
- Check off all 5 round 4 review findings as resolved - Update story status to review - Update sprint-status.yaml accordingly - Correct file list to 1018 lines and 37 unit tests
- Add 5 new findings from adversarial code review round 5 (2 MEDIUM, 3 LOW — no blocking issues) - Revert story status to in-progress pending follow-up items - Update sprint-status.yaml accordingly
- Add post-read content.len() size check to guard against TOCTOU where file grows between fs::metadata() and fs::read() - Reject whitespace-only markdown templates with trim().is_empty() - Clarify validate_format docstring: path used for error context only - Add detailed rationale docs for MAX_MD_SIZE and MAX_PPTX_SIZE - Add #[cfg(test)] LoadedTemplate::new_for_test() constructor - Add 3 new tests: 2 whitespace-only rejection + 1 new_for_test
- Check off all 5 round 5 review findings as resolved - Update story status to review - Update sprint-status.yaml accordingly - Correct file list to 1121 lines and 40 unit tests
- Add 9 new findings from adversarial code review round 6 (4 MEDIUM, 5 LOW — no blocking issues) - Revert story status to in-progress pending follow-up items - Update sprint-status.yaml accordingly
- Add [features] section with test-utils flag for downstream test consumers to access LoadedTemplate::new_for_test()
- Change TemplateError kind fields from String to TemplateKind (type-safe) - Add Clone derive on TemplateError - Add BinaryContent variant for content_as_str() on binary templates - Convert validate_extension from &self method to free function - Pass TemplateKind to validate_pptx instead of hardcoding "ppt" - Extract oversized_error() helper to deduplicate size-check errors - Extract path.extension() to single binding in validate_extension - Change new_for_test from #[cfg(test)] to #[cfg(any(test, feature))] - Add 4 new tests: Clone, type-safe kind, BinaryContent, free fn
- Check off all 9 round 6 review findings as resolved - Update story status to review - Update sprint-status.yaml accordingly - Correct file list to 1185 lines and 44 unit tests
- Add 6 new findings from adversarial code review round 7 (3 MEDIUM, 3 LOW — no blocking issues) - Revert story status to in-progress pending follow-up items - Update sprint-status.yaml accordingly
- Add PartialEq derive on TemplateError for test ergonomics - Display "(none)" instead of empty string for no-extension files - Remove redundant path from oversized_error hint - Strengthen test_load_all assertion to verify FileNotFound variant - Add test for file without extension (README edge case)
- Check off all 6 round 7 review findings as resolved - Update story status to review - Update sprint-status.yaml accordingly - Correct file list to 1215 lines and 46 unit tests - Add Cargo.lock to file list
- Add 6 new findings from adversarial code review round 8 (3 MEDIUM, 3 LOW — no blocking issues) - Revert story status to in-progress pending follow-up items - Update sprint-status.yaml accordingly
- Rename validate_format to validate_content for clarity (validates bytes, not file-level format) — update lib.rs re-export - Fix content_as_str() to return InvalidFormat for non-UTF-8 markdown, reserve BinaryContent for PPTX only - Document relative path limitation in load_from_path() and load_template() docstrings - Document load_all() iteration order (Cr, Ppt, Anomaly) in docstring - Add test for non-UTF-8 markdown content_as_str() behavior
Replace has_valid_extension() with has_valid_template_extension() that delegates to TemplateKind::expected_extension() — single source of truth shared with template::validate_extension.
- Check off all 6 round 8 review findings as resolved - Update story status to review - Update sprint-status.yaml accordingly - Correct file list to 1251 lines and 47 unit tests - Add config.rs to file list
- Add 4 new findings from adversarial code review round 9 (2 HIGH, 2 MEDIUM — first round with HIGH findings) - Revert story status to in-progress pending follow-up items - Update sprint-status.yaml accordingly
Make redact_url_sensitive_params available crate-internally for reuse by template error path sanitization.
- Add OOXML [Content_Types].xml entry check to validate_pptx — enforces structural validity beyond ZIP magic + min size - Sanitize error paths via redact_url_sensitive_params to prevent credential leakage in error messages - Add test for missing Content_Types.xml rejection - Add test for sensitive URL redaction in error paths - Update create_valid_pptx_bytes helper with OOXML marker
- Check off all 4 round 9 review findings (2 HIGH, 2 MEDIUM) - Align subtasks 3.2/3.3/3.5 contract from kind: String to TemplateKind - Add traceability baseline SHA in Dev Agent Record - Update story status to review - Update sprint-status.yaml accordingly
- Add 5 new findings from adversarial code review round 10 (2 HIGH, 2 MEDIUM, 1 LOW) - Revert story status to in-progress pending follow-up items - Update sprint-status.yaml accordingly
- Replace fs::read() with bounded streaming read (read_bounded) to enforce size limits without unbounded memory allocation - Implement proper ZIP central directory parsing for PPTX validation: EOCD signature, central directory entries, file name extraction - Add generic path-segment redaction for non-URL secret paths (e.g. /tmp/token/sk-xxx/template.md) - Update validate_content docstring to reflect OOXML marker check - Replace heuristic pptx test helper with create_single_entry_zip() that generates structurally valid ZIP archives - Add tests: invalid ZIP structure, non-URL path redaction, read_bounded capping, valid archive acceptance
- Check off all 5 round 10 review findings (2 HIGH, 2 MEDIUM, 1 LOW) - Align subtask 2.7 from validate_format to validate_content signature - Update sprint-status.yaml accordingly
…t-templates # Conflicts: # _bmad-output/automation-summary.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TemplateLoaderAPI for loading and validating CR (.md), PPT (.pptx), and Anomaly (.md) templates from configured pathsTemplateErrorenum with type-safeTemplateKindfields,BinaryContentvariant,Clone/PartialEqderives[Content_Types].xmlentry enforcement, bounded streaming readredact_url_sensitive_params+ generic path-segment redactorChanges
crates/tf-config/src/template.rs(NEW, 1588 lines) —TemplateLoader<'a>,TemplateKind,LoadedTemplate,TemplateError,validate_content,validate_extension, ZIP parsing, bounded read, 51 unit testscrates/tf-config/src/config.rs— Deduplicated extension validation viaTemplateKind::expected_extension(), exposedredact_url_sensitive_paramsaspub(crate)crates/tf-config/src/lib.rs— Public re-exports for template typescrates/tf-config/Cargo.toml—test-utilsfeature flag, workspace dev-deps (serde_json,tempfile)Cargo.toml— Addedtempfileto workspace dependenciesTesting
Checklist
Related Issues
Closes Story 0-4: Charger des templates (CR/PPT/anomalies)