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 4c8ebf39..4c7ed43a 100644 --- a/docs/RESCRIPT-ELIMINATION.adoc +++ b/docs/RESCRIPT-ELIMINATION.adoc @@ -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 — 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.