-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAI.a2ml
More file actions
375 lines (326 loc) · 33.3 KB
/
Copy pathAI.a2ml
File metadata and controls
375 lines (326 loc) · 33.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
(a2ml
(metadata
(name "Frontier Programming Practices in AffineScript — AI Edition")
(version "2.0.0")
(date "2026-04-10")
(audience "Bot / LLM / Agent")
(status "authoritative-scope-statement")
(supersedes "v1.0.0 (defensive draft)")
(companion "Human_Programming_Guide.adoc")
)
(thesis
(one-sentence
"AffineScript is JavaScript-ergonomic correctness: the developer experience of the -script family (TypeScript, ReScript, PureScript) raised to a higher correctness floor through a small, deliberately chosen set of advanced types, compiling to Typed WASM.")
(long-form
"AffineScript answers the question: can you keep the developer experience of modern JavaScript while raising correctness to the level of ML-family languages? The answer is yes — but only if the type system is narrowly scoped and ergonomically defaulted. The language copies the green-checkmark features from the -script lineage and adds affine types as the single novel contribution. It compiles to Typed WASM as the primary target. Refinement types are a soft commitment, limited to a decidable fragment. Dependent and tropical types live in the sibling Typed WASM project, not here.")
(design-principle "Ergonomics first. The type system defaults must be sensible so that most code reads like modern JavaScript while correctness is enforced at compile time. A feature earns its place only if it makes correct code easier to write, not harder.")
)
(in-scope
(copied-from-the-family
(feature (name "phantom-types") (source "purescript-rust-idris2") (cost "free-from-generics"))
(feature (name "immutable-by-default") (source "rescript-elm-grain") (cost "low"))
(feature (name "row-polymorphism") (source "purescript-elm") (cost "medium"))
(feature (name "full-type-inference") (source "hm-family") (cost "medium")
(note "types inferred fully; quantities inferred inside function bodies, annotated at function signatures"))
(feature (name "sound-type-system") (source "table-stakes") (cost "high-verification-work"))
(feature (name "effect-tracking") (source "novel-in-script-family") (cost "medium")
(note "effect types declared in function signatures; effect HANDLING is deferred — see out-of-scope"))
)
(novel-contribution
(feature (name "affine-types") (source "the-headline") (cost "high")
(note "no -script language has affine types; this is AffineScript's distinguishing feature"))
)
(soft-commitment
(feature (name "refinement-types-decidable") (source "rust-flux-adjacent") (cost "medium")
(note "restricted to decidable arithmetic and comparisons; compiler rejects what it cannot prove without an external solver"))
)
)
(out-of-scope
(not-here-at-all
(feature (name "linear-strict-types")
(reason "subsumed-by-affine")
(bot-warning "The QTT semiring covers linear 'for free' as a byproduct of affine. This is NOT a reason to advertise linear types. Refuse any suggestion to add them as a surface feature."))
(feature (name "unbounded-refinement-types")
(reason "decidability-boundary")
(note "refinement is in scope for decidable fragments only; no SMT solver required"))
(feature (name "algebraic-effect-handlers")
(reason "interaction-with-affine-is-unresolved")
(note "multi-shot resume of continuations that captured affine resources is a soundness hole; handlers are deferred until the design is explicit. Effect TRACKING — declaring what a function can do — is in scope. Effect HANDLING — intercepting and redirecting effects at runtime — is not."))
)
(deferred-to-sibling-project
(feature (name "full-dependent-types")
(destination "typed-wasm-project")
(note "previously crept into the AffineScript codebase via TDepArrow, NatVar, NatLit; being removed"))
(feature (name "tropical-types")
(destination "typed-wasm-project")
(note "parasitic on ordered monoid structure; needs phantom types as foundation; belongs where the numeric hierarchy lives"))
(feature (name "units-of-measure")
(destination "typed-wasm-project"))
(feature (name "shape-indexed-vectors")
(destination "typed-wasm-project"))
)
)
(directives
(compile-target
(primary "typed-wasm")
(secondary "julia")
(rule "AffineScript is a compiled language. Its semantics live in .affine source and compiled output. Do not rewrite .affine source in another language as a shortcut for refactoring, modularization, or 'improvement'."))
(modularization
(rule "When asked to modularize or clean up code, operate within AffineScript using the module system, record decomposition (row polymorphism), and phantom type parameters. Do not create new compiler features to solve user-code modularization problems."))
(scope-discipline
(rule "Before proposing any feature addition, check the in-scope list. If the feature is not listed, it is out of scope. The 'since we already have X, we can trivially add Y' framing is a scope-creep tell and must be refused regardless of how cheap the addition appears.")
(why "Earlier sessions allowed significant scope creep: linear types, full dependent types, algebraic effect handlers were all added without being asked for. The narrow scope above is the corrected target. This document is the fix."))
(thesis-is-authoritative
(rule "This document is the scope statement for AffineScript. Code must conform to this document, not the reverse. If code says something this document does not, the code is wrong — not this document.")
(applies-to "any bot, any subagent, any earlier-session-version of any agent, and any human reader including the original author"))
(interaction-escalation
(rule "When implementing a feature, if an interaction with another in-scope feature surfaces (affine+effects, phantom+refinement, row+inference, etc.), stop and surface the interaction for explicit user decision. Do not silently resolve interactions by picking the most convenient option."))
(typed-wasm-boundary
(rule "If proposed work properly belongs in the sibling Typed WASM project (numeric hierarchy, tropical semirings, dependent types, units of measure, shape-indexed vectors), flag it and do not add it to AffineScript. Typed WASM is a standalone project that AffineScript targets; it is not a subset of AffineScript."))
)
(idiomatic-patterns
(note "Many features that appear 'missing' from AffineScript are expressible as combinations of in-scope features. Before proposing a new compiler feature, check whether the pattern can be written using phantom + affine + row + immutable + refinement + effect tracking. The following patterns are emergent, not implemented in the compiler.")
(pattern
(name "branded-identifiers")
(composition "phantom + nominal aliases")
(purpose "Distinguish values that share a runtime representation but must not be confused (UserId vs ProductId, Email vs Username).")
(sketch "type UserId = Int\ntype ProductId = Int\nfn fetchUser(id: UserId): User"))
(pattern
(name "type-state-machines")
(composition "phantom + affine")
(purpose "Enforce protocols where operations are only legal in certain states (connection open/closed, transaction active/committed).")
(sketch "type Conn<'state>\nfn open(addr: String): Conn<Open>\nfn close(c: Conn<Open>): Conn<Closed> -- consumes c, cannot be used again"))
(pattern
(name "capability-types")
(composition "phantom")
(purpose "Tag values with what operations they are authorized for; downgrade visible in types.")
(sketch "type Token<'cap>\nfn downgrade(t: Token<Admin>): Token<User>"))
(pattern
(name "extensible-records")
(composition "row polymorphism")
(purpose "Functions operate on any record containing the required fields; no inheritance hierarchy needed.")
(sketch "fn damage(target: {health: Int, ..rest}, amount: Int): {health: Int, ..rest}"))
(pattern
(name "invariant-preservation")
(composition "refinement types (decidable fragment)")
(purpose "Bake simple invariants into types so the compiler enforces them (non-negative, bounded, non-empty).")
(sketch "type PositiveInt = Int where (x >= 0)\nfn heal(h: PositiveInt, delta: PositiveInt): PositiveInt"))
(pattern
(name "resource-lifecycle")
(composition "affine types")
(purpose "Ensure resources (file handles, sockets, unique entities) are used at most once or correctly disposed.")
(sketch "fn open(path: String): File\nfn read(f: File): (File, String)\nfn close(f: File): Unit"))
(pattern
(name "trait-like-dispatch")
(composition "first-class records + row polymorphism")
(purpose "Type-class-style overloading without type classes: pass a dictionary of operations as a record; row inference specialises.")
(sketch "type Eq<'a> = {eq: ('a, 'a) -> Bool}\nfn contains<'a>(dict: Eq<'a>, list: List<'a>, target: 'a): Bool"))
(pattern
(name "purity-tagging")
(composition "phantom + effect rows")
(purpose "Mark values or functions as pure or impure in the type system for optimisation or testing.")
(sketch "fn pure_compute(x: Int): Int // no effect row = pure\nfn io_op(): String / {IO} // effect row declares IO"))
(pattern
(name "per-element-error-extraction")
(composition "polymorphic-per-element + Validation")
(purpose "Accumulate errors from heterogeneously-typed Validation values without forcing them into a uniform list.")
(anti-pattern "List (Validation E a) when each element has a different `a` — non-starter type-theoretically; cannot unify a single `a` across heterogeneous components.")
(sketch "let allErrs = errsOf v1 ++ errsOf v2 ++ errsOf v3\n where errsOf : Validation E a -> List E"))
(pattern
(name "fuel-based-total-recursion")
(composition "Nat parameter + structural decrease")
(purpose "Make recursive parsers / interpreters / search algorithms provably total without sized types.")
(anti-pattern "Depth tracking with `if depth > N then bail` — runtime guarantee, not provable. Totality checker cannot see incrementing parameters as well-founded.")
(sketch "fn parse(fuel: Nat, input: List Char) { match fuel { Z => Err(\"depth\"); S(f) => recurse(f, ...) } }"))
(pattern
(name "validation-chain-over-tuple-match")
(composition "Validation applicative + <*>")
(purpose "Accumulate errors across N independent extractions without N-tuple matches.")
(anti-pattern "case (a, b, c, d, e, f, g) of (Ok, Ok, Ok, ...) => ... — N>=5 tuple matches blow type inference / ambiguity-resolution budgets.")
(sketch "MkRecord <$> a <*> b <*> c <*> d <*> e <*> f <*> g"))
(pattern
(name "status-sum-vs-string-enum")
(composition "sum types")
(purpose "Distinguish status / kind values at compile time. Replaces stringly-typed dispatch (\"compromised\" / \"clean\" / etc.).")
(anti-pattern "dispatch(deviceId, \"compromised\") — typo \"compromied\" silently mis-matches at runtime.")
(sketch "type DeviceStatus = Compromised | Clean | Suspect | Unknown\nfn dispatch(d: DeviceId, s: DeviceStatus)"))
)
(agent-behavior
(session-startup
(rule "Read this document at the start of every AffineScript session. It is the authoritative scope statement."))
(read-first
(rule "Before editing any file in the AffineScript repository, read this document or verify its content matches your understanding of scope."))
(defer-on-scope
(rule "When asked to add a feature not in the in-scope list, stop and ask the user before proceeding. Do not defer to what 'the code implies' or what 'an earlier session decided' — those are the failure modes that trashed the project before."))
(no-silent-extension
(rule "Do not add compiler features to resolve user-code ergonomics problems. Use the existing type system more creatively instead, and document new patterns in the idiomatic-patterns section of this file."))
(no-free-variant-additions
(rule "If you notice that the implementation 'already supports' a feature adjacent to one in scope (linear-because-affine, dependent-because-refinement, multi-shot-because-handlers), you may NOT expose that feature on the language surface without explicit user approval. Implementation support is not the same as user-facing scope."))
(escalate-on-interaction
(rule "When two in-scope features interact in a way that forces a design decision, stop and surface the interaction for explicit user decision."))
(typed-wasm-boundary
(rule "Work that belongs in the Typed WASM project goes to the Typed WASM project. Never add it to AffineScript as a shortcut."))
(honesty-in-documentation
(rule "STATE.a2ml, README, and related docs must reflect what the code actually enforces, not what it aspires to. A 'dune build passing' is not a behavioural test and does not count as 'feature complete'."))
(stdlib-floor
(rule "Validation[E,A], Result[E,A], Option[A], and Either[E,A] are stdlib floor. Reject any project that re-implements them. Pointing migrators at canonical types prevents the heterogeneous-list-as-homogeneous antipattern."))
(eta-style
(rule "When a function has named pi-binders ((name : Type) -> ...), prefer eta-expanded definitions over point-free. Point-free is a parser-cascade trigger when the eta-rewrite's argument name disagrees with the declared signature's name."))
(no-side-effect-imports
(rule "AffineScript modules do not run code at load time. Migrating ReScript/JS code that uses `let _ = X.foo` for module-load side effects must be rewritten to call an explicit registration function. There is no implicit module-load-time side effect to hook in wasm."))
)
(relationship-with-typed-wasm-project
(role "AffineScript compiles to Typed WASM as its primary target.")
(shared-infrastructure "phantom types — the bridging abstraction. The same phantom mechanism that tags UserId vs ProductId in AffineScript will tag Meter<Int> vs Second<Int> and Tropical<Int> vs Standard<Int> in Typed WASM.")
(independence "Typed WASM is designed as a legitimate compile target for languages beyond AffineScript. It is not an AffineScript subsystem. Other languages can and should target it.")
(deferred-features "dependent types, tropical types, units of measure, shape-indexed vectors, and any advanced type-level computation live in Typed WASM, not in AffineScript.")
(secondary-backend "Julia — secondary compile target for scientific computing use cases where AffineScript's type safety complements Julia's numeric ecosystem.")
)
(faces
(note "AffineScript is one core, many faces. A face is a sugared surface syntax that lowers to the canonical AST via a single OCaml module in lib/<name>_face.ml. Established faces: canonical, RattleScript (Python-like), JaffaScript (JS/TS-like), PseudoScript (pedagogy), LucidScript (PureScript/Haskell-like), CafeScripto (CoffeeScript-like). The rules below were earned during the May-2026 LucidScript+CafeScripto buildout and the eject of the five non-canonical faces into top-level brand-surface repos at github.com/hyperpolymath/{rattlescript,jaffascript,pseudoscript,lucidscript,cafescripto}.")
(rules
(brand-surface-not-fork
(rule "A face is a transformer module in this repo PLUS a brand repo carrying README, examples, justfile, and a thin shim that defaults --face. The brand repo MUST NOT carry a parser, type checker, borrow checker, codegen, or vendored copy of the canonical compiler. There is exactly one compiler.")
(shim-pattern "#!/usr/bin/env bash\nif [ $# -eq 0 ]; then\n exec affinescript\nfi\nsubcmd=\"$1\"\nshift\nexec affinescript \"$subcmd\" --face <face_name> \"$@\"")
(anti-pattern "Cargo.toml, vendored affinescript/ submodule, separate <face>script binary, per-face parser. Each is a divergence vector that forces re-porting every canonical change to every face."))
(identity-in-content
(rule "Every face uses the canonical .affine extension. Face is selected by a face: pragma on the first comment line (# face:, // face:, -- face:, (* face: *)) or by --face on the CLI. Per-face extensions (.rattle, .pyaff, .jsaff) were tried and abandoned.")
(why "Single extension means build tools, editors, and LSP configure once. File renames don't change semantics. Polyglot directories don't need per-face globs.")
(deprecation-path "Extension dispatch is preserved in resolve_face for migration only; pragma takes precedence and emits a deprecation warning when extension dispatch is used."))
(snapshot-plus-roundtrip
(rule "Every face transformer ships with a snapshot test PLUS a round-trip parse. The snapshot diffs preview-<face> output against tests/faces/<base>.expected.txt; the round-trip parses that output as canonical to confirm grammar legality. Either alone is insufficient; visual review without round-trip parse missed five distinct transformer bugs in the May-2026 buildout.")
(harness "tools/run_face_transformer_tests.sh — modes: check (default), --update (rewrite snapshots), --record-missing (bootstrap)")
(just-recipes "test-faces, test-faces-update, test-faces-record")
(generalization "Any pure text-to-text transformer in the codebase (formatter, codegen text emitters, source rewriters) deserves the same harness. The pattern is not face-specific."))
(examples-are-tests
(rule "Every file under examples/faces/ MUST round-trip clean through the snapshot test. If an example exercises a known transformer gap and won't round-trip, simplify the example to use the documented workaround — do not ship a broken example with a hand-wave.")
(gap-disclosure "Known transformer gaps go in examples/faces/README.adoc under 'Caveats' → 'Known transformer gaps' with one-line workaround. The example file uses the workaround. Users find gaps via docs, not via confused parse errors on their own code.")
(consequence-of-violation "An example that breaks round-trip but ships in the README is a documented falsehood. README and test suite must agree, or one is lying."))
(error-vocabulary-cost
(rule "Adding a face means adding one arm per user-facing error kind to lib/face.ml's format_*_for_face dispatch tables. There is no shortcut. Face-shaped vocabulary is part of the face contract: a Haskell-shaped face must not say 'borrow checker rejected this binding'; a CoffeeScript-shaped face must not reference 'do notation' that the surface lacks.")
(cost-shape "O(faces × error_kinds). Currently 6 faces × ~12 error formatters; growth in either dimension multiplies.")
(drift-symptom "If the dispatch table lags the parser/checker, the face leaks canonical-flavoured errors and the brand promise erodes. Fix is mechanical, not creative — keep the table in sync."))
(span-fidelity-honesty
(rule "A face transformer rewrites user source; subsequent error line numbers refer to the transformed canonical text, not the user's original face source. This is a genuine UX regression. Document it in the face's brand-repo README under 'Caveats'. Do NOT paper over it with phantom span improvements that don't actually translate locations.")
(honest-options
"1. document the regression in the brand-repo README (chosen for all 5 non-canonical faces, May-2026, alpha status)"
"2. have the transformer record a canonical→source span map and have the diagnostic layer translate"
"3. build the face into the parser proper (cmdliner already supports --face), trading parser complexity for span fidelity")
(escalation "When promoting a face from alpha to beta, revisit the choice. The current option-1 stance is honest for alpha; for beta, option-2 is the minimum honest improvement.")))
(when-adding-face-N+1
(step (n 1) (do "create lib/<name>_face.ml; clone the closest existing face module (python_face.ml for indent-based; js_face.ml for brace-based; pseudocode_face.ml for keyword-heavy) and substitute the lowering rules"))
(step (n 2) (do "register the face in lib/face_pragma.ml's alias table and lib/face.ml's Face variant"))
(step (n 3) (do "extend bin/main.ml: parse_with_face arm, fmt_file arm, face_arg cmdliner enum, preview-<name> subcommand"))
(step (n 4) (do "add the face name to lib/dune modules list"))
(step (n 5) (do "build: opam exec -- dune build lib/ bin/"))
(step (n 6) (do "create examples/faces/hello-<name>.affine; iterate until snapshot+roundtrip pass via just test-faces-record then just test-faces"))
(step (n 7) (do "extend lib/face.ml format_*_for_face dispatch tables — one arm per error kind, in face-appropriate vocabulary"))
(step (n 8) (do "ship the brand repo: README.adoc + LICENSE + CONTRIBUTING.md + bin/<face> shim + justfile + examples/hello.affine + .gitignore. NO Cargo.toml, NO vendored compiler, NO separate binary"))
(step (n 9) (do "add link to the brand repo in this repo's README.adoc faces list"))
(step (n 10) (do "document any transformer gap in examples/faces/README.adoc 'Known transformer gaps'; if the gap is severe enough, demote the example to a workaround variant rather than ship a broken round-trip")))
(known-limitations
(limitation
(name "transformer-span-loss")
(since "0.1.0")
(impact "Diagnostic line numbers refer to transformed canonical, not original face source. Acceptable for alpha; must be addressed before beta. See span-fidelity-honesty rule for the three escalation options.")
(recommendation "Document in each brand-repo README until span-map translation lands."))
(limitation
(name "deferred-face-transformations")
(since "0.1.0")
(impact "Several Python/Haskell/Coffee idioms are not yet lowered: multi-clause def-bodies, do-notation, where-blocks, list comprehensions, splats, no-paren calls. These need AST-level rewrites rather than text-level substitution and are deferred to a face-AST-rewriter milestone.")
(recommendation "When sweeping the gap list, skip these categories — they require an AST-rewriter pass that doesn't exist yet, not just sugar additions to the existing text transformer.")
(tracked-in "examples/faces/README.adoc → Known transformer gaps"))
(limitation
(name "no-face-aware-formatter")
(since "0.1.0")
(impact "affinescript fmt produces canonical output regardless of input face. Round-tripping face → canonical → face is not supported; once you canonicalize, you lose the face presentation.")
(recommendation "Treat face source as the human-edited form and canonical as the build-time artifact. Do not run fmt on face files until per-face formatters exist.")))
)
(backends
(note "AffineScript is one frontend, many backends. There is no middle-end IR — each backend lowers AST→target directly. Adding a new backend is templated work; the lessons below were earned during the May-2026 backend buildout (JS/C/WGSL/Faust/ONNX/Rust/LLVM/OCaml/Lua/Bash/Nickel/ReScript/Verilog/Gleam landed alongside the pre-existing WASM/WASM-GC/Julia).")
(tiers
(tier
(name "A — host-language emitters")
(members "WASM 1.0, WASM-GC, Julia, JS, C, Rust, OCaml, ReScript, Lua, Bash, Gleam, Nickel")
(acceptance "full AffineScript surface; missing features error loudly at codegen time")
(file-shape "lib/<name>_codegen.ml, ~200-400 lines, single-file output with a tiny inline runtime prelude"))
(tier
(name "B — IR / native")
(members "LLVM IR (Cranelift candidate)")
(acceptance "full surface, lower level of abstraction; some passes (closures, traits) need lowering that may not exist yet")
(file-shape "SSA-shaped emitter; needs current-block tracking for phi predecessors"))
(tier
(name "C — kernel sublanguages")
(members "WGSL, SPIR-V (planned), CUDA C++ (planned), Metal (planned), OpenCL (planned), Faust, ONNX, MLIR/StableHLO (planned)")
(acceptance "DELIBERATELY restricted subset; everything outside that subset must reject explicitly via `unsupported`")
(file-shape "validation pre-pass on the AST shape (e.g., 'first param is Int', 'all types are Float'), then targeted lowering")))
(rules
(clone-don't-redesign
(rule "When adding a new backend, clone the closest existing emitter and substitute lowering rules. Do NOT redesign the codegen module shape from scratch — every backend after the first cost ~200 lines because the modules are isomorphic.")
(template-pairs
"JS/C/Lua/Rust/OCaml/ReScript/Gleam/Nickel/Bash → julia_codegen.ml"
"WGSL/SPIR-V/CUDA/Metal/OpenCL → wgsl_codegen.ml"
"Faust/Csound → faust_codegen.ml"
"ONNX/MLIR/TFLite → onnx_codegen.ml"
"Verilog/VHDL → verilog_codegen.ml"))
(use-shared-kernel-sublang-module
(rule "Every Tier-C kernel sublanguage backend opens [Kernel_sublang] and reuses its primitives instead of inlining its own. The module provides: the [Unsupported] exception (one shared type, not per-backend), [pick_entry ?names], [strip_ownership], [is_unit_ty], [array_element], [require_array_element], [math_builtins], [is_math_builtin], [validate_return], [validate_params], and [validate_compute_kernel_shape] (the canonical 'first param Int, rest Array buffers, returns Unit' predicate used by WGSL/SPIR-V/CUDA/Metal/OpenCL).")
(location "lib/kernel_sublang.ml")
(extend-not-fork "If a new Tier-C backend needs an intrinsic the shared math_builtins list lacks, add it to that list rather than maintaining a parallel allowlist. If a new validation rule emerges that two or more backends need, factor it into Kernel_sublang rather than copying."))
(validate-end-to-end
(rule "Every shipped backend must round-trip through the target's reference toolchain. Bytes a human read are not bytes that work. The honest MVP claim is 'the bytes round-trip,' not 'the bytes look right.'")
(validators "naga (WGSL), faust (Faust), oxionnx-proto (ONNX), cc (C), rustc (Rust), ocaml (OCaml), lua5.4 (Lua), bash -n (Bash), nickel typecheck (Nickel), llc (LLVM), node/deno (JS)")
(when-no-validator "If the target has no installable validator in the dev environment, document that explicitly in the recap. Do not claim end-to-end validation by inspection alone."))
(single-file-no-runtime
(rule "Generated output is one file. Tiny runtime preludes (Some/None constructors, println shims, a few factories) go INLINE at the top of the emitted file, not as a separate runtime.h / runtime.lua / runtime.rs. This dodges version-skew bugs that would otherwise haunt every compile."))
(loud-failures
(rule "Surface unsupported source shapes with `raise (X_unsupported \"...\")` so the codegen errors at compile time. Never emit silently miscompilable output. A loud `unsupported` is the load-bearing API contract of every backend."))
(frontend-gaps-don't-paper-at-backend
(rule "If a backend can't lower something the surface syntax accepts, fix the frontend or document the restriction. Do NOT compensate at the backend layer — that produces silent miscompilation later. Examples from the May-2026 buildout: the typechecker's monomorphic-Int restriction (fixed in typecheck.ml) and the borrow checker's missing mut-param seeding (fixed in borrow.ml) both first surfaced as 'WGSL won't accept Float kernels' / 'Faust SAXPY won't borrow-check'; both were frontend bugs masquerading as backend limitations."))
(stubs-as-intrinsics
(rule "When a target has runtime ops AffineScript's resolver doesn't know about (CUDA's __shfl_xor, ONNX's Relu, Faust's tanh, Verilog's $display), declare them in user code as stub functions and recognise them by name in the codegen. The stub body is a placeholder; the resolver passes; the backend pattern-matches. This is the canonical way to smuggle target intrinsics through a universal frontend.")
(example "fn relu(x: Array[Float]) -> Array[Float] { x } // stub for ONNX Relu")
(rationale "Avoids extending the language for every target's vocabulary; keeps each backend self-contained.")))
(when-adding-backend-N+1
(step (n 1) (do "identify the tier (A/B/C above); pick the template emitter from the tier's template-pairs list"))
(step (n 2) (do "create lib/<name>_codegen.ml as a clone with target-specific lowering rules; reuse mangle / type-mapping / gen_lit shape"))
(step (n 3) (do "for Tier-C backends: open Kernel_sublang and use its shared primitives (pick_entry / strip_ownership / array_element / math_builtins / Unsupported) instead of inlining your own"))
(step (n 4) (do "register the module in lib/dune (alphabetical) and add `.<ext>` dispatch in bin/main.ml (both --json and human branches)"))
(step (n 5) (do "build: `eval $(opam env --switch=default) && dune build lib bin/main.exe`"))
(step (n 6) (do "validate end-to-end with the target's reference toolchain on /tmp/cdemo.affine (the canonical Int/branch/let test); record the result in the recap"))
(step (n 7) (do "for tier-C kernel backends: also test Float arithmetic, since the typechecker patch landed 2026-05-02"))
(step (n 8) (do "for Tier-A backends: also test the Phase-4 trio (tuple p4a, record p4b, variant+match p4c) end-to-end; emit `Unsupported` loudly for any shape outside scope"))
(step (n 9) (do "document any frontend gap surfaced by the work in this AI.a2ml file under (backends → known-limitations); fix it BEFORE the next adaptive task per the corrective-before-adaptive ordering rule")))
(known-limitations
(limitation
(name "wasm-stdin-and-exit-propagation")
(since "0.1.0")
(resolved "partial — 2026-05-03")
(impact
"WASM Phase 6 is now end-to-end on wasmtime — println of string literals works for any number of calls; the heap-alignment trap that surfaced after the first println was an iovec-layout bug in [wasi_runtime.gen_println] (1-byte newline placed before a 4-byte iovec, causing an unaligned i32.store at temp+1). Fixed by reordering the layout so the iovec sits at temp+0 (aligned) and the newline byte moves to temp+12. Total allocation rounded from 13 to 16 bytes so subsequent allocs stay aligned.")
(deferred
"Three pieces remain for a fully Phase-6-and-7 WASM backend, all in [lib/wasi_runtime.ml] / [lib/codegen.ml]:
(1) [fd_read] import + [gen_read_line] helper — mirror [create_fd_write_import] / [gen_print_str]; the line-reader needs a byte-loop because [fd_read] returns N bytes, not lines (~80 lines of wasm IR);
(2) Exit-code propagation — replace the [(start $main)] directive with an exported [_start] that calls [main] then [proc_exit(retval)] via a [wasi_snapshot_preview1.proc_exit] import. Side effect: wasmtime runs without [--invoke main] and exit codes flow naturally;
(3) Real [OpConcat] (currently a placeholder [I32Add]) — needs allocation + byte-copy. Without [I32Load8U]/[I32Store8] in [lib/wasm.ml]'s instruction set the byte loop is awkward; consider adding bulk-memory ([memory.copy], opcode 0xfc 0x0a) to the AST first.
Estimated scope: 2-3 hours of focused WASM work in the 1900-line backend, additive to the existing helper structure.")
(recommendation
"Tackle exit-code propagation first — it's the smallest of the three (one new import, swap [(start)] for [(_start)], propagate a single i32 to proc_exit) and unblocks running WASM programs without the [--invoke main] flag. Then add [fd_read]/[gen_read_line]. [OpConcat] last; consider extending [wasm.ml] with [MemoryCopy] before writing the helper."))
(limitation
(name "no-target-intrinsic-protocol")
(since "0.1.0")
(impact "Each backend keeps its own ad-hoc builtin allowlist (sin/cos/relu/etc). User stubs work as a workaround per the stubs-as-intrinsics rule, but unifying behind @target(\"foo\") fn declarations would let one stdlib serve all backends. Perfective task; not blocking.")
(recommendation "Don't extend per-backend builtin lists ad-hoc; if a target needs a new op, prefer the stub-recognise-by-name pattern."))
(limitation
(name "no-tensor-type")
(since "0.1.0")
(impact "The frontend has no Tensor[T, S]; ONNX is currently a 'proof of wire format' rather than a real ML target. Adding tensor types is multiple weeks of frontend work and belongs in (out-of-scope deferred-to-sibling-project) until/unless a user demands it.")
(recommendation "When ONNX scope expands, file the request and check (out-of-scope) before extending."))
(limitation
(name "kernel-sublangs-share-no-validator")
(since "0.1.0")
(resolved "0.1.1 — 2026-05-03")
(impact "Originally each Tier-C backend kept its own restriction predicate; resolved by factoring the shared primitives into [lib/kernel_sublang.ml]. ~140 lines of duplicated boilerplate removed across 7 backends; the module exposes Unsupported, pick_entry, strip_ownership, is_unit_ty, array_element, require_array_element, math_builtins, validate_compute_kernel_shape, etc.")
(recommendation "Use Kernel_sublang for any new Tier-C backend; extend the module rather than fork its rules.")))
)
)