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:
1 parent
93d70253d7
commit
a91ea2ec89
22 files changed
+1685
-15
No files matched your search
@@ -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)
|
||||
|
||||
|
||||
@@ -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/`).
|
||||
@@ -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`)
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user