Status: DRAFT (2026-07-08) — Milestone A keystone of the face-sealing plan.
This document is the single normative source for how a language face binds to
the canonical AffineScript compiler. Where docs/specs/faces.adoc describes the
design of the face architecture (ADR-010), this document fixes the contract:
the roster, the file/pragma model, the CLI names, the shim protocol, the canonical
pin, and the conformance + proof obligations every face must meet. Brand-surface
repos link here rather than restating any of it.
- The face roster (normative)
- Binding model: one extension, a pragma per face
- Face identity table (normative)
- Shim protocol (hardened)
- Canonical pin
- ActionScript: welcomed in, but not a standalone face
- Conformance obligations
- Proof obligations
- Open implementation tasks (tracked by the plan)
- Provenance
A face earns a place on this roster only if it serves a distinct, living surface
community whose idioms map meaningfully onto AffineScript’s affine/linear
effect + typed-wasm semantics. Near-duplicate surfaces and dead-runtime dialects
do not qualify as standalone faces (see ActionScript: welcomed in, but not a standalone face).
| Brand | Source language | --face |
Status |
|---|---|---|---|
(canonical) |
AffineScript itself |
|
Stable (default) |
RattleScript |
Python |
|
Shipped |
JaffaScript |
JavaScript |
|
Shipped |
ProperScript |
TypeScript (+ AS3 preset) |
|
New — planned |
RealScript |
ReScript |
|
New — planned |
CafeScripto |
CoffeeScript |
|
Shipped |
LucidScript |
PureScript / Haskell |
|
Shipped |
PseudoScript |
pseudocode |
|
Shipped |
Each brand name is a lateral pun on its source language — either a synonym
(cafe≈coffee, lucid≈pure) or an associative leap (rattle→snake→Python,
jaffa→Java). The two new ECMAScript-descended faces form a coherent trio with
JaffaScript:
-
JaffaScript — raw JavaScript, untyped.
-
ProperScript — TypeScript, now typed properly. TypeScript’s types are structural and erased; routed through the affine core + typed-wasm they become sound. "The TypeScript that is finally typed properly."
-
RealScript — ReScript, whose ML-family types were real (sound, non-nullable, no
any) all along. The name is a homophone tribute (Re→Real), not a corrective.
ProperScript and RealScript are the "typed twins" of the JaffaScript family.
Ratified (supersedes the .pyaff/.jsaff/… prose in older docs). Every face
shares the canonical .affine file extension. The active face is selected, in
priority order:
-
an explicit
--face <name>flag (always wins), then -
a first-line
face:pragma in the source, then -
canonical (default).
The legacy per-face extensions (.pyaff, .jsaff, .pseudoaff, …) and the
--face pyaff|jsaff|pseudoaff spellings are deprecated (the compiler already
emits a deprecation warning for them, bin/main.ml). New material MUST use
.affine + pragma.
The pragma is the file’s first meaningful line, written in that face’s own comment
syntax, of the form <comment> face: <long-name>:
# face: rattlescript (Python-style comment)
// face: jaffascript (JS-style comment)
// face: properscript
// face: realscript
-- face: pseudoscript (or lucidscript)
# face: cafescripto| Brand | --face short |
pragma long | transformer + Face variant |
brand repo |
|---|---|---|---|---|
RattleScript |
|
|
|
|
JaffaScript |
|
|
|
|
ProperScript |
|
|
|
|
RealScript |
|
|
|
|
CafeScripto |
|
|
|
|
LucidScript |
|
|
|
|
PseudoScript |
|
|
|
|
* = does not exist yet; created under Part 2 of the plan. The CLI already
accepts the brand short aliases rattle/jaffa alongside the canonical
python/js names; it MUST be extended to accept proper/real (and
typescript/rescript) once those Face variants land. Note: bin/main.ml
already carries partial is_rescript error-vocabulary plumbing that RealScript
will absorb.
Each brand repo ships a bin/<short> shim carrying no compiler — it locates the
canonical affinescript binary and injects --face <short>. The shim MUST resolve
the binary in this order and fail with an actionable message if none is found:
-
--affinescript <path>/$AFFINESCRIPT, else -
affinescripton$PATH, else -
the sibling canonical checkout (
../γ-affinescript/affinescript/_build/default/bin/main.exe), else -
print:
affinescript not found — install via opam, or set $AFFINESCRIPT.
This replaces the current bare-affinescript shims, which fail with a raw
command not found.
Every brand repo pins the canonical compiler as a git submodule (the mechanism
RattleScript already uses) and bumps it via just update-affinescript. No brand
repo floats against "whatever is on `$PATH`". All language, face, and bug-fix
changes flow through a single submodule-pointer bump, so a face can never silently
drift from the compiler it targets.
Decision (2026-07-08): ActionScript is NOT a standalone face. It is folded into
ProperScript as a migration preset. This retires the "ActionScript-face (strategic
priority)" roadmap item in docs/specs/faces.adoc §194.
Why not a face. A face must serve a distinct, living surface community. AS3 fails
both halves: its surface is an ECMAScript-4 dialect (var x:Type,
function f():R, classes, packages) that is essentially JaffaScript
ProperScript already, and its runtime (Flash) died in 2020, so there is no
community writing new AS3 to rehome. Its -Script is native (like TypeScript /
JavaScript), the tell that it is a product name, not a face-brand.
But the door is open. Flash / Flex / AIR developers were displaced by platform
death, not language quality, and they are genuinely welcome. Their on-ramp is
ProperScript plus an AS3-idioms migration preset — extends → traits,
package { … } stripped, trace(x) → IO.println(x), Number/int → Float/
Int, Array.<T>/Vector.<T> → Array[T], null → (). The mapping already
drafted in faces.adoc §194 is retargeted from a hypothetical action_face onto
this preset.
Honest scope. A dedicated action_face with its own brand repo is deferred
indefinitely. This is a one-person, unfunded project; the resource-responsible
choice is a preset that reuses the ProperScript machinery, not a whole face we
cannot maintain. It will be reassessed only if a funded AS3 cohort materialises.
A face is conformant only when all of the following are green:
-
Snapshot —
tools/run_face_transformer_tests.sh(the face’stests/faces/hello-<x>.expected.txtmatches itspreview-<x>output). -
Round-trip parse — the face’s examples parse via the normal pragma-auto-detecting pipeline.
-
Same-cube —
hyperpolymath/invariant-path/scripts/verify-same-cube.shplaces the face in the single canonical wasm equivalence class.ImportantThis gate is currently RED (exit 1): the six existing faces split into two wasm classes — {lucid, pseudo, rattle}(tail-expression) vs{cafe, canonical, jaffa}(statement) — because terminator-free faces guess a unit-typed trailing call as a tail expression and drop the;(canonical issue #601). Closing it (Milestone B) is a plan deliverable, via a post-typecheck tail-normalisation pass, so the text transformer’s guess no longer reaches codegen.
Same-cube MUST be promoted from an external script to a required CI check in the canonical repo, and each brand repo MUST run a per-face conformance gate against its pinned submodule.
The canonical core is being proved against the real lib/ast.ml / lib/wasm.ml
denotation (the "real lift"). The faces must ride that same rigour. Current status
(docs/PROOF-NEEDS.adoc):
| Obligation | Statement | Status |
|---|---|---|
F-1 |
every |
partial (toy model only) |
F-2 |
tail/statement normalisation preserves denotation |
partial (unit-tail case, |
F-6 |
preview round-trip totality (transform always yields parseable canonical) |
absent |
F-7 |
face/canon confluence (all faces of one program reach one canonical AST) |
absent |
Sealing the connection "for real" (Milestone D) means moving F-1 to per-face preservation against the real AST, generalising F-2 to the normalisation pass of #601, and discharging F-6/F-7. Until then the real transformers are grounded by tests only; this table is the honest statement of that gap.
-
P1.2 — add
proper/real(+typescript/rescript) to the CLI--facealias table andFacevariant type. -
P2 (js_face split) — JaffaScript narrows to plain JavaScript; ProperScript gets its own transformer + TS-idiom error vocabulary (interfaces→traits,
type,as, generics→type params,| nullunions). `lib/js_face.ml’s current "JS/TS" double duty is split. -
Create brand repos
properscriptandrealscript(brand-surface only, per this contract). -
Span fidelity (ADR-010 §4) — error spans map back to original face source, not transformed canonical text.
-
Path —
lucidscript+cafescriptocurrently live underγ-languages/; ratify or relocate to match the flatrepos/siblings. -
AFFIRMATION — absent from all brand repos; add if RSR-required.
-
Roster + names ratified in session 2026-07-08 (RattleScript/JaffaScript/ CafeScripto/LucidScript/PseudoScript pre-existing; ProperScript, RealScript, and the ActionScript fold-in decided this session).
-
Supersedes:
.pyaff-style extensions; the ActionScript standalone-face roadmap item (faces.adoc §194). -
Companion design doc:
docs/specs/faces.adoc(ADR-010).