- public TemplateSet::render (String sink) and TemplateSet::render_to_write (io::Write sink), synchronous and deterministic, both surfacing the wave-1 Error enum - render-name rule enforced behaviorally: the entry resolves names against the set-level bookkeeping before the substrate is consulted, so a partial's name or an unknown name is a typed Error::Render (substrate TemplateNotFound reason), never a rendered partial - strict-mode missing variable is a typed error in both sinks, never silently-empty output; the write sink's streaming (non-atomic) semantics on mid-render failure documented - determinism pinned by tests: byte-identical repeated renders, byte-identical output across registry rebuilds in opposite registration order, and byte-for-byte agreement between sinks Verification: cargo test (19 passed), cargo clippy --all-targets -- -D warnings, cargo fmt --check, cargo doc --no-deps, and cargo build --target wasm32-unknown-unknown --release all green.
6.6 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| w1-render-entry | Wave 1: the render entry point (both sinks, deterministic, strict) | completed |
|
narrow | low | project | implementation |
Description
Land the render entry point (core.md §Render entry point), the component every consumer actually calls:
- Synchronous and deterministic: render a registered template by name over a serde-serializable context. Same registry contents plus same context produce byte-identical text (ADR-003): test-pinned here (repeated renders over cloned and rebuilt registries), deepened by wave 2's battery. No I/O, no time reads, no hash-map iteration order leaking into output.
- Two sinks: render into a String, and render into an
io::Writetarget (both wasm-clean; the substrate exposes both,renderandrender_to_write; the core passes them through so a large page or large generated file need not force intermediate allocation). Strict errors through both sinks surfacing the wave-1 error enum. - Templates are the render targets: rendering by a partial's name is rejected per the wave-1 registry task's recorded mechanism (bookkeeping or convention plus tests; whatever landed there, the render entry is where the behavior is visible and tested).
- Strict errors: a missing context variable in strict mode is a typed error through the public enum, never silent-empty output (ADR-002 decision 2).
src/lib.rsre-exports the surface.
Acceptance Criteria
- String-sink and
io::Write-sink render entry points, both surfacing the public error enum - determinism pinned by tests (byte-identical repeated renders, including across registry rebuilds)
- strict-mode missing-variable render is a typed error in both sinks, never empty output
- render-by-partial behavior matches the registry task's recorded decision and is pinned by a test
cargo test,cargo clippy --all-targets -- -D warnings,cargo fmt --checkpass
References
- docs/architecture/core.md §Render entry point
- docs/architecture/decisions/002-handlebars-substrate-strict-mode.md
- docs/architecture/decisions/003-core-posture-sync-wasm-deterministic.md
- docs/architecture/emit.md §Cross-posture obligations (the determinism row every posture leans on)
- /workspace/@alkdev/alkminer/reference/alkminer-v1/src/compute/ kernel.rs (render-then-compile precedent: purity for downstream caching)
Notes
Agent fills this during implementation.
Landed surface. Two public methods on TemplateSet
(src/registry.rs): render<T: serde::Serialize>(name, context) -> Result<String, Error> and render_to_write<T: serde::Serialize, W: std::io::Write>(name, context, writer) -> Result<(), Error>. Both
are synchronous, &self, and wrap the substrate's render /
render_to_write (registry.rs:786, :804 in the vendored checkout);
both surface the wave-1 Error enum, mapping the substrate
RenderError through the existing #[from] arm. No new dependency, no
new error variant, no change to lib.rs re-exports (the entry points
are methods on the already-re-exported TemplateSet). The
#[cfg(test)] render shim the registry task left behind is removed;
the earlier registry tests now exercise the public entry.
Render-name rule enforcement (the registry task's decision 1,
behavioral half). The substrate cannot enforce the rule
(register_partial is literally register_template_string, its
registry is one namespace), so the render entry resolves the name
against the set-level bookkeeping (template_names) before the
substrate is consulted. A name registered as a partial, or never
registered, is rejected as Error::Render wrapping the substrate's
RenderErrorReason::TemplateNotFound, constructed through the public
non-deprecated path (From<RenderErrorReason> for RenderError).
Unknown names surface through the same guard, which also removes any
chance of the substrate's own template-not-found path diverging.
Pinned by render_by_partial_name_and_unknown_name_are_rejected_typed,
covering both sinks.
Strict errors through both sinks. A missing context variable fails
both sinks as Error::Render, never silent empty output: pinned by
strict_missing_variable_is_a_typed_error_in_both_sinks (the string
sink must return Err, never an empty Ok). Recorded interpretation
about the write sink: the substrate streams to the writer
incrementally, so a render that fails partway may leave bytes already
written to the target before the typed error returns. This is
documented in render_to_write's doc comment as streaming semantics
(consumers wanting atomicity buffer first); the test pins the typed
error, not the exact partial byte count, to avoid pinning substrate
internals.
Determinism. Pinned by two tests: a fixture set (template +
partial + {{#each}} loop) renders byte-identical across five
repeated renders and across a rebuilt registry where the same
registrations land in the opposite order (no registration order leaks
into output); the write sink agrees with the string sink byte for
byte. No time, no I/O, no hash-map iteration order in the path: the
only additions are two method bodies delegating to the substrate plus
the name guard.
Verification (all green). cargo test (19 passed),
cargo clippy --all-targets -- -D warnings, cargo fmt --check,
cargo doc --no-deps (warning-free; one broken intra-doc link during
development fixed), cargo build --target wasm32-unknown-unknown --release. No em dashes in the added prose.
Summary
Agent fills this on completion.
Render entry point landed as the public surface of TemplateSet
(src/registry.rs): render (String sink) and render_to_write
(io::Write sink), synchronous and deterministic, both surfacing the
wave-1 Error enum. The render-name rule from the registry task's
recorded mechanism is now behavior: the entry resolves names against
the set-level bookkeeping before the substrate is consulted, so a
partial's name (or a never-registered name) is a typed
Error::Render in both sinks, never a rendered partial. Strict mode
makes a missing variable a typed error in both sinks, never empty
output; the write sink's streaming (non-atomic) semantics on mid-render
failure are documented at the method. Determinism is pinned by tests:
byte-identical repeated renders, byte-identical output across a
rebuilt registry registering in the opposite order, and byte-for-byte
agreement between the two sinks. Verification: 19 tests pass, clippy
-D warnings clean, fmt --check clean, cargo doc --no-deps
clean, wasm release build to wasm32-unknown-unknown clean.