Skip to content

feat(tf-config): implement template loading with format validation (Story 0-4)#15

Merged
EdouardZemb merged 39 commits intomainfrom
feature/0-4-chargement-templates
Feb 6, 2026
Merged

feat(tf-config): implement template loading with format validation (Story 0-4)#15
EdouardZemb merged 39 commits intomainfrom
feature/0-4-chargement-templates

Conversation

@EdouardZemb
Copy link
Owner

Summary

  • Implement TemplateLoader API for loading and validating CR (.md), PPT (.pptx), and Anomaly (.md) templates from configured paths
  • Full TemplateError enum with type-safe TemplateKind fields, BinaryContent variant, Clone/PartialEq derives
  • PPTX validation: ZIP central directory parsing, OOXML [Content_Types].xml entry enforcement, bounded streaming read
  • Security: error path sanitization via redact_url_sensitive_params + generic path-segment redactor
  • 10 rounds of adversarial code review with 56 findings resolved (all checked off)

Changes

  • crates/tf-config/src/template.rs (NEW, 1588 lines) — TemplateLoader<'a>, TemplateKind, LoadedTemplate, TemplateError, validate_content, validate_extension, ZIP parsing, bounded read, 51 unit tests
  • crates/tf-config/src/config.rs — Deduplicated extension validation via TemplateKind::expected_extension(), exposed redact_url_sensitive_params as pub(crate)
  • crates/tf-config/src/lib.rs — Public re-exports for template types
  • crates/tf-config/Cargo.tomltest-utils feature flag, workspace dev-deps (serde_json, tempfile)
  • Cargo.toml — Added tempfile to workspace dependencies
  • Story & sprint artifacts — Story 0-4 with 10 review rounds documented, sprint status updated

Testing

  • 51 template unit tests (all passing)
  • 299 total tests across workspace (0 regressions)
  • 0 clippy warnings
  • Case-insensitive extensions, oversized files, whitespace-only MD, directory-as-path, no-extension, invalid ZIP structure, missing OOXML entry, sensitive path redaction all covered

Checklist

  • Code follows project style guidelines
  • Self-review completed (10 adversarial rounds)
  • Tests pass locally
  • No breaking changes
  • No secrets or credentials committed

Related Issues

Closes Story 0-4: Charger des templates (CR/PPT/anomalies)

EdouardZemb and others added 30 commits February 6, 2026 09:21
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
@EdouardZemb EdouardZemb merged commit 5db9664 into main Feb 6, 2026
@EdouardZemb EdouardZemb deleted the feature/0-4-chargement-templates branch February 6, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant