Skip to content

Commit e315180

Browse files
AffineScript co-development (round 2): verified Kernel_IO/SecurityRank migration + frontier guide + cross-repo proposals (#532)
## What this is Round 2 of the AffineScript co-development run (round 1 — the verified echo-types soundness proof — merged as #531). This round **practises the idaptik ReScript → AffineScript → wasm migration end-to-end, host-native**, develops the frontier-practices guide, and stages the cross-repo proposals. Writes are affinescript-only; everything targeting another repo is staged under `proposals/` for review. ## Verified this session (every claim was run, not asserted) | Artifact | Outcome | |---|---| | **`Kernel_IO`** — *fresh* re-decomposition | Compiles to wasm (2262B); `main()` logic **6/6 green** through the wasm. The re-decomposition *deletes* the `String.startsWith` step — compares Int arrays directly, so no string crosses the boundary. | | **`SecurityRank`** — existing port + proof tie-in | Differential parity **78/78 ALL GREEN** vs a JS oracle; its out-of-band clamp is certified as controlled loss by the echo-types `clamp-sentinel-no-section` theorem (demonstrated live: `rank(-5)=rank(0)=0`, `rank(99)=rank(3)=3`). | | **`Kernel_Quantum`** — honest non-migration | Documented as **compiler-gated** on the effect-codegen wall (module `Dict` state + `Date.now` + `Console.log`), with the re-decomposition path it needs. | ## What's in the diff - **Guide (first-class, `docs/guides/`)** — the co-processor integerization chapter in `migration-playbook.adoc` (+444), `frontier-guide.adoc` Chapter 9 (+145), and the `AI.a2ml` machine companion (+76). Dual audience: human *and* agent. Six named patterns, each grounded in a verified example. - **`proposals/idaptik/`** — the two migrations + the non-migration, source embedded (AGPL, since idaptik is AGPL-3.0-or-later; kept out of the MPL tree as standalone files). - **`proposals/toolchain/`** — six compiler findings: the variable-string-backend gap (#1 leverage), compiler-emitted `@boundary` Echo obligations, `--deno-esm` to retire the TS harnesses, the stale-harness finding (TS + retired podman container), github-only-network portability, release-cadence coupling, plus the interpreter/codegen divergence bonus finding. - **`proposals/standards/`** — drafted normative text (AS-MIG-1/2, AS-ABI-STR-1, EST-CI-NET-1, the `--deno-esm`/TS-0 exemption amendment, EST-REL-PIN-1). ## Lessons surfaced Re-decompose-don't-transliterate (sometimes that means *deleting* the String); affine types forbid casual aliasing of owned arrays; the interpreter and wasm codegen **disagree** on a `let mut` self-reassignment (verify through the wasm); host-native compile vs the retired container; and the two compiler walls (string backend, effect codegen) **are** the migration frontier — IO and Quantum are gated on exactly those. ## Toolchain Built entirely against the **github-only network policy** (crates.io / deno.land / opam.ocaml.org all 403): agda+stdlib, ocaml/dune (apt, no opam), deno (GitHub release), panic-attack — all installed and verified by routing around the block. Draft for review. https://claude.ai/code/session_01WoKhFQePiRsAj7aqnxbG8s --- _Generated by [Claude Code](https://claude.ai/code/session_01WoKhFQePiRsAj7aqnxbG8s)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent cf0fcdf commit e315180

7 files changed

Lines changed: 1712 additions & 23 deletions

File tree

docs/guides/frontier-guide.adoc

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// SPDX-License-Identifier: MPL-2.0
22
= AffineScript Frontier Guide: The Unveiling
33
Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4-
v1.0, 2026-04-11
4+
v1.2, 2026-06-04
55
:toc:
66
:icons: font
77
:source-highlighter: rouge
8-
:revnumber: 1.0
9-
:revdate: 2026-04-11
8+
:revnumber: 1.2
9+
:revdate: 2026-06-04
1010

1111
[NOTE]
1212
====
@@ -444,6 +444,141 @@ A practical implication for translators: identify your target's tier
444444
writing kernel-shaped code from the first line; sum types and effects
445445
won't be available, and that should drive the design.
446446

447+
== Chapter 9: The Co-processor and the Integer
448+
449+
You already know the problem, even if you have not named it. You have a
450+
working ReScript or JavaScript program — a game, a simulator, a tool — and
451+
you want the *logic* to run with AffineScript's guarantees, compiled to
452+
wasm, without rewriting the rendering, the input handling, or the network.
453+
The instinct is to port the whole thing. The better move is to split it.
454+
455+
**AffineScript is the brain; JS/Pixi are the senses.** The wasm core does
456+
logic, physics, decisions, state machines; the host does rendering, input,
457+
strings, and dispatch. The two halves communicate by passing **raw
458+
primitives** — Ints, Floats, Arrays — across the wasm boundary. Nothing
459+
stringly-typed crosses; nothing stateful crosses; only numbers.
460+
461+
This chapter unveils the mental model. The systematic, verified
462+
re-decomposition patterns live in the
463+
link:migration-playbook.adoc#coprocessor-integerization[Migration Playbook's
464+
co-processor section] — read that when you are actually carrying a codebase
465+
across.
466+
467+
=== The integer IS the X
468+
469+
The keystone idea: a value drawn from a small, named domain — a security
470+
rank `Open/Weak/Medium/Strong`, a device-port name, a tile kind — is
471+
**encoded to a canonical integer on the host side**, and the wasm core
472+
computes on that integer. The host keeps the names; the core gets the code.
473+
474+
[source,affine]
475+
----
476+
// The encoding IS the rank. 0 = Open, 1 = Weak, 2 = Medium, 3 = Strong.
477+
// The host owns "Open" <-> 0; this core owns the arithmetic.
478+
pub fn rank_security_level(v: Int) -> Int {
479+
if v < 0 { 0 } else { if v > 3 { 3 } else { v } }
480+
}
481+
482+
pub fn passes_min_security(actual: Int, required: Int) -> Bool {
483+
rank_security_level(actual) >= rank_security_level(required)
484+
}
485+
----
486+
487+
There is no `String` here, and no sum type crossing the boundary. The four
488+
rank *names* never enter wasm; only their codes do. Compiled host-native,
489+
this is a tiny module — in idaptik's case **454 bytes**, whose only WASI
490+
import is `fd_write`. Every decision the host used to phrase in terms of
491+
rank *names* is re-phrased as integer arithmetic on rank *codes*. That is
492+
the whole pattern, and its slogan is _"the integer IS the X."_
493+
494+
=== Why a co-processor, and not a rewrite
495+
496+
Three things fall out of the split that a full rewrite would not give you:
497+
498+
. **The boundary is narrow and auditable.** Only primitives cross. You can
499+
enumerate exactly what passes between brain and senses, because it is a
500+
short list of Ints and Floats — not an open-ended graph of objects.
501+
. **The core is small and verifiable.** A pure-integer kernel has no
502+
strings to allocate, no state to corrupt, no effects to handle. It
503+
compiles to a handful of kilobytes and its behaviour is a function of its
504+
inputs — which is exactly what makes <<chapter-9-parity,differential
505+
parity>> (below) a usable acceptance test.
506+
. **The senses stay where they are good.** Rendering, input, and the DOM
507+
remain in the host, which already does them well. You are not reimplementing
508+
Pixi in AffineScript; you are giving Pixi a verified brain to call.
509+
510+
[NOTE]
511+
====
512+
This is the operational form of the WASM Co-processor Model described in the
513+
link:frontier-programming-practices/Human_Programming_Guide.adoc[Frontier
514+
Programming Practices] guide. That guide states the posture; this chapter and
515+
the Playbook show you how to *port into it*.
516+
====
517+
518+
[#chapter-9-parity]
519+
=== How you know it worked: differential parity
520+
521+
A co-processor port has a crisp acceptance bar, and it is *not* "the compiler
522+
accepted it." It is two conditions, both required:
523+
524+
* **Build-green** — the `.affine` compiles to wasm, host-native
525+
(`affinescript compile FILE -o OUT.wasm`).
526+
* **Parity-green** — the wasm output matches an **independent oracle** — a
527+
separate implementation written from the *contract*, not from the
528+
`.affine` — across a sweep of inputs, including out-of-band ones.
529+
530+
idaptik's `SecurityRank` core cleared this bar at **78/78** against a JS
531+
oracle of the documented `0..3` contract: every input, in-band and
532+
out-of-band, agreed. Build-green alone would only have proved the compiler
533+
ran; parity-green proves the kernel is *right*.
534+
535+
=== The honest edges
536+
537+
The co-processor model is sharp because its limits are sharp, and the limits
538+
are teaching points, not apologies:
539+
540+
* **Strings have an ABI, so avoid crossing them.** When a string genuinely
541+
must cross, there is a real wire format (`[len: i32 little-endian][utf8]`)
542+
and an exported allocator to honour — see the Playbook. The cheapest way
543+
to handle a string at the boundary is to keep it host-side and send a code
544+
instead (the integerization above). Sometimes the data was *never* a
545+
string — idaptik's `Kernel_IO` does a sandbox prefix check on bytes that
546+
are already an `Int` array, so it compares integer arrays directly and
547+
never builds a `String` at all.
548+
* **No code runs at module load.** AffineScript modules have no load-time
549+
side effect to hook. ReScript that mutates a module-level cell, reads
550+
`Date.now()`, or calls `Console.log` ambiently has nowhere to put that
551+
after the port — those capabilities must be *threaded in as parameters*
552+
(or supplied by `extern` host functions) until effect-handling codegen
553+
lands. A kernel that relies on module-load effects will not compile to
554+
wasm today; that is the compiler telling you to make the dependency
555+
explicit.
556+
* **The faithfulness is provable.** "The integer IS the X" is not folklore —
557+
it is an encoding-faithfulness theorem, machine-checked in
558+
link:../../proposals/echo-types/README.adoc[`EchoEncodingFaithfulness.agda`].
559+
An *injective* encoder is lossless; a *clamp* is provable, localised loss
560+
with no decoder. SecurityRank's clamp is exactly that certified hazard —
561+
and which *direction* it clamps (garbage up to `Strong` is fail-open;
562+
garbage down to `Open` is fail-closed) is a security decision the proof
563+
surfaces but does not make for you.
564+
565+
=== Try it
566+
567+
Sketch the boundary for a different domain before reading the Playbook.
568+
Suppose the host has tile kinds `Empty | Wall | Door | Water`:
569+
570+
. Choose the integer encoding (`Empty = 0`, `Wall = 1`, …). Write it down —
571+
that table is the contract.
572+
. Write a pure-integer AffineScript function `is_passable(tile: Int) -> Bool`
573+
that the host can call per tile. No `String`, no sum type crosses.
574+
. Name one out-of-band input (a negative code, or `99`) and decide — *as a
575+
game-design question* — what `is_passable` should return for it, and
576+
whether that default is fail-open or fail-closed.
577+
578+
You have just done the three hardest parts of a co-processor port: the
579+
encoding, the pure kernel, and the out-of-band policy. The Playbook turns
580+
the sketch into a compiled, parity-checked module.
581+
447582
== Putting It Together: A Complete Program
448583

449584
[source,affinescript]
@@ -591,4 +726,8 @@ This document is intended to evolve. When you change it — adding a chapter, sh
591726
(5) New Appendix — *Bringing in Target Intrinsics* — documents the stub-as-intrinsic pattern that lets user code call CUDA / ONNX / Faust / Verilog runtime ops without extending the language.
592727
No removals; existing v1.0 content unchanged.
593728

729+
| 1.2
730+
| 2026-06-04
731+
| New Chapter 9 — *The Co-processor and the Integer* — unveils the ReScript → AffineScript → wasm co-processor mental model ("AffineScript is the brain; JS/Pixi the senses; only primitives cross the boundary") and the "the integer IS the X" encoding idea, grounded in idaptik's verified 454-byte `SecurityRank` core (78/78 differential-parity). The chapter introduces the model and the honest edges (String ABI, no module-load effects, machine-checked faithfulness via `EchoEncodingFaithfulness.agda`); the systematic re-decomposition patterns live in the link:migration-playbook.adoc#coprocessor-integerization[Migration Playbook §Co-processor Integerization] (playbook v1.4). No removals; existing v1.1 content unchanged.
732+
594733
|===

0 commit comments

Comments
 (0)