Files
glm-5.3-flash 14d8a49611 test(fixtures): wave-1 engine fixture doubles (ssr-shaped and code-shaped)
- ssr double: page template composing header/footer partials under the
  HTML escaper, typed context built by a builder fn per ADR-004; golden
  pins for composition, escaping of hostile interpolation, the trusted
  {{& }} slot, and render-by-partial-name rejection
- code double: struct-writer template under the no-op escaper, typed
  context built from a schema document with kind mapping and
  source-escaped literals in the builder; golden pins for document-order
  arrays and the active preserve_order feature
- fixture assets are include_str! files under tests/fixtures/; fixture
  modules are per-test-binary so wave-2 proofs can grow them
- task file updated to completed with Notes and Summary

Verification: cargo test green (19 lib + 8 fixture tests), clippy
-D warnings clean, cargo fmt --check clean
2026-11-06 20:05:42 +00:00

7.1 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
w1-engine-fixtures Wave 1: engine fixture doubles (ssr-shaped and code-shaped sets) completed
w1-render-entry
narrow low component implementation

Description

Land the minimal in-crate fixture doubles that every wave-2 proof runs through (the alkssh wave-1 doubles move: the deep harness lands wave 2; the doubles land now). They are the first full-round-trip exercise of the shipped core: typed context in, rendered text out, through the public API only.

  • ssr-shaped fixture set: a small page template composing fragment partials (header/footer shape), registered through the public API, HTML escaper active, typed context (derived Serialize) built by a Rust builder function (the ADR-004 shape: kind mapping and structure decisions live in builder code, templates stay dumb conditionals over context flags). Golden render tests pin the output (AGENTS.md convention 7 discipline starts here: changing a fixture template changes the pinned expectations, deliberately).
  • code-shaped fixture set: a small set with the no-op escaper and a typed context whose arrays are document-ordered: serde_json's preserve_order is load-bearing (convention 5); the fixture proves insertion order drives rendered order (an ordered list context, not a map, exercising the array path handlebars iterates).
  • Fixture placement per the wave-1 conventions: fixtures live in the repo as pinned code assets (include_str! files or inline literals), versioned, never throwaway strings; test binaries own their fixture modules so wave-2 proofs can grow them without single-writer collisions.
  • Establish the naming convention for fixture template kinds (convention 9 posture, consistent with whatever the registry task decided).

Acceptance Criteria

  • ssr-shaped set renders through the public API with HTML escaping; page composes registered fragment partials; golden tests pin output
  • code-shaped set renders with no-op escaping; document-order arrays render in insertion order (the preserve_order test)
  • both sets use typed contexts built by Rust builder functions per ADR-004
  • fixtures are embedded code assets, goldens committed and asserted
  • cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check pass

References

  • docs/architecture/core.md §Context conventions, §Template assets
  • docs/architecture/decisions/001-one-core-template-set-targets.md (the generality claim these fixtures will help prove)
  • docs/architecture/decisions/004-typed-contexts-builders-not-templates.md
  • docs/architecture/emit.md (postures B and A: the shapes these fixtures model)
  • docs/plans/implementation.md (wave 2 consumes these doubles)

Notes

Landed surface. Two integration test binaries, each owning its fixture module: tests/ssr_fixtures.rs and tests/code_fixtures.rs. Template and builder-input assets are include_str! files under tests/fixtures/ (ssr/page.hbs, ssr/header.hbs, ssr/footer.hbs, code/struct-writer.hbs, code/schema.json), versioned, never throwaway strings. The fixture modules themselves are per-binary (mod fixtures inside each test file), so wave-2 proof binaries can include_str! the same assets into their own modules and grow them without single-writer collisions on module code.

ssr-shaped set (posture-B shape). One template kind page plus two composition partials header and footer, registered through the public API (EscapePolicy::Html, strict mode forced by TemplateSet::new). The typed context (PageContext) is built by build_page per ADR-004: the SectionKind::{Prose, Note} enum maps to an is_callout flag in builder code, an Option<&str> banner becomes a show_banner flag plus text, and the template carries only dumb conditionals over flags. Goldens pin: page composing both partials; interpolated user data entity-escaped (including a hostile <script> nav label, and the substrate's &#x3D; escaping of =, now pinned as actual behavior); the {{& }} deliberately-trusted slot passing through verbatim; a no-banner context rendering without any banner element; render-by-partial-name rejected through the public API (the w1-render-entry rule exercised from a consumer-shaped test).

code-shaped set (posture-A/C shape). One template kind struct-writer under EscapePolicy::NoOp. The builder build_struct_writer consumes the schema.json document fixture and carries the kind mapping in Rust: schema kinds (u32, string, ...) map to writer call names, the snake_case document name maps to a PascalCase Rust type, and string defaults are builder-quoted into Rust literals (escaping at the source, not by the escaper). Goldens pin the full generated-artifact shape and that the source-escaped literal ("Say \"<b>&\" hi") passes through the no-op escaper verbatim with no &lt; anywhere.

Document order (convention 5). build_struct_writer walks the schema document's fields array in order and produces a Vec (an ordered list context, not a map), so {{#each}} iterates the array path in insertion order. Two tests pin it: field lines render in the document's order, and re-sorting the document's array re-orders the rendered lines the same way, proving insertion order (not sorting or hash order) drives the output. A separate test pins that the preserve_order feature is actually active in the dependency set (insertion order of a serde_json::Map survives serialization; disabling the feature would make the engine's contexts sort keys and fail this pin).

Naming convention for fixture template kinds (convention 9). Per the registry task's decision (naming enforced by tests, not registration): render targets are named by their template kind in lowercase kebab (page for the ssr set, struct-writer for the code set), one name per kind within a set; partials are named for their composition role (header, footer). Fixture module and file names follow the same kebab shapes. Reviewed like source, as the registry task pinned.

Deliberately deferred to wave 2 (per the plan text): fragments as direct render targets (wave-2 ssr proof), the DO-NOT-EDIT banner (an emit-layer obligation with no engine-owned set in v1; the fixture templates model artifact shape without implying banner coverage), and the deep determinism/error battery, which the conformance task runs.

Summary

Wave-1 engine fixture doubles landed: the ssr-shaped set (one page template composing header/footer partials, HTML escaper, typed context built by build_page per ADR-004, goldens pinning escaping, raw-output, composition, and the render-name rule) and the code-shaped set (one struct-writer template, no-op escaper, typed context built from a schema document with kind mapping and source-escaped literals in the builder, goldens pinning document-order arrays and the active preserve_order feature). Assets are include_str! files under tests/fixtures/ owned as embedded code; fixture modules are per-test-binary so wave-2 proofs can grow them. Verification: full suite green (19 lib + 8 fixture tests), clippy -D warnings clean, cargo fmt --check clean, no em dashes in prose.