From b783e5460f0af8f0ed97990d32896f5a471564e0 Mon Sep 17 00:00:00 2001 From: hyperpolymath Date: Tue, 19 May 2026 19:25:58 +0100 Subject: [PATCH] =?UTF-8?q?docs(adr):=20ESC-01=20=E2=80=94=20ADR-018=20no?= =?UTF-8?q?=20raw/FFI=20escape,=20typed=20extern=20only=20(Refs=20#245=20#?= =?UTF-8?q?229)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: Claude Opus 4.7 (1M context) --- .machine_readable/6a2/META.a2ml | 46 +++++++++++++++++++++++++++++++ docs/RESCRIPT-ELIMINATION.adoc | 11 ++++---- docs/specs/SETTLED-DECISIONS.adoc | 33 ++++++++++++++++++++++ 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/.machine_readable/6a2/META.a2ml b/.machine_readable/6a2/META.a2ml index 7725473c..fc33ccbf 100644 --- a/.machine_readable/6a2/META.a2ml +++ b/.machine_readable/6a2/META.a2ml @@ -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("")` / `%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)", +] diff --git a/docs/RESCRIPT-ELIMINATION.adoc b/docs/RESCRIPT-ELIMINATION.adoc index f9631500..4c7ed43a 100644 --- a/docs/RESCRIPT-ELIMINATION.adoc +++ b/docs/RESCRIPT-ELIMINATION.adoc @@ -200,11 +200,12 @@ 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-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 diff --git a/docs/specs/SETTLED-DECISIONS.adoc b/docs/specs/SETTLED-DECISIONS.adoc index 24884421..e979438f 100644 --- a/docs/specs/SETTLED-DECISIONS.adoc +++ b/docs/specs/SETTLED-DECISIONS.adoc @@ -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("")` / `%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.