Skip to content

Commit d19afa1

Browse files
docs(adr): ESC-01 — ADR-018 no raw/FFI escape, typed extern only (Refs #245 #229) (#272)
ReScript %%raw injects arbitrary untyped host source; AffineScript has only typed extern fn / extern type. 14 estate %%raw (ESC-01 #245) had no clean target. ADR-018 (accepted): no raw escape by design — typed extern is the SOLE FFI surface; every %%raw ports to a typed extern, host impl to the embedder shim; no untyped extern-raw will be added (an arbitrary-source hole defeats affine/effect tracking — the ADR-012 contortion). No compiler change (extern already exists). ADR-017 = the block-module disposition (#262); this is 018 because main merged ADR-016 = effect-threaded async-boundary (#234/#270). SETTLED-DECISIONS + META.a2ml [[adr]] ADR-018 + RESCRIPT-ELIMINATION cross-linked. Docs-only; gate unaffected by construction. Refs #245 #229 (not Closes — per-repo %%raw->extern port execution remains). Co-authored-by: hyperpolymath <hyperpolymath@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent adb177b commit d19afa1

3 files changed

Lines changed: 87 additions & 11 deletions

File tree

.machine_readable/6a2/META.a2ml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,3 +1095,49 @@ references = [
10951095
"lib/effect_sites.ml (new; shared call-site numbering)",
10961096
"docs/specs/SETTLED-DECISIONS.adoc (ADR-016 section)",
10971097
]
1098+
1099+
[[adr]]
1100+
id = "ADR-018"
1101+
status = "accepted"
1102+
date = "2026-05-19"
1103+
title = "No raw/FFI escape: typed `extern` is the only host bridge"
1104+
context = """
1105+
ReScript `%%raw("<host source>")` / `%raw` injects arbitrary untyped
1106+
host source. AffineScript's only host bridge is `extern fn` / `extern
1107+
type` (parser.mly extern_fn_decl/extern_type_decl, FnExtern body) —
1108+
typed, host-supplied, no body, no arbitrary-source escape. 14 estate
1109+
`%%raw` occurrences (#229 Tier-3, ESC-01 #245) had no clean target.
1110+
Escalated language-side — the bidirectional-evidence discipline of
1111+
ADR-014 / #228 and the affine spirit of explicit, typed boundaries.
1112+
(ADR-017 is the block-module disposition #262; this is ADR-018 because
1113+
main merged ADR-016 = effect-threaded async-boundary, #234.)
1114+
"""
1115+
decision = """
1116+
No raw escape — there is no `%%raw` analogue, by design. Typed `extern
1117+
fn` / `extern type` is the SOLE FFI surface. Every estate `%%raw` ports
1118+
to a typed `extern` whose signature states the host contract the raw
1119+
blob assumed implicitly; the host implementation moves to the
1120+
embedder/runtime shim. AffineScript will NOT gain an untyped
1121+
intrinsic/`extern raw` block: an arbitrary-source hole defeats
1122+
affine/effect tracking at the boundary where the guarantees matter most
1123+
— precisely what the type-and-effect discipline (and ADR-012) exists to
1124+
prevent. Doctrine decision: `extern` already exists, no compiler change;
1125+
it settles the #229 canonical-map target and the FFI stance.
1126+
"""
1127+
consequences = """
1128+
- A `%%raw` encoding genuine logic (not just a host call) is a design
1129+
smell the port surfaces: re-express as real AffineScript + a typed
1130+
`extern` for any true host primitive, never smuggled through.
1131+
- `%%raw`-bearing #229 files (idaptik Main/StartupError, parts of
1132+
burble) port under this doctrine; per-file execution is #229 per-repo.
1133+
- No language/compiler change; no estate consumer churn from this ADR.
1134+
- Settled; do not reopen without amending. ESC-01 #245.
1135+
"""
1136+
references = [
1137+
"https://github.com/hyperpolymath/affinescript/issues/245",
1138+
"https://github.com/hyperpolymath/affinescript/issues/229",
1139+
"lib/parser.mly (extern_fn_decl; extern_type_decl; FnExtern)",
1140+
"docs/specs/SETTLED-DECISIONS.adoc (ADR-018 section)",
1141+
"docs/RESCRIPT-ELIMINATION.adoc (#229 canonical map; Tier-3 ESC-01)",
1142+
"META.a2ml [[adr]] ADR-012 (grammar changes are correctness assertions)",
1143+
]

docs/RESCRIPT-ELIMINATION.adoc

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -200,17 +200,14 @@ same discipline that produced #228.
200200
|===
201201
|Esc |Construct |Finding
202202

203-
|*ESC-01* (#245) |`%%raw("…")` (14) |AffineScript has *no raw-host-expression
204-
/ FFI escape*. The only host bridge is typed `extern fn` / `extern type`
205-
(`lib/parser.mly:185+`) — host-supplied, typed, no arbitrary-source escape.
206-
Needs a language decision on a raw/FFI form (or an explicit "port every
207-
`%%raw` to typed `extern`" doctrine).
208-
|*ESC-02* (#246) — LANDED |`JSON.t` (7) |`stdlib/json.affine`: pure
209-
recursive `pub type Json = JNull \| JBool \| JInt \| JFloat \| JString \|
210-
JArray \| JObject([(String,Json)])` + encoders/decoders/`get_field`/
211-
`stringify`; AOT-gated (#136). `JSON.t` → `json::Json`. String→Json
212-
*parse* is deliberately the typed `Http` boundary bridge (ADR-018), not a
213-
hand-rolled stdlib parser. (#161 / STDLIB-02.)
203+
|*ESC-01* (#245) — SETTLED by ADR-018 |`%%raw("…")` (14) |*Doctrine: no
204+
raw escape, by design.* Typed `extern fn` / `extern type` is the **sole**
205+
FFI surface; every `%%raw` ports to a typed `extern` whose signature
206+
states the host contract, host impl moving to the embedder/runtime shim.
207+
No untyped `extern raw` will be added (an arbitrary-source hole defeats
208+
affine/effect tracking — the ADR-012 contortion). No compiler change.
209+
|*ESC-02* (#246) |`JSON.t` (7) |No stdlib JSON type (`stdlib/` has `Ajv` but
210+
no `Json`). Needs a stdlib JSON type.
214211
|*ESC-03* (#247) |`Dict.t` (6) |No stdlib `Map`/`Dict` type
215212
(`stdlib/collections.affine` is list ops only; `stdlib/Http.affine:16`
216213
already flags the `Dict` gap, tied to #160/#162). Needs a stdlib `Map` type —

docs/specs/SETTLED-DECISIONS.adoc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,36 @@ for table-miss only).
342342
This decision is settled; do not reopen without amending the ADR. Full
343343
ADR in `.machine_readable/6a2/META.a2ml` (ADR-016); ledger #234 /
344344
CORE-02 in `docs/TECH-DEBT.adoc`.
345+
346+
== No Raw/FFI Escape: Typed `extern` Is the Only Host Bridge (ADR-018)
347+
348+
ReScript `%%raw("<host source>")` / `%raw` injects arbitrary untyped host
349+
source. AffineScript's only host bridge is `extern fn` / `extern type`
350+
(grammar `parser.mly`: `extern_fn_decl` / `extern_type_decl`, `FnExtern`
351+
body) — *typed*, host-supplied, no body, no arbitrary-source escape. 14
352+
estate `%%raw` occurrences (#229 Tier-3, ESC-01 #245) had no clean
353+
target. Escalated language-side — the bidirectional-evidence discipline
354+
of ADR-014 / #228, and the affine spirit of explicit, typed boundaries.
355+
356+
Decision: *no raw escape — there is no `%%raw` analogue, by design.* The
357+
typed `extern fn` / `extern type` declaration is the **sole** FFI
358+
surface. Every estate `%%raw` ports to a typed `extern` declaration whose
359+
signature states the host contract the raw blob assumed implicitly; the
360+
host implementation moves to the embedder/runtime shim. AffineScript will
361+
*not* gain an untyped intrinsic/`extern raw` block: an arbitrary-source
362+
hole defeats affine/effect tracking at the very boundary where the
363+
guarantees matter most — exactly what the type-and-effect discipline (and
364+
ADR-012) exists to prevent. This is a *doctrine* decision: `extern`
365+
already exists, so there is no compiler change; it settles the #229
366+
canonical-map target and the language's FFI stance.
367+
368+
Consequence: a `%%raw` that encodes genuine logic (not just a host call)
369+
is a design smell surfaced by the port — it must be re-expressed as real
370+
AffineScript plus a typed `extern` for any true host primitive, not
371+
smuggled through. `%%raw`-bearing #229 files (idaptik `Main`/`StartupError`,
372+
parts of `burble`) port under this doctrine; per-file execution is the
373+
#229 per-repo work.
374+
375+
This decision is settled; do not reopen without amending the ADR. Full
376+
ADR in `.machine_readable/6a2/META.a2ml` (ADR-018); #229 canonical map in
377+
`docs/RESCRIPT-ELIMINATION.adoc`; escalation issue #245.

0 commit comments

Comments
 (0)