docs(architecture): phase 1 base: text-generation core specs, ADRs 001-010, OQ tracker

- overview.md, core.md, emit.md, targets.md: modular spec set for the
  generalized engine (contexts + templates + partials -> text), with
  the three targets (ssr-html, bast-rust-codecs, call-handlers,
  wgsl-jit) as template sets plus emit postures
- ADRs 001-010: one core/template-set split, handlebars 6.4 strict
  substrate, sync/wasm/deterministic posture, typed contexts with
  builders, per-registry escaper, SSR library posture, typed direct
  codecs output contract, template-set ownership, feature gating,
  library-only v1
- open-questions.md: OQ-01..OQ-08 centralized tracker carrying phase-0
  residue (GN-01/02/07/09) with three deferred(scope) entries plus a
  resolved disposition for phase-0 OQ-GN-04
- tasks/architecture/: external-trigger tracker tasks paired with the
  deferred OQs (taskgraph validate passes)
- phase-0.md checklist dispositioned for OQ-GN-04; AGENTS.md lifecycle
  updated to phase 1

Verification: taskgraph validate (4 tasks OK), cargo fmt --check,
cargo test, em-dash grep clean; architecture-reviewer pass returned
zero critical issues
This commit is contained in:
glm-5.3-flash committed 2026-11-06 08:18:43 +00:00
1 parent 93d70253d7
commit a91ea2ec89
22 files changed
+1685 -15

No files matched your search

