Files
alkgen/docs/architecture/core.md
T
glm-5.3-flash 9d57d1612e docs(architecture): review pass: flip specs to reviewed; fix review findings
- 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
2026-11-06 10:33:43 +00:00

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 &str inline literals or include_str! files. handlebars' dir_source and script_helper features 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, validator for the codec set; page, fragment for 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-form serde_json::Value contexts 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_type precedent). 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_order is 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::Write target (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/expect outside 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 to wasm32-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)