- 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
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 |
|
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'spreserve_orderis 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_ordertest) - 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 --checkpass
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 = 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 < 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.