Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .machine_readable/6a2/META.a2ml
Original file line number Diff line number Diff line change
Expand Up @@ -1095,3 +1095,49 @@ references = [
"lib/effect_sites.ml (new; shared call-site numbering)",
"docs/specs/SETTLED-DECISIONS.adoc (ADR-016 section)",
]

[[adr]]
id = "ADR-018"
status = "accepted"
date = "2026-05-19"
title = "No raw/FFI escape: typed `extern` is the only host bridge"
context = """
ReScript `%%raw("<host source>")` / `%raw` injects arbitrary untyped
host source. AffineScript's only host bridge is `extern fn` / `extern
type` (parser.mly extern_fn_decl/extern_type_decl, FnExtern body) —
typed, host-supplied, no body, no arbitrary-source escape. 14 estate
`%%raw` occurrences (#229 Tier-3, ESC-01 #245) had no clean target.
Escalated language-side — the bidirectional-evidence discipline of
ADR-014 / #228 and the affine spirit of explicit, typed boundaries.
(ADR-017 is the block-module disposition #262; this is ADR-018 because
main merged ADR-016 = effect-threaded async-boundary, #234.)
"""
decision = """
No raw escape — there is no `%%raw` analogue, by design. Typed `extern
fn` / `extern type` is the SOLE FFI surface. Every estate `%%raw` ports
to a typed `extern` whose signature states the host contract the raw
blob assumed implicitly; the host implementation moves to the
embedder/runtime shim. AffineScript will NOT gain an untyped
intrinsic/`extern raw` block: an arbitrary-source hole defeats
affine/effect tracking at the boundary where the guarantees matter most
— precisely what the type-and-effect discipline (and ADR-012) exists to
prevent. Doctrine decision: `extern` already exists, no compiler change;
it settles the #229 canonical-map target and the FFI stance.
"""
consequences = """
- A `%%raw` encoding genuine logic (not just a host call) is a design
smell the port surfaces: re-express as real AffineScript + a typed
`extern` for any true host primitive, never smuggled through.
- `%%raw`-bearing #229 files (idaptik Main/StartupError, parts of
burble) port under this doctrine; per-file execution is #229 per-repo.
- No language/compiler change; no estate consumer churn from this ADR.
- Settled; do not reopen without amending. ESC-01 #245.
"""
references = [
"https://github.com/hyperpolymath/affinescript/issues/245",
"https://github.com/hyperpolymath/affinescript/issues/229",
"lib/parser.mly (extern_fn_decl; extern_type_decl; FnExtern)",
"docs/specs/SETTLED-DECISIONS.adoc (ADR-018 section)",
"docs/RESCRIPT-ELIMINATION.adoc (#229 canonical map; Tier-3 ESC-01)",
"META.a2ml [[adr]] ADR-012 (grammar changes are correctness assertions)",
]
19 changes: 8 additions & 11 deletions docs/RESCRIPT-ELIMINATION.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -200,17 +200,14 @@ same discipline that produced #228.
|===
|Esc |Construct |Finding

|*ESC-01* (#245) |`%%raw("…")` (14) |AffineScript has *no raw-host-expression
/ FFI escape*. The only host bridge is typed `extern fn` / `extern type`
(`lib/parser.mly:185+`) — host-supplied, typed, no arbitrary-source escape.
Needs a language decision on a raw/FFI form (or an explicit "port every
`%%raw` to typed `extern`" doctrine).
|*ESC-02* (#246) — LANDED |`JSON.t` (7) |`stdlib/json.affine`: pure
recursive `pub type Json = JNull \| JBool \| JInt \| JFloat \| JString \|
JArray \| JObject([(String,Json)])` + encoders/decoders/`get_field`/
`stringify`; AOT-gated (#136). `JSON.t` → `json::Json`. String→Json
*parse* is deliberately the typed `Http` boundary bridge (ADR-018), not a
hand-rolled stdlib parser. (#161 / STDLIB-02.)
|*ESC-01* (#245) — SETTLED by ADR-018 |`%%raw("…")` (14) |*Doctrine: no
raw escape, by design.* Typed `extern fn` / `extern type` is the **sole**
FFI surface; every `%%raw` ports to a typed `extern` whose signature
states the host contract, host impl moving to the embedder/runtime shim.
No untyped `extern raw` will be added (an arbitrary-source hole defeats
affine/effect tracking — the ADR-012 contortion). No compiler change.
|*ESC-02* (#246) |`JSON.t` (7) |No stdlib JSON type (`stdlib/` has `Ajv` but
no `Json`). Needs a stdlib JSON type.
|*ESC-03* (#247) |`Dict.t` (6) |No stdlib `Map`/`Dict` type
(`stdlib/collections.affine` is list ops only; `stdlib/Http.affine:16`
already flags the `Dict` gap, tied to #160/#162). Needs a stdlib `Map` type —
Expand Down
33 changes: 33 additions & 0 deletions docs/specs/SETTLED-DECISIONS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,36 @@ for table-miss only).
This decision is settled; do not reopen without amending the ADR. Full
ADR in `.machine_readable/6a2/META.a2ml` (ADR-016); ledger #234 /
CORE-02 in `docs/TECH-DEBT.adoc`.

== No Raw/FFI Escape: Typed `extern` Is the Only Host Bridge (ADR-018)

ReScript `%%raw("<host source>")` / `%raw` injects arbitrary untyped host
source. AffineScript's only host bridge is `extern fn` / `extern type`
(grammar `parser.mly`: `extern_fn_decl` / `extern_type_decl`, `FnExtern`
body) — *typed*, host-supplied, no body, no arbitrary-source escape. 14
estate `%%raw` occurrences (#229 Tier-3, ESC-01 #245) had no clean
target. Escalated language-side — the bidirectional-evidence discipline
of ADR-014 / #228, and the affine spirit of explicit, typed boundaries.

Decision: *no raw escape — there is no `%%raw` analogue, by design.* The
typed `extern fn` / `extern type` declaration is the **sole** FFI
surface. Every estate `%%raw` ports to a typed `extern` declaration whose
signature states the host contract the raw blob assumed implicitly; the
host implementation moves to the embedder/runtime shim. AffineScript will
*not* gain an untyped intrinsic/`extern raw` block: an arbitrary-source
hole defeats affine/effect tracking at the very boundary where the
guarantees matter most — exactly what the type-and-effect discipline (and
ADR-012) exists to prevent. This is a *doctrine* decision: `extern`
already exists, so there is no compiler change; it settles the #229
canonical-map target and the language's FFI stance.

Consequence: a `%%raw` that encodes genuine logic (not just a host call)
is a design smell surfaced by the port — it must be re-expressed as real
AffineScript plus a typed `extern` for any true host primitive, not
smuggled through. `%%raw`-bearing #229 files (idaptik `Main`/`StartupError`,
parts of `burble`) port under this doctrine; per-file execution is the
#229 per-repo work.

This decision is settled; do not reopen without amending the ADR. Full
ADR in `.machine_readable/6a2/META.a2ml` (ADR-018); #229 canonical map in
`docs/RESCRIPT-ELIMINATION.adoc`; escalation issue #245.
Loading