+19 -12
View File
@@ -171,9 +171,11 @@ semantics live in `docs/sdd_process.md` §"Task File Format" /
## Lifecycle Status
The project is in **SDD phase 0** (exploration; no architecture
committed, no implementation on main). It was paused 2026-09-08, after
the reader POC (branch 1), and unpaused 2026-11-06.
The project is in **SDD phase 1** (architecture). Phase 0 completed
with the reader POC; the project was paused 2026-09-08 and unpaused
2026-11-06. The architecture base (spec docs, ADRs 001-010, OQ
tracker in `docs/architecture/`) was committed 2026-11-06; docs are
in `draft` status pending OQ resolution and review finalization.
The unpause reason: alkforge (family ADR-002, gitea replacement; see
`/workspace/@alkdev/alkfamily/docs/architecture/decisions/002-alkforge-gitea-replacement.md`)
@@ -190,12 +192,13 @@ Family ADR-002 also records the no-deformation rule for consumers:
downstream asks file upstream gaps rather than forking or patching the
core, so alkforge's SSR needs flow into alkgen's design, not around it.
Phase 0 output lands in `docs/research/` (the phase 0 document is the
current work item); `docs/architecture/` + ADRs come with phase 1. POC
code never merges to main; two modes exist: branch mode (POC builds on
repo code; git branch, findings merged into research docs, branch
dropped) or standalone mode (POC independent of repo code; scratch
project at `/workspace/<poc-name>`). POCs have relaxed constraints
Phase 0 output lands in `docs/research/` (completed: phase-0 findings
and the reader POC summary); Phase 1 output lands in
`docs/architecture/` (see Architecture Context). POC code never
merges to main; two modes exist: branch mode (POC builds on repo
code; git branch, findings merged into research docs, branch dropped)
or standalone mode (POC independent of repo code; scratch project at
`/workspace/<poc-name>`). POCs have relaxed constraints
(comments/unwrap acceptable) since they are exploration tools, not
production code.
@@ -209,9 +212,13 @@ production code.
answers questions. The branch list there reflects the 2026-11-06
unpause (alkforge SSR first, then BAST readers, then call-protocol
handlers, then the template-JIT core).
- `docs/architecture/`: will exist after Phase 1; the authoritative
spec once present (ADRs numbered, OQs tracked in open-questions.md).
- `tasks/`: task graph after decomposition.
- `docs/architecture/`: the authoritative architecture spec (started
2026-11-06, docs in `draft` status). `README.md` there is the index
(doc table, ADR table, lifecycle); ADRs are numbered under
`decisions/`; OQs are tracked in `open-questions.md` with stable
OQ-IDs. Extend it rather than re-litigating recorded decisions.
- `tasks/architecture/`: architecture-side taskgraph tasks, including
the external-trigger tracker tasks paired with deferred(scope) OQs.
## Reference Material (outside this repo)
+81
View File
@@ -0,0 +1,81 @@
---
status: draft
last_updated: 2026-11-06
---
# alkgen Architecture
This directory is the authoritative architecture specification for the
alkgen crate. It was created at the start of Phase 1 (2026-11-06) from
the Phase 0 findings in `docs/research/phase-0.md` and
`docs/research/reader-poc-summary.md`.
## Current State
Phase 1 (architecture) has started; Phase 0 is complete. The project
was paused 2026-09-08 after the reader POC and unpaused 2026-11-06
because alkforge (family ADR-002) needs server-side HTML generation,
which alkgen provides. No implementation exists on main yet; `src/`
holds the validated dependency skeleton only.
The crate is a **text-generation engine**: typed contexts plus
templates plus partials produce text. Targets (HTML SSR, Rust codecs,
WGSL) are template sets plus emit layers over one shared core. The
generalization is enforced by the consumers themselves, not asserted:
HTML is handlebars' native case, Rust codecs were validated end-to-end
by the reader POC, and WGSL is proven by alkminer-v1's `ShaderBuilder`.
Branch numbering below follows Phase 0 conventions: branch 0 is the
SSR addition from the unpause; branches 1, 2, 3 retain their historical
meanings. Target names (ssr-html, bast-rust-codecs, call-handlers,
wgsl-jit) are preferred in these documents; historical branch numbers
may appear alongside them, and the mapping lives in `targets.md`.
## Architecture Documents
| Document | Scope | Status |
|---|---|---|
| [overview.md](overview.md) | Purpose, engine model, dependencies, constraints | draft |
| [core.md](core.md) | Shared text-generation core: registry, contexts, rendering, errors | draft |
| [emit.md](emit.md) | How rendered text is consumed: the three emit postures | draft |
| [targets.md](targets.md) | Per-target template sets, context shapes, validity pinning | draft |
| [open-questions.md](open-questions.md) | Centralized OQ tracker | draft |
## ADR Table
ADRs live in [decisions/](decisions/).
| ADR | Title | Status |
|---|---|---|
| [001](decisions/001-one-core-template-set-targets.md) | One text-generation core; targets are template sets plus emit layers | Accepted |
| [002](decisions/002-handlebars-substrate-strict-mode.md) | handlebars-rust 6.4 substrate: strict mode, typed errors, embedded templates | Accepted |
| [003](decisions/003-core-posture-sync-wasm-deterministic.md) | Core posture: synchronous, wasm-clean, deterministic | Accepted |
| [004](decisions/004-typed-contexts-builders-not-templates.md) | Contexts are typed Rust structs built by context builders; kind mapping in Rust | Accepted |
| [005](decisions/005-escape-strategy-per-template-set.md) | Escape strategy is a per-registry policy | Accepted |
| [006](decisions/006-ssr-library-posture.md) | SSR: alkgen is an in-process rendering library for alkforge | Accepted |
| [007](decisions/007-bast-codec-output-contract.md) | BAST codec template set: typed direct codecs, zero-dependency output | Accepted |
| [008](decisions/008-template-set-ownership.md) | Engine-owned template sets ship in alkgen; consumer page templates live in the consumer | Accepted |
| [009](decisions/009-feature-gated-template-sets.md) | Template sets are feature-gated; default features ship the core only | Accepted |
| [010](decisions/010-library-only-no-cli.md) | alkgen is library-only; build-script integration is a documented pattern, no CLI | Accepted |
## Open Questions
All open questions are tracked in [open-questions.md](open-questions.md)
with stable OQ-IDs. Spec documents reference OQs by number and ADRs by
number; rationale and unresolved questions are not inlined.
## Document Lifecycle
| Status | Meaning |
|---|---|
| `draft` | Under active development; may change significantly |
| `reviewed` | Architecture final; implementation may begin; changes require review |
| `stable` | Implementation complete and verified; changes warrant an ADR |
| `deprecated` | Superseded; kept for reference |
Docs carry YAML frontmatter (`status`, `last_updated`). A doc moves to
`reviewed` when its open questions are resolved and review passes.
The centralized tracker is `open-questions.md`; deferred questions
follow the two-half convention in `docs/sdd_process.md` (visible OQ
entry here plus an external-trigger tracker task under
`tasks/architecture/`).
+186
View File
@@ -0,0 +1,186 @@
---
status: draft
last_updated: 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. The reader POC caught a
stale context key through strict mode at build time; silent gaps in
SSR would surface as blank page regions.
- **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). 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). This is
the only branch-3 (template-JIT) requirement that reaches v1, and it
is also the natural SSR unit: header, footer, and nav fragments are
partials (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. 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): semantic content (offsets, strides, dispatch
tables) comes from alktype plans; names, docs, and descriptive
metadata come from the source document, because plans eagerly
resolve `$ref`s and lose def names. 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.
- **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/](decisions/).
| ADR | Decision | Summary |
|---|---|---|
| [001](decisions/001-one-core-template-set-targets.md) | One core, targets as template sets | No target assumptions in shared types |
| [002](decisions/002-handlebars-substrate-strict-mode.md) | handlebars 6.4, strict mode, embedded templates | Substrate, typed registration |
| [003](decisions/003-core-posture-sync-wasm-deterministic.md) | Sync, wasm-clean, deterministic | Platform posture of the whole core |
| [004](decisions/004-typed-contexts-builders-not-templates.md) | Typed contexts, mapping in builders | Context conventions |
| [005](decisions/005-escape-strategy-per-template-set.md) | Escaper per registry | HTML-escape policy for SSR, no-op for code |
| [008](decisions/008-template-set-ownership.md) | Template set ownership | Consumer-owned assets register through the core API |
## Open Questions
Open questions are tracked in [open-questions.md](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)
@@ -0,0 +1,64 @@
# ADR-001: One text-generation core; targets are template sets plus emit layers
## Status
Accepted
## Context
Phase 0 scoped the crate as "codegen over BAST documents" with static
file emission into downstream projects as the product. Two things
dissolved that framing:
1. The 2026-11-06 unpause arrived because alkforge (family ADR-002)
needs server-side HTML generation, a runtime rendering use with no
schema, no plans, and no file emission.
2. WGSL generation for alkjs (alkminer-v1's ShaderBuilder precedent)
and Rust codec generation (reader POC) are provably the same
machinery: contexts plus templates plus partials produce text.
Three data points, one shape. Treating SSR as a special case baked
into a codegen-shaped core would either fork the machinery or
deform it.
## Decision
alkgen's core is a text-generation engine: it owns registry
construction (strict mode, first-class partials, per-registry escape
policy), context conventions, a deterministic synchronous render entry
point, and typed errors. A target is a template set plus a context
shape plus an emit posture. Shared core types encode none of the
targets: no reader/writer assumptions, no HTML assumptions, no WGSL
assumptions.
Validity pinning is per template set: render tests for each set pin
what "valid output" means for it (Rust compilability, HTML
well-formedness, WGSL syntax are set-level property tests, never core
concepts).
The dev-tool identity is demoted from "the product" to "posture A of
the emit layer" (emit.md).
## Consequences
- Positive: three consumers, one maintenance surface; the SSR branch
acts as a standing overfitting test; the WGSL path needs no core
work beyond template sets when it arrives.
- Positive: the demotion is cheap because it changes nothing required
by branches 1 and 2; their needs are now a posture, not the crate's
definition.
- Negative: generalization can hide real per-target needs until late.
Mitigation: the first consumer is SSR (the least codegen-like), so
the core is exercised against the differentiator immediately.
- Negative: core APIs must stay small enough to remain
"everything-for-nothing" (no target vocabulary), which constrains
helpers and conveniences that would drift toward one target.
## References
- [targets.md](../targets.md) (the instance table)
- [emit.md](../emit.md) (the posture layer)
- Phase 0, section "Core generalization steer" and the framing
correction (`docs/research/phase-0.md`)
- [ADR-006](006-ssr-library-posture.md) (SSR posture),
[ADR-007](007-bast-codec-output-contract.md) (codec contract)
@@ -0,0 +1,68 @@
# ADR-002: handlebars-rust 6.4 substrate: strict mode, typed errors, embedded templates
## Status
Accepted
## Context
The render substrate was settled in Phase 0 (validated stack, wasm
playground shipped by handlebars itself), but the substrate posture
was distributed across conventions, phase-0 prose, and two precedent
codebases (typebox-rs and the reader POC). It needs one decision
record, because posture is what typebox-rs got wrong (unwrap-based
registration, no strict mode, silent empty output) and what the POC
validated as right.
## Decision
1. **Substrate**: handlebars-rust pinned to `6.4` from crates.io. The
vendored checkout (`/workspace/handlebars-rust`, one commit past
v6.4.4) is reading material and an emergency patch source only,
not the dependency.
2. **Strict mode always**: every registry the library constructs or
exposes sets strict mode on. A missing context variable is a
render error, never silently-empty text (AGENTS.md convention 6;
alkminer-v1 ShaderBuilder precedent; the POC caught a stale context
key through strict mode at build time).
3. **Registration is fallible and typed**: template and partial
registration surfaces propagate parse errors as typed errors
(no unwrap). Malformed templates and malformed inputs are data,
not crashes (AGENTS.md convention 2).
4. **Assets are embedded code**: templates come from `const &str` or
`include_str!`; `dir_source` (filesystem) and `script_helper`
(rhai) features are off-limits in lib code; templates are pinned
by golden render tests (AGENTS.md convention 7).
5. **Partials are first-class**: registration supports partials
alongside templates from day one (branch-3 (template-JIT)
requirement priced in while free; also the SSR fragment unit).
6. **One registry per template set**: strict mode, escape policy
(ADR-005), and partial namespaces are registry-scoped properties,
so a single per-set registry is what makes them enforceable rather
than advisory. Cross-set asset reuse is a source-level include,
never registry surgery. Consumer sets use the same construction
path (machinery equality, ADR-008).
## Consequences
- Positive: strict mode turns silent-output bugs into build-time
errors at the generator, close to the cause, instead of compile or
runtime bugs in consumers; typed registration keeps the no-panic
posture intact for hostile inputs.
- Positive: the embedded-asset rule keeps lib code wasm-clean and
reproducible.
- Negative: strict mode can reject templates for missing-but-defaulted
keys; the remedy is context shape and template guards (`{{#if}}`),
the pattern F-3 validated, not a strict-mode escape hatch.
- Negative: pinning to a crates.io version means substrate bug fixes
ride the handlebars release train; the vendored checkout bounds the
emergency case.
## References
- [core.md](../core.md) (registry construction, template assets)
- [ADR-003](003-core-posture-sync-wasm-deterministic.md) (platform
posture incl. the off-limits features)
- Phase 0, sections "What is already settled" and typebox-rs prior art
(`docs/research/phase-0.md`); reader POC findings F-3, F-4
(`docs/research/reader-poc-summary.md`)
@@ -0,0 +1,58 @@
# ADR-003: Core posture: synchronous, wasm-clean, deterministic
## Status
Accepted
## Context
alkgen links into wasm-bound runtimes (alkjs), wasm browsers via
handlebars' own playground precedent, and Rust services serving
hostile traffic (alkforge, alkcall consumers). Three properties are
prerequisites for all of those and far cheaper to keep than to
retrofit: synchronous execution, wasm-cleanliness, and deterministic
output.
## Decision
The core (and everything engine-owned) is:
1. **Synchronous**: no async, no tokio, no threads. Template rendering
and context building are CPU-bound; there is no await anywhere.
2. **Wasm-clean**: compiles to `wasm32-unknown-unknown`. No filesystem
access in lib code (templates embedded, `dir_source` off-limits),
no `std::time`, no environment reads, no platform deps; the dep
set is validated by an actual wasm build of every relevant
dependency combination (skeleton verified 2026-11-06).
3. **Deterministic**: same registry contents plus same context values
produce byte-identical text. No hash-map iteration order leaks into
output; contexts preserve document order where order is semantic
(serde_json `preserve_order`, AGENTS.md convention 5); no time or
randomness in rendering.
4. **Panic-free on inputs**: typed errors instead of panics for
malformed templates and malformed inputs.
## Consequences
- Positive: determinism is what makes consumer-side caching safe to
build (SSR content hashing), regeneration diff-quiet (posture A),
and render output compilable (posture C); it is one property paying
three ways.
- Positive: wasm-cleanliness is a supply-chain and deployment
posture for the family (minimal, auditable build), not just a
capability.
- Negative: no background refresh, no async caching, no wall-clock
features. Consumers wanting those build them on their side; the
engine's purity is the point, not a gap.
- Negative: `preserve_order` costs allocation and must be preserved
by every dependency that touches schema JSON; it is pinned because
packed byte order depends on it downstream.
## References
- [core.md](../core.md) (constraints section)
- [ADR-002](002-handlebars-substrate-strict-mode.md) (substrate
posture, off-limits features)
- [emit.md](../emit.md) (postures consuming determinism)
- AGENTS.md conventions 2, 3, 4, 5, 8
- Family ADR-002 decision 5 (security posture)
@@ -0,0 +1,64 @@
# ADR-004: Contexts are typed Rust structs built by context builders; kind mapping in Rust
## Status
Accepted
## Context
Templates need a context. Two precedent paths existed: free-form
`serde_json::Value` contexts assembled by templates or helpers, and
typed context structs built by Rust code (typebox-rs
`#[derive(Serialize)]` structs; the reader POC's `FnCtx` tree). The
second is what the POC validated, and the first is what F-3's
anti-patterns showed to be fragile (offset expression threading,
dynamic partial dispatch). A related boundary needed recording: where
semantic mapping (schema kind to target-language type, plan node to
statement kind) belongs.
## Decision
1. **Contexts are typed Rust structs**, `serde::Serialize` (usually
derived), built by Rust context builders. Free-form Value contexts
are not the convention; document trees are passed as values inside
typed contexts where needed.
2. **Kind mapping lives in builders, not templates or helpers.**
Mapping schema kinds to type strings, plan nodes to statement
kinds, and similar semantic decisions are Rust functions in the
context layer. Templates carry conditionals over context flags
only; helpers never interpret documents.
3. **Ordering is document order.** Context arrays and schema-derived
maps preserve insertion order (`preserve_order`); sorting happens
only where a template explicitly requires it.
4. **Plan semantics, document names** (for plan-driven targets): the
plan is the semantic source (offsets, strides, dispatch); the
source document supplies names and descriptive metadata, because
plans eagerly resolve `$ref`s and lose definition names (reader
POC finding F-6).
5. **Pure-cursor model for addressing contexts**: uniform cursor
semantics with no static/dynamic offset phases (reader POC finding
F-1).
## Consequences
- Positive: templates stay dumb and portable to other language targets
(phase-0 OQ-GN-04 hunch: contexts language-neutral, template sets
differ); strict mode catches builder/template drift at build time.
- Positive: builders own the semantic walk once; the interpreter and
compiler duplication (phase-0 OQ-GN-03) stays a clean split.
- Negative: context structs are code that must track alktype's public
plan API on upgrades; this is an ordinary dependency concern, and
was validated as such by the 0.3.0 to 0.4.0 POC migration needing no
context-builder changes.
- Negative: typed contexts are more Rust code per target than a
loose Value soup; accepted because the POC showed the alternative
is where template fragility concentrates.
## References
- [core.md](../core.md) (context conventions)
- Phase-0 OQ-GN-02 resolution and residues
(`docs/research/phase-0.md`); reader POC findings F-1, F-3, F-6
(`docs/research/reader-poc-summary.md`)
- [ADR-007](007-bast-codec-output-contract.md) (the plan-driven
target's output contract)
@@ -0,0 +1,56 @@
# ADR-005: Escape strategy is a per-registry policy
## Status
Accepted
## Context
handlebars applies HTML entity escaping to `{{expr}}` output by
default (handlebars 6.4: `register_escape_fn` sets a registry-wide
escaper; `{{& }}` and triple-brace skip escaping). For HTML that is
correct and safety-relevant. For generated Rust or WGSL text it is a
corruption vector: type names and string literals containing `&`, `<`,
or quotes (doc comments, literals with entities) would be silently
mangled into invalid code. Escaping policy therefore cannot be a
core-wide constant, and cannot be left to template authors choosing
between `{{ }}` and `{{& }}` ad hoc.
## Decision
The escaper is a property of the template set, set at registry
construction:
1. **ssr-html sets**: handlebars' default HTML escaper stays on as the
registry default. Template authors opt out per expression with
`{{& }}` only for deliberately-trusted markup (rare, reviewed like
source). This is a rendering-safety posture for user-supplied page
data, aligned with alkforge serving hostile traffic.
2. **Code target sets** (bast-rust-codecs, call-handlers, wgsl): the
registry registers a no-op escaper; templates use plain `{{ }}`
throughout. Codegen output is escaped at the source (literals are
quoted properly by the builder), never by entity mangling.
3. The core provides both policies; choosing one is part of defining
a template set (targets.md table).
## Consequences
- Positive: codegen correctness is by construction; SSR safety is by
construction. The one-registry-per-set rule (core.md) makes the
policy non-negotiable per set instead of per-template.
- Positive: matches handlebars' actual API shape (escaper is
registry-scoped), so no wrapper machinery.
- Negative: `{{& }}` in code sets becomes unnecessary and its use in
engine-owned code templates is forbidden by posture (it would
double-escape nothing but signals confusion); reviewers of template
changes watch for it.
- Negative: an HTML set with a no-op escaper would be a silent XSS
surface; mitigated because posture 1 is fixed by this ADR and
consumer sets (alkforge) build through the same documented policy.
## References
- [core.md](../core.md) (registry construction)
- [ADR-006](006-ssr-library-posture.md) (SSR consumer posture)
- [targets.md](../targets.md) (escaper column of the target summary)
- handlebars-rust registry API (vendored checkout, `register_escape_fn`)
@@ -0,0 +1,69 @@
# ADR-006: SSR: alkgen is an in-process rendering library for alkforge
## Status
Accepted
## Context
alkforge (family ADR-002, the gitea replacement) needs server-side
HTML generation before the codegen branches matter to it. Its
no-deformation rule routes the ask into alkgen's design rather than
around it. During the unpause discussion three candidate postures
existed for SSR rendering, and the choice shapes alkgen's public API
and dependency weight inside alkforge:
1. pre-rendered static assets (the dev-tool posture reused);
2. in-process library rendering, render-per-request;
3. in-process library rendering with consumer-side caching.
A separate quickjs-based SSR route (preact) was floated and rejected:
pure Rust rendering is faster and removes an interpreter from the
attack surface, which matters for a git-serving web product.
## Decision
1. **In-process library rendering** (posture B in emit.md): alkforge
links alkgen and renders HTML on demand. Static pre-rendering
remains possible later (same machinery, posture A), but the
design-time posture is in-process.
2. **Render-per-request vs cached rendering is alkforge's choice.**
alkgen guarantees pure, synchronous, deterministic rendering
(ADR-003), which is the property that makes consumer-side caching
and content hashing safe to build; alkgen provides no cache.
3. **Output units are fragments and full pages.** Partials make
fragments natural; pages compose fragments. The phase-0 fragment
exclusion was scoped to the code branches and does not apply here
(phase-0 OQ-GN-09).
4. **Render failures are typed errors** mapped by alkforge to its
error surface; no panics; no silently-blank regions (strict mode,
ADR-002).
5. **No DO-NOT-EDIT banner on rendered HTML**: it is the served
product, not a checked-in generated artifact. Determinism still
applies, and golden render tests pin pages in alkforge.
## Consequences
- Positive: no filesystem, no build step, and no staleness discipline
needed for SSR content; templates can change with alkforge's
builds safely against live data.
- Positive: the same API shape (build registry, render template) is
the dev-tool integration shape, so posture A and posture B share
the core's surface rather than forking it.
- Negative: rendering cost sits in the request path when uncached;
consumers who need zero request-path cost can still pre-render
(posture A) with the same sets.
- Negative: alkgen gains an always-deployed dependency weight in
alkforge; ADR-009 keeps that weight to the core by keeping alktype
out of SSR builds.
## References
- [emit.md](../emit.md) (posture B)
- [targets.md](../targets.md) (ssr-html)
- [ADR-001](001-one-core-template-set-targets.md),
[ADR-005](005-escape-strategy-per-template-set.md),
[ADR-008](008-template-set-ownership.md),
[ADR-009](009-feature-gated-template-sets.md)
- Phase-0 OQ-GN-09 (`docs/research/phase-0.md`); family ADR-002
(`/workspace/@alkdev/alkfamily/docs/architecture/decisions/002-alkforge-gitea-replacement.md`)
@@ -0,0 +1,78 @@
# ADR-007: BAST codec template set: typed direct codecs, zero-dependency output
## Status
Accepted
## Context
The reader POC (2026-09-08, re-validated on alktype 0.4.0 on
2026-11-06) answered the branch-1 output questions with executable
evidence:
1. A `Value`-materializing generated reader runs ~1.9x faster than the
alktype interpreter at identical output shape, and the decomposition
showed the celebrated ~20x interpreter-vs-handwritten gap is
`Value` allocation cost, not dispatch: hand-rolled code at the same
output shape is only ~10 to 20% faster.
2. A second render with typed output (plain structs, direct
cursor-based codecs) runs 291 to 322 ns: roughly 10x the Value shape
and 15 to 20x the interpreter, within ~4x of the no-allocation
hand-rolled floor (the residual is inherent String/Vec
allocation).
3. The typed direction was set at the 2026-09-08 debrief (russh-sftp
protocol-type shape), leaving the (a) serde-derive vs (b) direct
codec fork open. The debrief resolved it for (b) on speed, size,
and wasm grounds: direct codecs are zero-dependency (no serde, no
serde_json, no alktype in generated code), while serde-derive would
reintroduce a serde dep and wrapper-heavy handling of BAST's
per-field endianness and TUnion byte-discriminators.
## Decision
The bast-rust-codecs template set emits **typed direct codecs**:
1. Generated output is plain structs and enums with direct
`decode`/`encode` functions over a generated cursor core
(`fn decode(buf) -> Result<T, GenError>` shape). No `Value`
materialization, no serde machinery.
2. Generated code has **zero dependencies**; alktype appears only in
the generator. Serde bridges (or a serde feature) may be added
later if a consumer requires interop; starting dependency-free is
the irreversible-safe direction (adding serde is additive,
removing it is breaking).
3. Generated code is **self-contained** (no alktype runtime calls) and
**error-returning end to end**: bounds checks, UTF-8 validation,
and union dispatch limits return typed errors, never panic
(reader POC finding F-2; AGENTS.md convention 2; alkcall accepts
BAST from arbitrary peers). The generated error type is
generated-code-local; its exact shape (mirroring
`AlkTypeError::Access`'s variants without the alktype dependency)
is a branch-1 detailed-spec decision constrained by this ADR.
4. Addressing is the uniform cursor model; compile-time `fixed_size`
and dispatch tables from plans are emitted as data, and constant
cursor chains rely on LLVM folding rather than source offset
algebra (finding F-1).
## Consequences
- Positive: the success metric (recovering most of the ~20x gap at
equal output contract) is achieved at the typed contract, with
parity discipline proving semantic agreement byte-for-byte.
- Positive: zero-dep generated code keeps consumers light and the
wasm story simple.
- Negative: no serde interop out of the box; consumers needing
`serde`-compatible output wait for a bridge feature (accepted trade
recorded above).
- Negative: generated-code error type duplicates alktype error
semantics without alktype; parity and drift tests (three-way
benchmark discipline) are the standing mitigation.
## References
- [targets.md](../targets.md) (bast-rust-codecs)
- [ADR-004](004-typed-contexts-builders-not-templates.md) (contexts:
plans for semantics, document for names)
- `docs/research/reader-poc-summary.md` (executive summary, typed
output resolution, follow-ups 1-4; phase-0 OQ-GN-05/06 resolutions)
- `/workspace/russh-sftp/src/protocol/` (typed-structure precedent)
@@ -0,0 +1,60 @@
# ADR-008: Template set ownership: engine-owned sets ship in alkgen; consumer page templates live in the consumer
## Status
Accepted
## Context
A target is a template set, but who owns the assets? The codegen
branches made this obvious: the BAST codec templates are alkgen's
intellectual content (they encode the plan-to-code translation), and
consumers should not copy them. The SSR branch made it non-obvious:
alkforge's page templates are application UI. If alkgen ever became
a home for application layouts, it would drift into a web framework,
which it is not (overview.md).
## Decision
1. **Engine-owned template sets ship in alkgen** (behind features per
ADR-009): bast-rust-codecs now, call-handlers on its deferral path
(OQ-04), and any future set whose ownership resolves to
engine-owned. The wgsl set's ownership (alkjs vs alkgen) is
explicitly open under OQ-05; this ADR decides only where
engine-owned sets live when they exist. Their assets, builders,
and golden tests are alkgen's, versioned and pinned like source.
2. **Consumer-owned template sets live in the consumer**: alkforge's
page templates, partials, and helpers are alkforge assets
registered into a core-built registry via the alkgen API. alkgen
ships no application HTML assets in v1; whether generic layout
primitives ever join the engine is deliberately unresolved (OQ-07)
and starts empty.
3. **The registration API is the same machinery for both**: consumer
assets register through the same typed, strict, policy-bearing
construction path as engine sets (no second-class registry;
ADR-002).
## Consequences
- Positive: clear supply chain and pinning story (consumers upgrade
engine sets by dependency version; their own templates by git);
clear review boundaries (template edits touching parity-pinned
output are gated by the codegen templates' golden tests).
- Positive: alkgen does not accrete an application-layer UI
vocabulary.
- Negative: alkforge re-inventing layout structure that another
consumer might also want is possible; that is what OQ-07 tracks,
and the remedy path (file the ask upstream; ADR if shared assets
become real) is pre-decided.
- Negative: the registration API must be ergonomic enough that owning
templates externally is pleasant; a bad API here would push
consumers toward requesting in-crate placement for convenience.
The API shape is an implementation-time concern constrained by this
ADR's machinery-equality rule.
## References
- [targets.md](../targets.md) (owner column of the target summary)
- [core.md](../core.md) (registration machinery)
- [ADR-006](006-ssr-library-posture.md) (SSR consumer posture),
[ADR-009](009-feature-gated-template-sets.md) (feature gating)
@@ -0,0 +1,57 @@
# ADR-009: Template sets are feature-gated; default features ship the core only
## Status
Accepted
## Context
alkforge links alkgen for SSR (ADR-006). alktype is needed only by the
codec (and future handler) template sets and their context builders;
the SSR consumer inherits no BAST machinery. Family ADR-002's security
posture treats consumer dependency surface as a standing constraint:
pulling alktype into every alkgen consumer would widen cargo
supply-chain surface for no benefit to SSR builds. The symmetric
concern exists for the codegen sides only if the core itself grew
optional platform deps, which ADR-003 forbids.
## Decision
1. **Default features ship the core only**: registry construction,
render entry point, error model, escape policies. No engine-owned
template set, no alktype.
2. Engine-owned template sets are cargo features:
- `bast-codegen`: the BAST codec templates plus their context
builders; pulls `alktype`.
- Future engine-owned sets land as additional features (naming per
the set name; call-handlers may join `bast-codegen` or take its
own, decided when OQ-04 resolves; the wgsl direction tracks
OQ-05).
3. Consumer-owned sets (alkforge pages) need no feature: they ride the
default core, registering their own assets (ADR-008).
4. Current dependency state vs target state: `Cargo.toml` on main
lists alktype unconditionally today; moving alktype behind the
feature is an implementation task of phase 3, not a further
architecture decision.
## Consequences
- Positive: alkforge's dependency graph carries only the substrate
(handlebars, serde_json, thiserror, serde); alkcall consumers
explicitly opt into alktype, which matches whose problem it is.
- Positive: the wasm-clean story per feature stays testable
individually.
- Negative: feature-combination testing multiplies; mitigation is a
small fixed matrix in CI (core, core+bast-codegen, wasm build of
both), not exhaustive.
- Negative: a slight discoverability cost (codegen users must opt in);
acceptable because each consumer type is homogeneous.
## References
- [overview.md](../overview.md) (dependencies table)
- [ADR-006](006-ssr-library-posture.md) (SSR dependency weight),
[ADR-008](008-template-set-ownership.md) (ownership),
[ADR-003](003-core-posture-sync-wasm-deterministic.md) (why the
core carries no optional platform deps)
- Family ADR-002 decision 5 (security posture)
@@ -0,0 +1,56 @@
# ADR-010: alkgen is library-only; build-script integration is a documented pattern, no CLI
## Status
Accepted
## Context
Phase 0 demoted the dev-tool identity (ADR-001), and OQ-GN-01 resolved
the output unit as module-level files generated into a downstream
project. The reader POC validated the integration shape end-to-end as
a build script (`build.rs` calling schema plus templates plus context
builder, emitting into `OUT_DIR` or the source tree; finding F-4). An
open surface remained: whether alkgen should also ship a CLI
generator binary (a sibling to codegen CLIs in other template tooling).
## Decision
alkgen v1 is **library-only**. Posture-A consumers integrate by
calling the library from their build scripts; the build.rs pattern is
documented usage, not a binary. No CLI ships in v1.
Rationale for recording the negative:
1. Every current integration point is either in-process (alkforge,
posture B) or a build script on repo code (codec consumers,
posture A); a CLI adds packaging, argument, and workspace-layout
decisions the crate does not need to make yet.
2. The dev-tool identity is demoted; a CLI is the strongest form of
re-promoting it, and would pull workspace-layout questions
(where generated files land, how regeneration is triggered) into
alkgen that belong to consumers.
3. If a consumer wants a CLI later, the library API supports one by
composition, and adding a binary to the same crate is additive.
## Consequences
- Positive: no argument grammar, no output-path policy, no process
model to stabilize; the API surface stays a Rust surface.
- Positive: build scripts get exactly the schema-to-source-tree
semantics they need (the POC shape), including `OUT_DIR`
integration that a CLI cannot replicate cleanly.
- Negative: non-Rust or non-cargo consumers have no entry point; if
such a consumer ever appears (none exists in the family today),
that is the moment to revisit this decision via ADR.
- Negative: "documented pattern" means examples in docs; the pattern
must be maintained as the API evolves (a doc cost, not a design
cost).
## References
- [emit.md](../emit.md) (posture A integration shape)
- [ADR-001](001-one-core-template-set-targets.md) (dev-tool demotion),
[ADR-009](009-feature-gated-template-sets.md) (feature surface)
- Phase-0 OQ-GN-01; reader POC findings F-4 and the POC structure note
(`docs/research/reader-poc-summary.md`)
+131
View File
@@ -0,0 +1,131 @@
---
status: draft
last_updated: 2026-11-06
---
# alkgen Architecture: Emit Postures
## What and why
A template set renders text; an emit posture is how that text reaches
its consumer. The three postures correspond to the three real
consumers, and the differences between them are the honest answer to
"what differs per target": not the render machinery, but where the
text goes and what obligations follow.
ADR-001 demoted the dev-tool from "the product" to one posture among
three. This document specifies all three: posture A (build-time
static emit), posture B (in-process runtime render), and posture C
(render-to-compile feed, future).
## Posture A: build-time static emit
Used by the BAST codec template set and its future siblings
(call-protocol handlers; branch numbering per phase 0: branches 1 and
2).
- **Consumer**: a downstream project's build script. alkgen is a
generator dependency of the build, not a runtime dependency of the
generated output (ADR-007: generated code is zero-dependency;
phase-0 OQ-GN-06).
- **Output unit**: module-level files (phase-0 OQ-GN-01 resolution):
one document produces one or more self-contained modules dropped
into the consumer's source tree. Crate and fragment generation are
out for v1. The exact file partitioning policy is OQ-01 and the
regeneration posture is OQ-02.
- **Output hygiene, because files get checked in** (AGENTS.md
convention 8):
- every artifact starts with the "Auto-generated by alkgen.
DO NOT EDIT." banner;
- rendering is deterministic, so regeneration is diff-quiet:
same document, same templates, same options produce byte-identical
files;
- generated code is convention-conformant: no panics on input
(error-returning), no unsafe, no dependence on alktype.
- **Integration shape**: the build script calls the API directly
(documented pattern; the reader POC validated the build.rs shape
end-to-end, finding F-4). No CLI exists in v1 (ADR-010).
## Posture B: in-process runtime render
Used by SSR consumers, currently alkforge (ADR-006).
- **Consumer**: alkforge links alkgen as a library and renders HTML on
demand: per request, or cached by the consumer and re-rendered on
content change. The choice between eager cache and
render-per-request is alkforge's; determinism of rendering is what
makes consumer-side caching safe to build (ADR-003).
- **Output unit**: fragments and full pages. Partials make fragments
natural; pages compose fragments (ADR-006). The phase-0 fragment
exclusion (OQ-GN-01) was scoped to posture A and does not apply
here.
- **Output hygiene**: rendered HTML is the product, not a checked-in
generated artifact, so the DO-NOT-EDIT banner does not apply.
Determinism still applies (caching, hashing, and alkforge's render
tests all lean on it), and alkforge's page templates are pinned by
golden render tests in alkforge, per the templates-are-code
convention.
- **Assets**: alkforge page templates live in alkforge and register
into a core-built registry (ADR-008). alkgen ships no HTML assets
for posture B beyond the registry API itself; the open question
about generic layout primitives is OQ-07.
- **Errors**: render failure is a typed error mapped by the consumer
to its error surface (an alkforge error page). No panics; alkforge
serves hostile traffic (AGENTS.md convention 2).
- **Performance**: alkforge owns its latency budget; the architecture
obligation here is only the shape that keeps it achievable: pure,
cheap, repeated rendering with no hidden I/O. The reader POC's
numbers are research data for the codec target, not acceptance
thresholds for this posture; page-template budgets are set by
alkforge's web design when it exists.
## Posture C: render-to-compile feed
Future (branch 3, template-JIT; OQ-06). The render step is the JIT
step: alkgen renders shader (WGSL) text, and downstream compiles and
caches the compiled artifact, exactly the alkminer-v1
ShaderBuilder shape.
- Nothing new is required from the core today. The posture-critical
properties were fixed up front because retrofitting them is costly:
partials are first-class (core.md), rendering is pure, synchronous,
and deterministic (ADR-002, ADR-003), and output carries no banner
or file-system assumptions. ADR-010's library-only rule also applies:
the wasm-bound alkjs runtime uses the library, not a tool.
## Cross-posture obligations
| Obligation | A | B | C |
|---|---|---|---|
| Deterministic byte-identical output | yes | yes | yes |
| Typed errors, no panics | yes | yes | yes |
| DO-NOT-EDIT banner | yes | no | no |
| Golden render tests pin output | yes | yes (in owning crate) | yes |
| Validity pinning by template set | yes | yes | yes |
The last row is the general rule (ADR-001): each template set's render
tests pin what "valid output" means for it. The core cannot know and
must not pretend to: WGSL syntax, HTML well-formedness, and Rust
compilability are property tests owned by each set, not core concepts.
## Design Decisions
All design decisions are documented as ADRs in
[decisions/](decisions/).
| ADR | Decision | Summary |
|---|---|---|
| [003](decisions/003-core-posture-sync-wasm-deterministic.md) | Sync, wasm-clean, deterministic | Underwrites caching and hashing in every posture |
| [006](decisions/006-ssr-library-posture.md) | In-process SSR library | Posture B's shape for alkforge |
| [007](decisions/007-bast-codec-output-contract.md) | Typed direct codecs | Posture A's generated-code contract |
| [010](decisions/010-library-only-no-cli.md) | Library-only v1 | Build-script integration, no CLI |
## Open Questions
Open questions are tracked in [open-questions.md](open-questions.md).
Key questions affecting this document:
- **OQ-01**: file partitioning policy for posture A artifacts (open)
- **OQ-02**: regeneration posture for posture A artifacts: wholesale
overwrite vs partial regeneration (open)
- **OQ-06**: template-JIT core requirements (deferred(scope))
+158
View File
@@ -0,0 +1,158 @@
---
status: draft
last_updated: 2026-11-06
---
# alkgen Architecture: Open Questions
Centralized tracker. Phase-0 questions carried forward note their
legacy IDs (`OQ-GN-NN` in `docs/research/phase-0.md`) so traceability
survives the renumbering. Spec documents reference OQs by number.
**Status taxonomy**: `open` marks a question that is decidable now and
resolves when its owning spec or decision is written (typically a
phase-1/branch-detail artifact). `deferred(scope)` marks one waiting
on an external condition (a consumer, a crate spec); each follows the
two-half convention from `docs/sdd_process.md`, naming its unblocking
condition here and pairing with an external-trigger tracker task under
`tasks/architecture/`. Re-triage between the two statuses is an
architect action, not a default.
## Theme: Emit (code branches)
### OQ-01: File partitioning policy for posture-A artifacts
- **Origin**: emit.md (phase-0 OQ-GN-01 residue)
- **Status**: open
- **Priority**: medium
- **Impacts**: blocks the branch-1 detailed spec (the emit layer's
file layout section) and the consumer-facing regeneration story.
- **Resolution**: Not yet decided. One file per document vs per-type
files, and where the reader/writer/validator split lands, interacts
with module size and the consumer's `include` layout. Decided when
the branch-1 detailed spec is written.
- **Cross-references**: emit.md posture A, ADR-007
### OQ-02: Regeneration posture for posture-A artifacts
- **Origin**: emit.md (phase-0 OQ-GN-01 residue)
- **Status**: open
- **Priority**: low
- **Impacts**: blocks only the final shape of OQ-01's answer (regen
follows partitioning); nothing else.
- **Resolution**: Not yet decided. Wholesale overwrite is the likely
landing (determinism makes it diff-quiet); partial regeneration
would only matter if consumers hand-edit generated files, which the
DO-NOT-EDIT banner forbids. Resolve together with OQ-01.
- **Cross-references**: OQ-01
## Theme: Generated validation
### OQ-03: Full BAST validation strategy for generated validators
- **Origin**: targets.md (phase-0 OQ-GN-07; reader POC follow-up 5)
- **Status**: open
- **Priority**: medium
- **Impacts**: blocks the validator template arm set and settles
whether generated code carries a jsonschema dependency (an
ADR-007 surface, since ADR-007 records zero dependencies except
serde bridges).
- **Resolution**: Materially informed, not closed. Read-path
validation (bounds checks, UTF-8, union dispatch limits) is proven
generatable as error-returning code with no jsonschema dependency
(reader POC). The open half: do generated validators re-express
`ValidationPlan` semantic checks in template-generated code (drift
risk, mitigated by the parity discipline) or call into jsonschema
(heavyweight dependency in generated code)? Decided in the branch-1
detailed spec.
- **Cross-references**: targets.md bast-rust-codecs, ADR-007
## Theme: Future targets
### OQ-04: call-handlers template set scope
- **Origin**: targets.md (phase-0 branch 2)
- **Status**: deferred(scope)
- **Door type**: two-way
- **Priority**: medium
- **Impacts**: blocks nothing currently active; the branch-2 consumers
arrive after branch 1. Adds a template set and possibly an input
path (JSON Schema/OpenAPI) beyond BAST when it activates.
- **Blocked on**: branch-1 convergence plus a concrete consumer ask
naming the protocol surfaces to generate (tracker task
`architecture/oq-04-call-handlers-scope`).
- **Resolution**: Not yet decidable. The adapter precedent
(`alkhttp/src/adapters`) frames the shape; no concrete protocol
use cases exist to design against.
- **Cross-references**: targets.md call-handlers, ADR-001, ADR-009
### OQ-05: wgsl template set requirements
- **Origin**: targets.md (phase-0 branch 3; alkjs codegen need)
- **Status**: deferred(scope)
- **Door type**: two-way
- **Priority**: medium
- **Impacts**: blocks the alkjs WGSL codegen path only; nothing in v1.
Template-set ownership (alkjs vs alkgen), feature name, and context
shape are all downstream of the alkjs crate spec.
- **Blocked on**: alkjs crate spec naming the shaders to generate and
its wgpu pipeline shape (tracker task
`architecture/oq-05-wgsl-requirements`).
- **Resolution**: Not yet decidable. alkminer-v1's ShaderBuilder is
the structural precedent; the requirements cannot be read from it
alone.
- **Cross-references**: targets.md wgsl-jit, OQ-06
### OQ-06: template-JIT core requirements
- **Origin**: targets.md (phase-0 branch 3, JIT core)
- **Status**: deferred(scope)
- **Door type**: two-way
- **Priority**: low
- **Impacts**: blocks the render-then-compile cache concept (branch 3)
only. Everything v1 needed from it is already decided and built in:
first-class partials, pure sync deterministic rendering (ADR-002,
ADR-003), wasm-clean (ADR-003).
- **Blocked on**: concrete alkjs runtime integration (wgpu pipeline
caching needs) plus a measured motivation that the v1
render-entry-point shape obstructs JIT use (tracker task
`architecture/oq-06-jit-core-requirements`).
- **Resolution**: Not yet decidable. A far-timeline consumer; v1's
posture is designed so that no retroactive core change is expected.
- **Cross-references**: targets.md wgsl-jit, emit.md posture C,
ADR-002, ADR-003
## Theme: Shared core
### OQ-07: Generic HTML layout primitives in alkgen
- **Origin**: emit.md posture B, targets.md ssr-html
- **Status**: deferred(scope)
- **Door type**: two-way
- **Priority**: low
- **Impacts**: blocks nothing in v1; only relevant if a demonstrated
shared need emerges across SSR consumers.
- **Blocked on**: alkforge's SSR experience demonstrating a concrete
shared need for engine-owned layout primitives (tracker task
`architecture/oq-07-html-primitives`).
- **Resolution**: Not yet decidable. ADR-008 puts all page templates
in the consumer; whether a shared need for alkgen-owned generic
primitives (base layouts, form partials) exists is exactly what the
consumer experience would show. If it arrives, alkforge files it
upstream (family ADR-002 no-deformation rule) and the decision
becomes an ADR plus a new engine-owned feature.
- **Cross-references**: targets.md ssr-html, ADR-006, ADR-008
### OQ-08: Target-language scope for v1 template sets
- **Origin**: phase-0 OQ-GN-04; dispositioned by adoption in Phase 1
- **Status**: resolved (2026-11-06, by Phase-1 adoption)
- **Priority**: n/a (resolved)
- **Resolution**: The core is target-language-agnostic by
construction (ADR-001, ADR-004: contexts are language-neutral,
kind mapping lives in builders), and v1 template sets are written per consumer need with no
second-language template work speculatively: ssr-html (HTML) first,
bast-rust-codecs (Rust) second, wgsl-jit deferred (OQ-05). The
SSR branch arriving with no core change is the hunch's predicted
data point. Revisiting costs are low while ADR-004 holds.
- **Cross-references**: ADR-001, ADR-004, OQ-05
+161
View File
@@ -0,0 +1,161 @@
---
status: draft
last_updated: 2026-11-06
---
# alkgen Architecture: Overview
## Purpose
alkgen is the text-generation engine of the alk* stack. The core takes
typed contexts, templates, and partials and produces text. Targets
differ only in the template set, the context shape, and the posture by
which the text is consumed. Everything underneath that is shared, and
the shared part is what this architecture specifies.
This framing is a decision, not a description of intent. Phase 0 began
with "codegen over BAST documents" and generalized through three real
consumers until the general claim held: HTML is handlebars' native
case, Rust codecs were validated end-to-end by the reader POC, and
WGSL is proven by alkminer-v1's ShaderBuilder (three data points, one
shape). ADR-001 records the decision and its constraint: shared core
types must not bake reader/writer, HTML, or WGSL assumptions in.
## The consumers, one shape
| Consumer | Output text | When |
|---|---|---|
| alkforge (family ADR-002) | HTML fragments and pages, rendered in-process | first, active now |
| alkcall channels, via BAST codec templates | Rust reader/writer/validator modules, emitted at build time | next; POC-validated |
| alkjs | WGSL shader text feeding a render-then-compile cache | far out; shapes core requirements today |
A fourth consumer class (call-protocol handlers, the static counterpart
of alkhttp's `from_openapi` / `from_jsonschema` adapters) is deferred
until the codec templates converge; see targets.md and OQ-04.
The queue order is alkforge's need, not a preference: the SSR branch is
the simplest consumer and the generality test. If the core renders
HTML cleanly with no target-specific exceptions, the codegen paths are
not overfitted.
## Engine model
```
inputs: BAST document, or page data
│
▼
context builder template set
(Rust code; typed (templates, partials,
contexts out) helpers, escaper)
│ │
▼ ▼
contexts ───────────────► registry
│
▼
render (sync, deterministic)
│
▼
text artifacts (one per template kind)
│
▼
emit posture (see emit.md)
```
The vertical layers in this diagram are the crate's three concerns
(two terms from sibling crates appear in the pipeline: a BAST document
is alktype's binary abstract syntax tree document model, the wire
schema format of the alk* stack; and plans are alktype's runtime
walk structures over such documents, from which offset, size, and
dispatch semantics are read out):
1. **Shared core** (spec: [core.md](core.md)): registry construction
(strict mode, first-class partials, per-registry escape policy,
where an escaper is the function handlebars applies to
interpolated output), context conventions, a deterministic
synchronous render entry point, and the typed error model. All
wasm-clean.
2. **Template sets** (spec: [targets.md](targets.md)): the per-target
assets and their context shapes. Engine-owned sets ship in alkgen
behind features (ADR-008, ADR-009); consumer-owned sets (alkforge
page templates) live in the consumer.
3. **Emit postures** (spec: [emit.md](emit.md)): how rendered text
reaches its consumer: build-time static files, in-process runtime
render, or a render-to-compile feed.
Context building straddles 1 and 2: the conventions (typed structs,
document order, mapping logic in Rust) are core; the actual context
shapes are per-target.
## What alkgen is not
- Not an interpreter. alktype walks BAST documents at runtime and owns
dynamic-schema semantics; alkgen is the compile-time counterpart for
the static case (phase-0 OQ-GN-03). The two are complements, and the
measured interpreter-vs-generated gap is codegen's value: roughly
1.9x at the `Value` output contract, 15 to 20x at the typed contract
the POC validated.
- Not a validator. Generated validators are a branch-1
(bast-rust-codecs) template-set concern (OQ-03); the core has no
validation vocabulary.
- Not a web framework. SSR consumers get rendering; routing, headers,
lifecycle, and caching are theirs (ADR-006).
- Not a CLI. v1 is library-only; build scripts integrate by documented
pattern (ADR-010).
## Dependencies
| Dependency | Role | Notes |
|---|---|---|
| handlebars 6.4 | render substrate | strict mode, partials, per-registry escaper; pinned to `6.4` from crates.io; the vendored `/workspace/handlebars-rust` checkout is reading material and an emergency patch source only |
| serde, serde_json | context serialization, schema documents | `preserve_order` is load-bearing: schema-JSON key order determines packed byte order downstream |
| alktype 0.4 | BAST document model and plans | used by codec template sets; feature-gated (ADR-009) so SSR consumers do not pull it |
| thiserror 2 | typed errors | convention 2 posture |
Wasm-cleanliness is validated, not assumed: the skeleton (the exact dep
set above) compiles to `wasm32-unknown-unknown` release, re-verified
2026-11-06, and handlebars-rust ships its own wasm playground. The
optional handlebars features `dir_source` and `script_helper` are
off-limits in lib code (filesystem access, rhai scripting).
## Inherited constraints
The crate conventions in `AGENTS.md` bind all architecture work: no
comments in code, thiserror-only error handling, no async, wasm-clean,
preserve_order, strict template mode, templates as pinned code assets,
generated-code hygiene, standard naming, one module per file, zero
unsafe, no em dashes in prose. Where a convention is load-bearing for
the architecture, the relevant spec references it explicitly (strict
mode and error posture in core.md; banner and determinism in emit.md).
The family security posture (family ADR-002 decision 5) applies
downstream-facing constraints here: minimal dependency surface for
consumers (ADR-009 exists for this), deterministic output safe to
cache and hash, and typed errors instead of panics on hostile or
malformed input, since alkcall accepts BAST from arbitrary peers.
## Design Decisions
All design decisions are documented as ADRs in
[decisions/](decisions/).
| ADR | Decision | Summary |
|---|---|---|
| [001](decisions/001-one-core-template-set-targets.md) | One core, targets as template sets | The generalized text engine; no target assumptions in core types |
| [002](decisions/002-handlebars-substrate-strict-mode.md) | handlebars 6.4, strict mode, embedded templates | Substrate and registration posture |
| [003](decisions/003-core-posture-sync-wasm-deterministic.md) | Sync, wasm-clean, deterministic | Core platform posture |
| [004](decisions/004-typed-contexts-builders-not-templates.md) | Typed contexts, mapping in builders | Context conventions |
| [005](decisions/005-escape-strategy-per-template-set.md) | Escaper per registry | HTML-escape for SSR sets, no-op for code sets |
| [006](decisions/006-ssr-library-posture.md) | In-process SSR library | alkforge links alkgen; fragments and pages |
| [007](decisions/007-bast-codec-output-contract.md) | Typed direct codecs | Branch-1 output contract from the POC debrief |
| [008](decisions/008-template-set-ownership.md) | Template set ownership | Engine-owned sets in-crate; consumer page templates outside |
| [009](decisions/009-feature-gated-template-sets.md) | Feature-gated template sets | Default features ship core only |
| [010](decisions/010-library-only-no-cli.md) | Library-only v1 | Build-script integration pattern, no CLI |
## Open Questions
Open questions are tracked in [open-questions.md](open-questions.md).
The ones shaping this overview's scope:
- **OQ-04**: call-protocol handler template set scope (deferred(scope))
- **OQ-05**: WGSL template set requirements (deferred(scope))
- **OQ-06**: template-JIT core requirements (deferred(scope))
+146
View File
@@ -0,0 +1,146 @@
---
status: draft
last_updated: 2026-11-06
---
# alkgen Architecture: Targets
## What a target is
A target is a concrete instantiation of the engine (ADR-001): a
template set (templates, partials, helper registrations, escaper
choice), a context shape, an emit posture, and a validity-pinning
strategy. The core knows nothing about targets; this document is the
registry of instances and the place each target's remaining
decisions are named.
Branch numbering follows phase-0 conventions (branch 0 = SSR addition
from the 2026-11-06 unpause; 1, 2, 3 historical). The table gives the
mapping; everything else in this doc uses names.
| Branch | Target | Status |
|---|---|---|
| 0 | ssr-html | active, first consumer |
| 1 | bast-rust-codecs | POC-validated; template set not yet in-crate |
| 2 | call-handlers | deferred(scope) |
| 3 | wgsl-jit | deferred(scope) |
## Target summary
| Target | Template set owner | Escaper (ADR-005) | Context shape | Emit posture | Feature (ADR-009) |
|---|---|---|---|---|---|
| ssr-html | consumer (alkforge) | HTML | page/data views | B (in-process render) | none (core) |
| bast-rust-codecs | alkgen | none (no-op) | plan projection + document names | A (build-time emit) | `bast-codegen` |
| call-handlers | alkgen | none (no-op) | deferred (OQ-04) | A | `bast-codegen` or own |
| wgsl-jit | alkjs or alkgen, open (OQ-05) | none (no-op) | shader contexts | C (render-to-compile) | open (OQ-05) |
## ssr-html
alkforge's server-side web view. The engine's first consumer and the
generality test: if this target needs no core exceptions, the core is
not overfitted to codegen (ADR-001, ADR-006).
- **Posture**: alkforge links alkgen; pages render in-process, on
demand or cached by alkforge (ADR-006).
- **Contexts**: page/data views defined by alkforge; no layout math,
no plan projection. The simplest context shape of the three, which
is the point.
- **Escaper**: handlebars' HTML escaping as the registry default
(ADR-005).
- **Validity pinning**: alkforge pins page and fragment renders with
golden tests in alkforge; rendered HTML is served, not checked in
as generated code, so no banner (emit.md posture B).
- **Ownership**: all page templates, partials, and helpers live in
alkforge and register through the core API (ADR-008). Whether
alkgen should ever ship generic HTML layout primitives is OQ-07.
- **Remaining decisions**: minimal; this target is where
decomposition into implementation tasks starts when alkforge's web
view design names its pages.
## bast-rust-codecs
Binary reader/writer/validator modules generated from alktype BAST
documents for alkcall channels consumers. The branch the reader POC
validated end-to-end: plan-driven contexts, parity-exact generated
code, typed output at 291 to 322 ns versus the interpreter's 5.4 us
(re-validated 2026-11-06 on alktype 0.4.0).
- **Posture**: build-time emit into the consumer's source tree
(emit.md posture A); build-script integration (ADR-010).
- **Contexts**: plan-driven projection (ADR-004). The plan is the
semantic source (offsets, strides, dispatch tables); the source
document is the name source, since plans eagerly resolve `$ref`s and
drop def names (phase-0 OQ-GN-02 refined by reader POC finding
F-6).
- **Output contract**: typed direct codecs over plain structs, zero
dependencies in generated code, self-contained (no alktype runtime
calls), pure cursor addressing, error-returning end to end (ADR-007;
reader POC findings F-1, F-2, F-6; phase-0 OQ-GN-05/06 resolutions).
- **Escaper**: no-op; generated Rust must not be HTML-escaped
(ADR-005).
- **Validity pinning**: parity discipline against the alktype
interpreter plus compilation of the generated modules; template
inventory enumerates the full kind arm surface (records,
field-disc unions, offset-indirect, big-endian, maxLength
reservations, writers; reader POC follow-ups 3 and 4; phase-0
OQ-GN-05 POC coverage).
- **Remaining phase-1 residue, not yet specced**: the exact
plan-to-context projection (which plan structures become which
context shapes), generated-code error type shape, writer-side plan
survey, and full-validation strategy (OQ-03). These are the branch's
detailed spec, to be written when this target activates; the
decisions above are the constraints it cannot violate.
## call-handlers
The static counterpart of alkhttp's `from_openapi` /
`from_jsonschema` adapters: generated handler tables and types from
schema documents (BAST or JSON Schema/OpenAPI inputs).
Status: deferred(scope). The consumer asks that would pin its shape
(the specific protocol surfaces to generate against) have not arrived;
branch 1 converges first. See OQ-04. No template set, context shape,
or feature decision is made here beyond the constraint that the core
must not assume it (ADR-001).
## wgsl-jit
WGSL generation for alkjs, the quickjs-based wgpu runtime, feeding a
render-then-compile cache: the alkminer-v1 ShaderBuilder shape.
Status: deferred(scope). The alkjs crate spec does not exist yet, so
the template set's requirements (which shaders, which parameters,
which compile pipeline) cannot be read. Everything v1 needs from this
branch has been decided instead: partials first-class, pure sync
deterministic rendering (ADR-002, ADR-003), and wasm-clean so the
render step itself can run inside the wasm-bound runtime (phase-0
"Core generalization steer"). See OQ-05 and OQ-06.
## Design Decisions
All design decisions are documented as ADRs in
[decisions/](decisions/).
| ADR | Decision | Summary |
|---|---|---|
| [001](decisions/001-one-core-template-set-targets.md) | One core, targets as template sets | The instantiation model |
| [005](decisions/005-escape-strategy-per-template-set.md) | Escaper per registry | HTML for ssr-html; no-op for code sets |
| [006](decisions/006-ssr-library-posture.md) | In-process SSR library | ssr-html posture |
| [007](decisions/007-bast-codec-output-contract.md) | Typed direct codecs, zero-dep output | bast-rust-codecs output contract |
| [008](decisions/008-template-set-ownership.md) | Template set ownership | alkforge owns its page templates; codec set ships in-crate |
| [009](decisions/009-feature-gated-template-sets.md) | Feature-gated template sets | alktype and engine-owned sets behind features |
## Open Questions
Open questions are tracked in [open-questions.md](open-questions.md).
Key questions affecting this document:
- **OQ-01**: posture-A file partitioning policy (open)
- **OQ-03**: full BAST validation strategy for generated validators
(open)
- **OQ-04**: call-handlers scope (deferred(scope))
- **OQ-05**: wgsl template set requirements (deferred(scope))
- **OQ-06**: template-JIT core requirements (deferred(scope))
- **OQ-07**: generic HTML layout primitives in alkgen
(deferred(scope))
- **OQ-08**: target-language scope for v1 template sets (resolved)
+7 -3
View File
@@ -559,9 +559,13 @@ is a place the compiled path stays interpreter-shaped.
refined by the POC (names from BAST, typed output resolved to
direct codecs).
Residue: exact plan→context projection (Phase 1 spec task)
- [ ] OQ-GN-04 (language scope): low-stakes; core language-agnostic,
Rust-only code templates for v1 (half-hunch; the SSR branch's
HTML template set is an in-family data point the hunch predicts)
- [x] OQ-GN-04 (language scope): dispositioned 2026-11-06 at Phase 1
start: core language-agnostic by construction (architecture
ADR-001/ADR-004), template sets written per consumer need (SSR
HTML first, Rust codecs second, WGSL deferred); tracker entry
OQ-08 in `docs/architecture/open-questions.md` records the
adoption. The SSR branch arriving with no core change is the
hunch's predicted data point
- [x] OQ-GN-05/06 (delegation boundary, dependency shape): resolved
2026-09-08 by reader POC: self-contained generated code,
serde_json-only dependency for the Value contract, zero-dep for
@@ -0,0 +1,42 @@
---
id: architecture/oq-04-call-handlers-scope
name: "External trigger: call-handlers consumer ask arrives"
status: pending
depends_on: []
scope: single
risk: trivial
impact: isolated
level: research
---
## Description
[external-trigger, deferred-oq] Tracker for OQ-04 (call-handlers
template set scope, `deferred(scope)` in
`docs/architecture/open-questions.md`). This task is not actionable
work; it exists so the task graph can represent the unblocking
condition. Mark `completed` when branch 1 converges and a concrete
consumer ask names the protocol surfaces to generate; then re-open
OQ-04 (or resolve it directly if no architecture decision remains).
## Acceptance Criteria
- [ ] Branch 1 (bast-rust-codecs) has converged (template set
in-crate, golden tests pinned)
- [ ] A consumer ask exists naming concrete protocol surfaces to
generate (the alkhttp adapter counterpart need)
- [ ] OQ-04 transitioned out of `deferred(scope)`
## References
- docs/architecture/open-questions.md (OQ-04)
- docs/architecture/targets.md (call-handlers)
- docs/sdd_process.md, "Deferred OQs and the blocker-task half"
## Notes
> Agent fills this during implementation.
## Summary
> Agent fills this on completion.
@@ -0,0 +1,41 @@
---
id: architecture/oq-05-wgsl-requirements
name: "External trigger: alkjs crate spec arrives"
status: pending
depends_on: []
scope: single
risk: trivial
impact: isolated
level: research
---
## Description
[external-trigger, deferred-oq] Tracker for OQ-05 (wgsl template set
requirements, `deferred(scope)` in `docs/architecture/open-questions.md`).
This task is not actionable work. Mark `completed` when the alkjs
crate spec exists and names the shaders to generate and its wgpu
pipeline shape; then re-open OQ-05 (or resolve directly).
## Acceptance Criteria
- [ ] alkjs crate spec exists
- [ ] Shader set and pipeline shape named (the codegen requirements
can be read from the spec)
- [ ] OQ-05 transitioned out of `deferred(scope)`
## References
- docs/architecture/open-questions.md (OQ-05)
- docs/architecture/targets.md (wgsl-jit)
- /workspace/@alkminer/reference/alkminer-v1/src/compute/kernel.rs
(ShaderBuilder precedent)
- docs/sdd_process.md, "Deferred OQs and the blocker-task half"
## Notes
> Agent fills this during implementation.
## Summary
> Agent fills this on completion.
@@ -0,0 +1,42 @@
---
id: architecture/oq-06-jit-core-requirements
name: "External trigger: template-JIT need materializes with measured motivation"
status: pending
depends_on: []
scope: single
risk: trivial
impact: isolated
level: research
---
## Description
[external-trigger, deferred-oq] Tracker for OQ-06 (template-JIT core
requirements, `deferred(scope)` in `docs/architecture/open-questions.md`).
This task is not actionable work. Mark `completed` when the alkjs
wgpu integration exists and a measured motivation shows the v1 render
entry point shape obstructs render-then-compile caching; then re-open
OQ-06 (or resolve directly).
## Acceptance Criteria
- [ ] alkjs wgpu runtime integration exists (OQ-05 context available)
- [ ] A measured motivation recorded (what the v1 shape obstructs)
- [ ] OQ-06 transitioned out of `deferred(scope)`
## References
- docs/architecture/open-questions.md (OQ-06)
- docs/architecture/emit.md (posture C)
- docs/architecture/decisions/002-handlebars-substrate-strict-mode.md,
decisions/003-core-posture-sync-wasm-deterministic.md (the v1
properties that already satisfy posture C)
- docs/sdd_process.md, "Deferred OQs and the blocker-task half"
## Notes
> Agent fills this during implementation.
## Summary
> Agent fills this on completion.
@@ -0,0 +1,41 @@
---
id: architecture/oq-07-html-primitives
name: "External trigger: SSR shared need demonstrated"
status: pending
depends_on: []
scope: single
risk: trivial
impact: isolated
level: research
---
## Description
[external-trigger, deferred-oq] Tracker for OQ-07 (generic HTML layout
primitives in alkgen, `deferred(scope)` in
`docs/architecture/open-questions.md`). This task is not actionable
work. Mark `completed` when alkforge's SSR experience demonstrates a
concrete shared need for engine-owned layout primitives (base
layouts, form partials); then re-open OQ-07, which becomes an ADR plus
a new engine-owned feature.
## Acceptance Criteria
- [ ] alkforge SSR is running with its own page templates (ADR-008)
- [ ] A demonstrated shared need recorded (what two or more consumers
or pages would reuse from engine-owned primitives)
- [ ] OQ-07 re-opened or resolved
## References
- docs/architecture/open-questions.md (OQ-07)
- docs/architecture/decisions/008-template-set-ownership.md
- docs/sdd_process.md, "Deferred OQs and the blocker-task half"
## Notes
> Agent fills this during implementation.
## Summary
> Agent fills this on completion.