- Define the fragment-rendering convention: render targets are registered templates composing partials; partials are composition units (core.md, emit.md posture B) - Define template kind; trim/restage inline rationale with ADR citations (core.md) - Amend ADR-008: wgsl set ownership resolved engine-owned via OQ-05's 2026-11-06 resolution - Add OQ-09: consolidate the untracked bast-rust-codecs detailed-spec residue (projection, error shape, writer survey) with paired tracker task - Fix oq-05 tracker name to match the corrected unblocking condition - Add reviewed-transition rule: deferred OQs with tracker tasks do not block reviewed; flip six spec docs draft -> reviewed Verification: taskgraph validate (8 tasks valid), em-dash grep zero hits, cargo doc --no-deps clean
10 KiB
10 KiB
status, last_updated
| status | last_updated |
|---|---|
| reviewed | 2026-11-06 |
alkgen Architecture: Shared Core
What and why
The core is everything in alkgen that produces text without knowing what the text is for. It exists because three consumers (alkforge SSR, BAST codecs, WGSL) share one shape, and because the first consumer is an SSR use whose needs contain no codegen concepts: if registry, context, and render machinery stayed clean under HTML, they are clean (ADR-001).
The core has six components: registry construction, template asset conventions, context conventions, the helper policy, the render entry point, and the error model. All are synchronous, wasm-clean, and deterministic (ADR-003).
Component map
| Component | Role |
|---|---|
| Registry construction | Builds one strict registry per template set; registration is fallible and typed |
| Template assets | Embedded templates and partials, named and versioned as code |
| Context conventions | Typed context structs built by Rust context builders |
| Helper policy | Minimal, pure textual utilities; semantic mapping stays in builders |
| Render entry point | Deterministic render of named templates over context values |
| Error model | One typed public error enum; failures are data |
Registry construction
- One registry per template set (rationale recorded in ADR-002, decision 6: it is what makes the escape policy and partial namespaces enforceable rather than advisory). A template set is a coherent unit (an HTML set for a consumer, the BAST codec set, a future WGSL set). Each set gets its own registry with its own strict mode, escaper, and partial namespace. Registries do not share templates across sets; cross-set reuse is a source-level include, not registry surgery.
- Strict mode is always on (ADR-002, AGENTS.md convention 6). A missing context variable is a render error, not silent empty output.
- Registration is data, not side effects. Registering a template, partial, or helper is fallible (malformed templates produce a typed error) and happens at set-construction time. This applies to alkgen's own sets and to the API exposed to consumers (alkforge registers its page templates through the same machinery; ADR-008).
- No filesystem in lib code. Templates come from
const &strinline literals orinclude_str!files. handlebars'dir_sourceandscript_helperfeatures are off-limits (ADR-002, ADR-003).
Template assets
- Templates are code. Assets are versioned in-repo, embedded by
reference (
include_str!) or inline (const &str), and pinned by render tests asserting golden output (AGENTS.md convention 7). Changing a template is a source change. - Naming: lowercase kebab or snake; one name per template kind
(convention 9). A template kind is a named top-level artifact
category within a template set (a rendered unit:
reader,writer,validatorfor the codec set;page,fragmentfor SSR) and is distinct from partials, which are composition units. Generated file naming conventions are per-emit posture and live in emit.md. - Partials are first-class. Registration supports partials alongside templates from day one (alkminer-v1 precedent). Partials are composition units, not primary render targets: anything a consumer renders by name (a full page, a reusable fragment such as header, footer, or nav) is registered as a template that may compose partials via includes. This is the only branch-3 (template-JIT) requirement that reaches v1, and it is also the natural SSR unit (ADR-006).
- Consumers register their own assets. The registry construction API accepts externally supplied template and partial text so that consumer-owned sets (alkforge pages) need no alkgen source access (ADR-008).
Context conventions
- Contexts are typed Rust structs (
serde::Serialize, usually derived) built by Rust context builders (ADR-004). This is the typebox-rs shape with its weaknesses removed (typebox-rs: the older handlebars codegen precedent at/workspace/@alkimiadev/typebox-rs/src/codegen). Free-formserde_json::Valuecontexts are not the convention; where a document tree is passed through (schema fragments), it is passed as a value inside a typed context. - Kind mapping happens in builders, not templates. Mapping schema
kinds to target-language type strings (or HTML structures) is Rust
code in the context builder (the
schema_to_rust_typeprecedent). Templates stay dumb: conditionals over context flags, no lookup logic of their own. This is what keeps template sets portable to a second language target without template rewrites (phase-0 OQ-GN-04). - Ordering is document order.
serde_json::preserve_orderis load-bearing in contexts built from schema JSON: field order determines packed byte order downstream, and rendering iterates context arrays in insertion order. Sort only where a template explicitly requires sorted output (convention 5). - Plan semantics, document names (branch-1 refined rule, carried from the reader POC; ADR-004 decision 4, finding F-6): semantic content (offsets, strides, dispatch tables) comes from alktype plans; names, docs, and descriptive metadata come from the source document. Whether a branch-1-style projection exists at all is a template-set property, and the core imposes nothing about it (ADR-001).
- Pure-cursor model for code contexts. Where a codec context models addressing, it is uniform cursor semantics; no static/dynamic offset-phase tracking (reader POC finding F-1).
Helper policy
- Custom helpers exist to serve text-level utilities that are awkward as template logic or context fields (padding, repetition, small formatting). They must be pure and deterministic; no environment reads, no time, no randomness, no I/O.
- Semantic decisions stay in builders (see above). A helper never interprets a schema or a document; helpers see text.
- Each helper is registered explicitly by the owning template set. The core registers none by default beyond handlebars' built-ins.
Render entry point
- Synchronous and deterministic. The entry point takes a registry, a template name, and a context value; produces the rendered text. Same registry contents plus same context produces byte-identical text (ADR-003). Rendering performs no I/O, no time reads, no ordering that depends on hash maps leaking into output.
- Templates are the render targets. The name argument resolves to a registered template, never a bare partial: whatever a consumer renders directly (a page or a fragment) must be registered as a template; partials exist to be composed inside one (see Template assets; ADR-006).
- Two sinks: render into a String, or into an
io::Writetarget (both wasm-clean; streaming a large page or a large generated file should not force intermediate allocation). Which is exposed first is an implementation choice; both are part of the concept. - Strict errors. Render failures (missing variable in strict mode, template error) are typed errors, not silent empty output.
- Purity for downstream caching. Because rendering is pure and deterministic, consumers may cache by content hash (SSR) or compile the output (template-JIT). The core does not cache; emit.md and consumers do.
Error model
- One public error enum for the crate, via thiserror, covering the engine-owned operations: registration, template parsing, and rendering, plus context-build failures raised by engine-owned set builders. Consumer-side context construction is the consumer's code (ADR-008): engine sets fold their errors into the crate enum; consumer builders surface their own types and wrap or translate at the boundary if they choose. Render and registration failures are data: malformed templates or malformed BAST input are typed errors, never panics (AGENTS.md convention 2, family posture: alkcall accepts BAST from arbitrary peers, alkforge serves hostile traffic).
- Generated code carries its own error type(s), decided per template set (branch-1 decision recorded in ADR-007). The core's error model stops at rendering.
- No
unwrap/expectoutside tests; no panics in lib code.
Constraints
- Synchronous; no async, no tokio, no threads (convention 3).
- Wasm-clean: no filesystem, no
std::time, no environment reads, no platform-specific dependencies in lib code (convention 4). Verified by building the actual dep set towasm32-unknown-unknown. - No unsafe (convention 11).
- Determinism as above; generated-artifact hygiene rules (banner, reproducibility) are emit-layer rules specified in emit.md, but they are enabled here by deterministic rendering.
Design Decisions
All design decisions are documented as ADRs in decisions/.
| ADR | Decision | Summary |
|---|---|---|
| 001 | One core, targets as template sets | No target assumptions in shared types |
| 002 | handlebars 6.4, strict mode, embedded templates | Substrate, typed registration |
| 003 | Sync, wasm-clean, deterministic | Platform posture of the whole core |
| 004 | Typed contexts, mapping in builders | Context conventions |
| 005 | Escaper per registry | HTML-escape policy for SSR, no-op for code |
| 008 | Template set ownership | Consumer-owned assets register through the core API |
Open Questions
Open questions are tracked in open-questions.md. Key questions affecting this document:
- OQ-07: whether alkgen ships generic HTML layout primitives for SSR consumers (deferred(scope))
- OQ-05: WGSL template set requirements (deferred(scope))
- OQ-06: template-JIT core requirements (deferred(scope); partials are already first-class and rendering already pure, so nothing in this doc is expected to change)