test(ssr-proof): wave-2 ssr-html proof through the public API (posture B)

- full consumer-shaped fixture set registered from the test binary via
  register_template/register_partial only (ADR-008 machinery equality
  in three caller-supplied text shapes: fixtures, runtime String,
  inline literal)
- page composes fragment partials; the card fragment registers as its
  own template, renders directly by name, and composes via include
- exact goldens pin html-escape posture (hostile data escaped, = and '
  escapes, raw-output {{& }} slot verbatim)
- strict-mode render failure maps consumer-side to an error page built
  from the typed Error's data, panic-free; source chain reachable
- determinism pinned across repeats, registry rebuilds, fresh
  contexts, and both sinks (byte-identical, ADR-003)
- posture-B hygiene rows: no banner, no filesystem; no core changes
  required (stop-and-book: nothing to book)
This commit is contained in:
glm-5.3-flash committed 2026-11-06 20:31:11 +00:00
1 parent a4ec8be3e8
commit 08803b3d17
7 files changed
+394 -11

No files matched your search

+62 -11
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
<footer><p>&copy; {{year}} {{site_name}}</p></footer>
+1
View File
@@ -0,0 +1 @@
<header><h1>{{site_name}}</h1>{{#if tagline}}<p class="tagline">{{tagline}}</p>{{/if}}</header>
+1
View File
@@ -0,0 +1 @@
<nav><ul>{{#each nav_links}}<li><a href="{{href}}">{{label}}</a></li>{{/each}}</ul></nav>
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
{{> header}}
<body>
{{> nav}}
<main>
{{#each cards}}{{> card}}{{/each}}
</main>
{{#if show_banner}}<div class="banner">{{banner_text}}</div>{{/if}}
{{& trusted_embed}}
{{> footer}}
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
<!doctype html>
<html lang="en">
{{> header}}
<body>
<main><h2>Account settings</h2><p>Signed in as {{account.email}}</p></main>
{{> footer}}
</body>
</html>
+308
View File
@@ -0,0 +1,308 @@
//! The wave-2 ssr-html proof (posture B, consumer-shaped): a full
//! fixture set modeled on the alkforge posture, rendered entirely through
//! the public API and pinned by goldens. Where the wave-1 double proved
//! the machinery minimally, this proof walks the ssr-html target's real
//! shape at fuller scale: if the shipped core serves this set without a
//! core change, ADR-001's generality claim holds on shipped code.
use std::error::Error as StdError;
use alkgen::{Error, EscapePolicy, TemplateSet};
use serde::Serialize;
use serde_json::json;
mod fixtures {
pub const PAGE: &str = include_str!("fixtures/ssr_proof/page.hbs");
pub const HEADER: &str = include_str!("fixtures/ssr_proof/header.hbs");
pub const NAV: &str = include_str!("fixtures/ssr_proof/nav.hbs");
pub const FOOTER: &str = include_str!("fixtures/ssr_proof/footer.hbs");
pub const SETTINGS_PAGE: &str = include_str!("fixtures/ssr_proof/settings_page.hbs");
}
/// The caller-supplied error page the failed render maps toward: built
/// on the consumer side from the typed error's data, never from a core
/// path.
const ERROR_PAGE: &str = "<!doctype html>\n<html lang=\"en\">\n<body><h1>Something went wrong</h1><p class=\"detail\">{{message}}</p></body>\n</html>\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<NavLink>,
cards: Vec<Card>,
show_banner: bool,
banner_text: String,
trusted_embed: String,
year: u16,
}
fn nav_link(href: &str, label: &str) -> NavLink {
NavLink {
href: href.to_owned(),
label: label.to_owned(),
}
}
fn card(kind: &str, title: &str, body: &str) -> Card {
Card {
kind: kind.to_owned(),
title: title.to_owned(),
body: body.to_owned(),
}
}
fn context_with(show_banner: bool, banner_text: &str) -> PageContext {
PageContext {
site_name: "Alk Forge".into(),
tagline: "Your repositories".into(),
nav_links: vec![
nav_link("/", "Home"),
nav_link("/search?q=<script>&lang=en", "<script>alert('x')</script>"),
],
cards: vec![
card(
"repo",
"Repo \"settings\" & <config>",
"if (a < b) { alert(\"<b>hi</b>\") }",
),
card("link", "Plain card", "A plain body."),
],
show_banner,
banner_text: banner_text.to_owned(),
trusted_embed: r#"<aside id="notice"><b>system notice</b></aside>"#.into(),
year: 2026,
}
}
/// The card fragment's template text arrives as a consumer-built
/// `String`: the caller owns the text however it wants (inline literal,
/// runtime-built, or fixture file) and registers it through the same
/// API engine-owned sets use (ADR-008 machinery equality).
fn caller_supplied_card_text() -> String {
let mut text = String::from("<article class=\"card\" data-kind=\"{{kind}}\">");
text.push_str("<h3>{{title}}</h3>");
text.push_str("<p>{{body}}</p>");
text.push_str("</article>");
text
}
/// The full consumer-shaped fixture set: pages, fragments, and the
/// error page all register from the test binary through the public API.
/// Partials exist to be composed; fragments a consumer serves directly
/// register as their own templates.
fn consumer_fixture_set() -> TemplateSet {
let mut set = TemplateSet::new(EscapePolicy::Html);
set.register_template("page", fixtures::PAGE)
.expect("fixture page template should be well-formed");
set.register_template("card", &caller_supplied_card_text())
.expect("caller-supplied card template should be well-formed");
set.register_template("settings_page", fixtures::SETTINGS_PAGE)
.expect("fixture settings page template should be well-formed");
set.register_template("error_page", ERROR_PAGE)
.expect("caller-supplied error page template should be well-formed");
set.register_partial("header", fixtures::HEADER)
.expect("fixture header partial should be well-formed");
set.register_partial("nav", fixtures::NAV)
.expect("fixture nav partial should be well-formed");
set.register_partial("footer", fixtures::FOOTER)
.expect("fixture footer partial should be well-formed");
set
}
const GOLDEN_PAGE: &str = concat!(
"<!doctype html>\n",
"<html lang=\"en\">\n",
"<header><h1>Alk Forge</h1><p class=\"tagline\">Your repositories</p></header>\n",
"<body>\n",
"<nav><ul><li><a href=\"/\">Home</a></li><li><a href=\"/search?q&#x3D;&lt;script&gt;&amp;lang&#x3D;en\">&lt;script&gt;alert(&#x27;x&#x27;)&lt;/script&gt;</a></li></ul></nav>\n",
"<main>\n",
"<article class=\"card\" data-kind=\"repo\"><h3>Repo &quot;settings&quot; &amp; &lt;config&gt;</h3><p>if (a &lt; b) { alert(&quot;&lt;b&gt;hi&lt;/b&gt;&quot;) }</p></article><article class=\"card\" data-kind=\"link\"><h3>Plain card</h3><p>A plain body.</p></article>\n</main>\n",
"<div class=\"banner\">&lt;em&gt;scheduled&lt;/em&gt; maintenance</div>\n",
"<aside id=\"notice\"><b>system notice</b></aside>\n",
"<footer><p>&copy; 2026 Alk Forge</p></footer>\n",
"</body>\n",
"</html>\n",
);
const GOLDEN_CARD: &str = concat!(
"<article class=\"card\" data-kind=\"repo\">",
"<h3>Repo &quot;settings&quot; &amp; &lt;config&gt;</h3>",
"<p>if (a &lt; b) { alert(&quot;&lt;b&gt;hi&lt;/b&gt;&quot;) }</p>",
"</article>",
);
const GOLDEN_ERROR_PAGE: &str = concat!(
"<!doctype html>\n",
"<html lang=\"en\">\n",
"<body><h1>Something went wrong</h1><p class=\"detail\">render failed: Error rendering &quot;settings_page&quot; line 5, col 48: Failed to access variable in strict mode Some(&quot;account.email&quot;)</p></body>\n",
"</html>\n",
);
#[test]
fn page_golden_pins_composition_escaping_and_raw_slot_at_fuller_scale() {
let set = consumer_fixture_set();
let context = context_with(true, "<em>scheduled</em> 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#"<a href="/search?q&#x3D;&lt;script&gt;&amp;lang&#x3D;en">&lt;script&gt;alert(&#x27;x&#x27;)&lt;/script&gt;</a>"#),
"hostile interpolated data must be entity-escaped"
);
assert!(
rendered.contains(r#"<h3>Repo &quot;settings&quot; &amp; &lt;config&gt;</h3>"#),
"hostile card data must be entity-escaped everywhere it interpolates"
);
assert!(
rendered.contains(r#"<aside id="notice"><b>system notice</b></aside>"#),
"the deliberate raw-output slot ({{& }}) must pass through verbatim"
);
assert!(
rendered.starts_with("<!doctype html>"),
"the output unit is the served page, unadorned"
);
assert!(
!rendered.contains("DO NOT EDIT"),
"posture-B hygiene: rendered HTML is the product, not a checked-in generated artifact"
);
assert!(
!rendered.contains("Auto-generated"),
"posture-B hygiene: no emit-layer banner on rendered HTML"
);
}
#[test]
fn card_fragment_registers_as_template_and_renders_directly_by_name() {
let set = consumer_fixture_set();
assert!(set.is_template("card"));
let rendered = set
.render(
"card",
&card(
"repo",
"Repo \"settings\" & <config>",
"if (a < b) { alert(\"<b>hi</b>\") }",
),
)
.expect("a fragment registered as its own template must render directly by name");
assert_eq!(rendered, GOLDEN_CARD);
let err = set
.render("header", &context_with(false, ""))
.expect_err("partial names are never render targets (the render-name rule)");
assert!(matches!(err, Error::Render(_)));
}
#[test]
fn strict_failure_maps_to_the_error_page_shape_panic_free() {
let set = consumer_fixture_set();
let context = json!({
"site_name": "Alk Forge",
"tagline": "",
"year": 2026
});
let err = set
.render("settings_page", &context)
.expect_err("a strict-mode render failure must return the typed error, never a panic");
assert!(matches!(err, Error::Render(_)));
let source = err
.source()
.expect("everything a consumer needs for the mapping is reachable through the public enum");
assert!(
format!("{source}").contains("account.email"),
"the substrate's missing-variable detail stays reachable consumer-side"
);
let error_page = set
.render("error_page", &json!({ "message": err.to_string() }))
.expect("the consumer-built error page must render over the error's data");
assert_eq!(error_page, GOLDEN_ERROR_PAGE);
assert!(
error_page.contains("&quot;settings_page&quot;")
&& error_page.contains("&quot;account.email&quot;"),
"the failing template's identity and the missing path surface entity-escaped"
);
let detail_begin = error_page
.find(r#"<p class="detail">"#)
.map_or(0, |at| at + r#"<p class="detail">"#.len());
let detail_end = error_page[detail_begin..]
.find("</p>")
.expect("the detail slot closes");
let detail = &error_page[detail_begin..detail_begin + detail_end];
assert!(
detail.contains("&quot;") && !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, "<em>scheduled</em> 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, "<em>scheduled</em> 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\" & <config>",
"if (a < b) { alert(\"<b>hi</b>\") }",
),
)
.expect("the fragment should render from the rebuilt set");
assert_eq!(
card_again, GOLDEN_CARD,
"fragment renders are byte-identical across registry rebuilds"
);
let mut sink = Vec::new();
consumer_fixture_set()
.render_to_write("page", &context, &mut sink)
.expect("the write sink should render the page");
assert_eq!(
&sink[..],
first.as_bytes(),
"both sinks must agree byte for byte"
);
}