Files
alkgen/tasks/implementation/w1-registry-construction.md
T
glm-5.3-flash 22f32e6fc5 feat(registry): the set builder per ADR-002/005/008
- 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
2026-11-06 19:59:19 +00:00

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
w1-error-model
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_fn vs 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 &str either as inline literals or caller text (ADR-008 consumer path: consumers register through the same machinery; no filesystem access anywhere, so dir_source is 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):
    1. 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.
    2. 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.rs re-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 --check pass

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: &quot; 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::new registers nothing beyond substrate built-ins) and pinned by a test: invoking an unregistered helper is a typed Error::Render, never silent output.
  • pub(crate) render was deliberately withheld: the render entry is w1-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-unknown verified 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.