- TemplateSet: one strict handlebars registry per template set; strict
mode forced at construction, unparameterizable; enforcement test pins
missing-variable renders as Error::Render, never empty output
- EscapePolicy::{Html, NoOp}: explicit per-set escaper choice; Html
leaves the substrate default html_escape, NoOp registers
handlebars::no_escape; both postures pinned by golden render tests
- typed fallible registration of templates and partials; malformed
text is Error::Registration, never a panic; failed registration
leaves bookkeeping untouched
- set-level bookkeeping of template vs partial names (decision 1,
flagged for the gate): the substrate's register_partial is identical
to template registration, so the render-name rule cannot delegate to
it; TemplateSet::is_template exposes the tracking for the
w1-render-entry rejection
- per-set helper registration seam; zero custom helpers by default,
pinned by test (unknown helper is a typed render error)
- naming conveventions enforced by tests only, not at registration
(decision 2, flagged for the gate): consumer-facing engine API
stays naming-neutral per ADR-001/008
Verification: cargo test (15 pass), clippy -D warnings, fmt --check,
wasm32-unknown-unknown release build
Verification: cargo test (15 pass), clippy --all-targets -D warnings, cargo fmt --check, wasm32-unknown-unknown --release
8.6 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| w1-registry-construction | Wave 1: registry construction (strict mode, escaper policy, typed registration) | completed |
|
narrow | medium | project | implementation |
Description
Land the set builder, the first and most contract-heavy component of the core (core.md §Registry construction; ADR-002 decision 6):
- One registry per template set, constructed through alkgen's API: the builder takes a set's identity options and wraps one handlebars registry scoped to that set. Strict mode, escape policy, and the partial namespace are registry-scoped properties; the one-registry- per-set rule is what makes them enforceable rather than advisory (ADR-002 decision 6). Strict mode is forced on at construction, never parameterizable. A test pins the enforcement (a registry built through the API cannot render missing variables as silent empty output).
- Escaper as an explicit per-registry choice (ADR-005): the build
parameter covers the two shipped postures: handlebars HTML escaping
for ssr-shaped sets and a no-op identity posture for code sets.
Decide the mechanism against handlebars 6.4's escape-fn surface
(
register_escape_fnvs disabling built-in HTML escape) and record it in Notes. Both postures get a rendering test pinning their behavior. - Typed fallible registration: register templates and partials by
name + text. Malformed template text produces the wave-1 error
enum's registration variant, never a panic (ADR-002 decision 3).
Registration accepts externally supplied
&streither as inline literals or caller text (ADR-008 consumer path: consumers register through the same machinery; no filesystem access anywhere, sodir_sourceis unreachable by construction). - Helpers: the registration seam exists (per-set helper registration pass-through), but the core registers none by default (core.md §Helper policy): helpers see text, are pure and deterministic, and are owned by their template sets; the purity statement is a documented convention, not runtime enforcement.
- Decide + record (both flagged for the wave-1 gate):
- How the render-name rule ("the name argument resolves to a registered template, never a bare partial"; core.md) is enforced, given handlebars' registry is a single namespace (registering a partial is template registration under the hood). Choose set- level bookkeeping (a set type that knows which registered names are templates) or convention plus tests, and pin it.
- Whether template naming conventions (AGENTS.md convention 9: lowercase kebab/snake, one name per template kind) are validated at registration or enforced by tests only.
src/lib.rsre-exports the public surface.
Acceptance Criteria
- set builder with forced strict mode; an enforcement test proves a missing variable renders as a typed error, never empty output
- escaper choice explicit per registry (HTML and no-op postures), each pinned by a rendering test
- template and partial registration fallible and typed; malformed template text yields the error enum, never a panic
- externally supplied text registers identically (the ADR-008 consumer path exercised)
- helper registration seam exists; zero helpers registered by default
- the two decisions recorded in Notes; both flagged in the task Summary for the gate
cargo test,cargo clippy --all-targets -- -D warnings,cargo fmt --checkpass
References
- docs/architecture/core.md §Registry construction, §Template assets, §Helper policy
- docs/architecture/decisions/002-handlebars-substrate-strict-mode.md (decisions 3, 5, 6)
- docs/architecture/decisions/004-typed-contexts-builders-not-templates.md
- docs/architecture/decisions/005-escape-strategy-per-template-set.md
- docs/architecture/decisions/008-template-set-ownership.md
- docs/plans/implementation.md (the partial-enforcement and naming interpretations)
- /workspace/@alkdev/alkminer/reference/alkminer-v1/src/compute/ kernel.rs (the strict-registry precedent)
Notes
Agent fills this during implementation. The two decisions (partial enforcement mechanism, naming validation posture) must be recorded here.
Landed surface. src/registry.rs (TemplateSet), src/escape.rs
(EscapePolicy::{Html, NoOp}), re-exported from src/lib.rs
alongside the wave-1 Error. The builder wraps one
handlebars::Handlebars<'static> per set; strict mode is set
unconditionally inside TemplateSet::new and no API exposes it, so it
is not parameterizable by construction (enforcement test: a missing
variable renders Error::Render, a present one renders the golden
hello world).
Decision 1: render-name rule enforced by set-level bookkeeping.
The substrate's register_partial is literally
register_template_string (handlebars 6.4, registry.rs:309-318,
vendored checkout: "Currently a registered partial is just identical
to a template"), so the substrate cannot distinguish them and the rule
cannot be delegated to it. Chosen mechanism: TemplateSet keeps
per-set bookkeeping (template_names / partial_names; registration
failure leaves it untouched, re-registering moves a name's kind),
exposed as TemplateSet::is_template (public, additive, pre-publish).
The behavioral enforcement (rejecting render-by-partial-name as a
typed error) lands in w1-render-entry, which consumes this surface
and pins the behavior; this task pins the bookkeeping itself by tests.
Flagged for the wave-1 gate.
Decision 2: naming conventions enforced by tests only, not at registration. Convention 9 (lowercase kebab/snake, one name per template kind) governs alkgen's own assets; validating names at registration would bake that house style into the consumer-facing engine API (ADR-001 consumer-blindness, ADR-008 machinery equality: consumers would get rejections for naming choices the core has no mandate to police). "One name per kind" is also semantic, not mechanical. Pinned the weaker structural facts by tests instead (fixture names follow the convention; reviewed like source). Flagged for the wave-1 gate.
Escaper mechanism (ADR-005). handlebars 6.4 keeps
register_escape_fn with html_escape / no_escape shipped fn
targets. Chosen: the Html posture leaves the substrate's default
html escaper untouched (ADR-005 posture 1 says exactly this: the
default "stays on"); the NoOp posture registers
handlebars::no_escape registry-wide. The EscapePolicy enum is the
explicit per-set choice; the escaper is not swappable afterward
through TemplateSet. Both postures pinned by golden render tests
(HTML: {{& }} escape-out documented alongside escaped interpolation;
NoOp: " passes through verbatim, which the HTML policy would
double-escape).
Other recorded choices.
- Re-registration overwrites (substrate semantics) and moves the name's kind in bookkeeping; no new error variant was invented, per the error model's no-catch-all rule. If the wave-1 gate wants collision rejection, it is an additive variant plus one check.
- Zero custom helpers registered by default is structural
(
TemplateSet::newregisters nothing beyond substrate built-ins) and pinned by a test: invoking an unregistered helper is a typedError::Render, never silent output. pub(crate) renderwas deliberately withheld: the render entry isw1-render-entry's surface; tests here exercise strict mode through the wrapped substrate via a#[cfg(test)]method.- ADR-008 consumer path exercised by test: caller-owned
Strings register through the identical methods; no filesystem anywhere. - Wasm build to
wasm32-unknown-unknownverified for the new modules (ADR-003 posture; the wave-2 battery will pin it standing).
Summary
Agent fills this on completion.
Wave-1 registry construction landed on the shipped error model:
EscapePolicy::{Html, NoOp} (src/escape.rs) and TemplateSet
(src/registry.rs), the per-set strict registry with typed fallible
registration of templates, partials, and the per-set helper seam.
Strict mode is forced and unparameterizable; the escaper is an
explicit construction-time choice pinned per posture; malformed text
is Error::Registration, never a panic; caller-supplied text
registers through the identical machinery (ADR-008).
Both flagged decisions recorded in Notes: (1) render-name rule via
set-level bookkeeping (TemplateSet::is_template), with the
behavioral rejection landing in w1-render-entry; (2) naming
conventions enforced by tests only. Verification: 15 tests pass,
clippy -D warnings clean, cargo fmt --check clean, wasm
wasm32-unknown-unknown release build clean.