{{title}}
"); + text.push_str("{{body}}
"); + text.push_str("diff --git a/tasks/implementation/w2-ssr-proof.md b/tasks/implementation/w2-ssr-proof.md
index 7788da9..6274c34 100644
--- a/tasks/implementation/w2-ssr-proof.md
+++ b/tasks/implementation/w2-ssr-proof.md
@@ -1,7 +1,7 @@
---
id: w2-ssr-proof
name: "Wave 2: the ssr-html proof (posture B, consumer-shaped fixture through the public API)"
-status: pending
+status: completed
depends_on: []
scope: narrow
risk: low
@@ -58,21 +58,21 @@ core invisibly (ADR-001 falsifiability discipline).
## Acceptance Criteria
-- [ ] the full fixture set registers from outside the crate through
+- [x] the full fixture set registers from outside the crate through
the public API only (caller-supplied text; ADR-008 equality
exercised at posture-B scale)
-- [ ] a page template composes fragment partials; at least one
+- [x] a page template composes fragment partials; at least one
fragment is a registered template rendered directly by name
-- [ ] HTML-escape goldens pin: interpolated hostile data escaped, a
+- [x] HTML-escape goldens pin: interpolated hostile data escaped, a
deliberate raw-output slot verbatim
-- [ ] a strict-mode render failure maps toward the error-page shape
+- [x] a strict-mode render failure maps toward the error-page shape
through the typed error, panic-free, using only public API
data
-- [ ] repeated renders produce byte-identical output
-- [ ] posture-B hygiene rows confirmed: no banner, no filesystem
-- [ ] `cargo test`, `cargo clippy --all-targets -- -D warnings`,
+- [x] repeated renders produce byte-identical output
+- [x] posture-B hygiene rows confirmed: no banner, no filesystem
+- [x] `cargo test`, `cargo clippy --all-targets -- -D warnings`,
`cargo fmt --check` pass
-- [ ] no core changes required; any gap found is booked for the gate
+- [x] no core changes required; any gap found is booked for the gate
in Notes per the stop-and-book rule
## References
@@ -91,8 +91,59 @@ core invisibly (ADR-001 falsifiability discipline).
## Notes
-> To be filled by implementation agent
+> Filled by implementation (2026-11-06). The shipped core served the
+> full set with **no core changes required**; the stop-and-book rule
+> found nothing to book. Details:
+
+- ADR-008 machinery equality exercised in three caller-supplied text
+ shapes at once: `include_str!` fixture files (page, header, nav,
+ footer, settings_page), a runtime-built `String` (the card fragment
+ template, assembled from `String::push_str` parts), and an inline
+ literal (the error page). All register through the identical
+ `register_template` / `register_partial` machinery; no second-class
+ path exists in the API to even attempt.
+- Composition and the render-name rule coexist cleanly: `card`
+ registers as its own template, is included in the page via
+ `{{> card }}` inside `{{#each}}` (posture B: fragments a consumer
+ serves directly are render targets), and renders directly by name;
+ the header partial's name is rejected by `render` as typed
+ `Error::Render`.
+- Error-page mapping: a strict-mode failure on `settings_page`
+ (missing `account.email`) returns `Error::Render`; the proof maps it
+ to a consumer-built fallback page from `err.to_string()` and
+ `err.source()` (the substrate detail, reachable via
+ `std::error::Error::source`). Substrate detail observed: template
+ name, line/column, and the missing path all survive into the mapped
+ page, entity-escaped. Panic-free by construction.
+- Determinism pin: repeated renders byte-identical, byte-identical
+ across registry rebuilds and fresh contexts, and the write sink
+ (`render_to_write`) agrees byte-for-byte with the string sink
+ (ADR-003; the substrate alkforge caching leans on).
+- Hygiene rows: the pinned page output is unadorned served HTML
+ (starts at the doctype, no DO-NOT-EDIT banner, no "Auto-generated"
+ marker); registration is string-only by construction, no filesystem
+ path exists. No emit-layer hygiene beyond what `render` returns.
+- Template whitespace note (observation, not a gap): the substrate
+ consumes the newline around lines whose only content is a block
+ expression (`{{#each cards}}{{> card}}{{/each}}`) while keeping
+ standalone partial lines' newlines. The goldens pin this behavior,
+ so it is now part of the pinned surface; consumers relying on exact
+ byte layouts have the golden as precedent.
+- Goldens follow convention 7 and are exact; the handlebars
+ html-escape posture (including `=` as `=` and `'` as `'`
+ inside interpolated attribute values) is exercised and pinned.
## Summary
-> To be filled on completion
+Implemented `tests/ssr_proof.rs` plus fixtures under
+`tests/fixtures/ssr_proof/` (page, header, nav, footer, settings_page
+templates) following the wave-1 per-binary fixture convention. Four
+tests cover: the full consumer fixture set rendered as an exact
+golden with escape and raw-slot rows (page), the card fragment
+registered as a template and rendered directly, the strict-failure to
+error-page mapping built from the typed error's public data, and
+determinism across repeats, rebuilds, contexts, and both sinks. All
+registration flows through the public API from the test binary;
+no core change was needed (stop-and-book: nothing to book).
+Verification: `cargo test` (31 passed), `cargo clippy --all-targets
+-- -D warnings`, and `cargo fmt --check` all pass.
diff --git a/tests/fixtures/ssr_proof/footer.hbs b/tests/fixtures/ssr_proof/footer.hbs
new file mode 100644
index 0000000..e872cdf
--- /dev/null
+++ b/tests/fixtures/ssr_proof/footer.hbs
@@ -0,0 +1 @@
+
diff --git a/tests/fixtures/ssr_proof/header.hbs b/tests/fixtures/ssr_proof/header.hbs
new file mode 100644
index 0000000..02637d7
--- /dev/null
+++ b/tests/fixtures/ssr_proof/header.hbs
@@ -0,0 +1 @@
+ {{tagline}}{{site_name}}
{{#if tagline}}
Signed in as {{account.email}}
{{message}}
\n\n"; + +#[derive(Serialize)] +struct NavLink { + href: String, + label: String, +} + +#[derive(Serialize)] +struct Card { + kind: String, + title: String, + body: String, +} + +#[derive(Serialize)] +struct PageContext { + site_name: String, + tagline: String, + nav_links: Vec{{body}}
"); + text.push_str("Your repositories
if (a < b) { alert("<b>hi</b>") }
A plain body.
if (a < b) { alert("<b>hi</b>") }
", + "render failed: Error rendering "settings_page" line 5, col 48: Failed to access variable in strict mode Some("account.email")
\n", + "\n", +); + +#[test] +fn page_golden_pins_composition_escaping_and_raw_slot_at_fuller_scale() { + let set = consumer_fixture_set(); + let context = context_with(true, "scheduled maintenance"); + + let rendered = set + .render("page", &context) + .expect("the full consumer fixture set should render through the public API"); + assert_eq!(rendered, GOLDEN_PAGE); + + assert!( + rendered.contains(r#"<script>alert('x')</script>"#), + "hostile interpolated data must be entity-escaped" + ); + assert!( + rendered.contains(r#""#) + .map_or(0, |at| at + r#"
"#.len()); + let detail_end = error_page[detail_begin..] + .find("
") + .expect("the detail slot closes"); + let detail = &error_page[detail_begin..detail_begin + detail_end]; + assert!( + detail.contains(""") && !detail.contains('"') && !detail.contains('<'), + "interpolated error detail is entity-escaped; the static markup alone carries quotes" + ); +} + +#[test] +fn repeated_renders_across_rebuilds_and_sinks_are_byte_identical() { + let set_a = consumer_fixture_set(); + let context = context_with(true, "scheduled maintenance"); + + let first = set_a + .render("page", &context) + .expect("the page should render"); + for _ in 0..4 { + let again = set_a + .render("page", &context) + .expect("repeated renders must succeed"); + assert_eq!( + again, first, + "repeated renders must be byte-identical (ADR-003, the caching substrate)" + ); + } + + let rebuilt_context = context_with(true, "scheduled maintenance"); + let rebuilt = consumer_fixture_set() + .render("page", &rebuilt_context) + .expect("a rebuilt set should render"); + assert_eq!( + rebuilt, first, + "same registry contents rebuilt must render byte-identical output" + ); + + let card_again = consumer_fixture_set() + .render( + "card", + &card( + "repo", + "Repo \"settings\" &