Skip to content

Latest commit

 

History

History
332 lines (280 loc) · 15.5 KB

File metadata and controls

332 lines (280 loc) · 15.5 KB

Estate ReScript-surface elimination — authoritative ledger (issue #229)

Important

This document is authoritative for issue #229: the post-#228 re-audit numbers, the true RS-surface scope, the language-grounded canonical RS→AffineScript map, the escalation set (constructs with no clean target), and the per-repo port plan. It is the #229 analogue of the front-loaded ledger established by #239 for the spine. Per-feature readiness remains CAPABILITY-MATRIX.adoc; the coordination ledger remains TECH-DEBT.adoc; the spine remains ECOSYSTEM.adoc. Reproducer: tools/estate-rs-audit/.

What #229 is

A subset of estate .affine files are unfinished ReScript→AffineScript hand-ports: the file declares itself AffineScript (module …;) but the body still carries ReScript surface syntax. Such a file is neither valid ReScript nor valid AffineScript — debt in limbo that cannot be oracle-certified. The committed goal is zero ReScript surface in any estate .affine. #229 was blocked on #228 (ADR-014, module-qualified type/effect paths) because the oracle stops at the first parse error: qualified-path faults masked each file’s full RS inventory. #228 landed on main via #241 — this re-audit is the now-unblocked step 2.

Post-#228 re-audit (authoritative)

Oracle = affinescript main with #241/ADR-014 (parse-equivalent to the int01-178-xmod build: zero lib/+bin/ delta vs origin/main). Same cached estate corpus as the pre-#228 baseline (controlled: only the oracle changed). Harness + raw data: tools/estate-rs-audit/.

Class n Meaning

PASS

552

parses + typechecks on the oracle

DRIFT-SYNTAX

491

first line is a parse/syntax error

TYPE-ONLY

133

parses; fails later (resolution/type) — not a syntax port

total

1176

.affine across 28 estate repos

Zero class-delta vs the superseded #231 postfix — #241 reproduces ADR-014 parse behaviour exactly; #231 was superseded for implementation reasons, not behaviour. This table is now the post-#228 baseline of record.

The honest scope correction

Warning

DRIFT-SYNTAX (491) is not the #229 workload. Most estate DRIFT is non-RS syntax drift — out of #229’s contract by design. The true #229 scope is the files that carry a detected ReScript construct in tools/estate-rs-audit/data/rs-inventory.tsv: ~84 files across 12 repos, two of which hold 71%.

True RS-surface scope (excludes the affinescript repo’s own intentional negative fixtures)

Repo RS files Shape

burble

32

Whole-repo RS port; richest construct mix (e.g. client/lib/src/BurbleClient.affine carries 7 RS construct families). Not module-import-coupled — proceeds first.

idaptik-dlc-vm

28

Uniform import … as VM dispatch. Gated on the INT-01 #178 qualified-value resolver (see §"Cross-unit gating").

standards

4

developer-ecosystem

4

stapeln

3

proof-burrower

3

idaptik

3

bofj-kitt

3

invariant-path

1

src/ui/tea/invariant_path_gui.affineList(X)
record sigil (layered)

git-scripts

1

src/ui/tea/git_scripts_gui.affineList(X)
record sigil (layered)

game-server-admin

1

src/ui/tea/gsa_gui.affineList(X) + record sigil (layered)

total

~83

over 11 repos (panll removed — see note)

Warning

The text-scan inventory is a lower-bound triage signal, not the true per-file inventory. The oracle stops at the first parse error, so a file’s deeper RS layers are invisible until earlier ones are removed (e.g. the _gui.affine files scan as "`List(X)` only" but in fact also need the #{ record sigil beneath it). And the scanner has false positives: mutable-field matched a *comment in panll/src/ui/tea/wizard.affine ("no shared mutable state") — panll carries no listed RS construct; its real first fault is a trailing comma in an enum variant list, which is not a #229 named construct. panll is reclassified out of #229 scope. The true per-file inventory only emerges by iterative oracle-peeling during each port — which is exactly why #229 mandates per-repo, oracle-validated, human-gated ports rather than one blind sweep.

Non-RS DRIFT (NOT #229 scope — recorded so it is not mistaken for it)

standards 112, proof-burrower 98, developer-ecosystem 97, bofj-kitt 96, affinescript 84 (its own negative fixtures), airborne-submarine-squadron 13, accessibility-everywhere 13, idaptik 9, stapeln 6, burble 3, singletons elsewhere. These DRIFT for non-ReScript reasons; they belong to separate workstreams, not #229.

Construct frequency (estate, excl. affinescript)

import-as 31 · mutable-field 29 · rs-generic<> 26 · array<T> 17 · %%raw 14 · labelled-(~x) 12 · List(X) 7 · JSON.t 7 · Dict.t 6 · rs-stdlib 4 · open-Mod 4 · type-rec 3.

The canonical RS→AffineScript map (language-grounded)

Derived from the language side — grammar / spec v2.0 / stdlib — not guessed. Every target form carries its grounding citation. Four tiers.

Tier 1 — Mechanical (clean grammar target; scriptable, oracle-revalidated)

ReScript AffineScript Grounding

expression-position record literal { f: v }#{ f: v }

the record sigil #{…}

The dominant estate blocker — and NOT in 229’s named construct set. docs/spec.md:414-421 prescribes exactly this rewrite ("rewrite each expression-position record literal {{; leave struct/type declaration bodies"); examples spec.md:901,1320. Oracle-verified (port-proven): bare {x:1} expr-literal parse-errors, {x:1} passes. Strictly expression-literal-only — record patterns in match ({ a: x } ⇒) and struct/enum/type decl bodies stay { } (oracle: {-pattern parse-errors, { }-pattern passes; earlier "applies to patterns" claim was wrong, corrected from doing the ports). Position-aware codemod (standalone-{-then-field:#{), not naive regex; record patterns/decls untouched.

ReScript string ` → `+

++

Concat is ` (`stdlib/string.affine:98` `a b); ` is numeric — `"x" + s` left a `Unify(String,Int)` *beneath* the parse wall. Oracle-verified: `"a" + "b" passes, "a" + "b"String/Int. String-literal-adjacent only (numeric +/- untouched). Another layered RS-ism the text-scanner cannot see — found by oracle-peeling the quick-win ports.

List(X)

[X]

list type is [T]; oracle-verified List(Int) parse-errors, [Int] passes. stdlib/collections.affine, stdlib/prelude.affine

lowercase array<…> / option<…> / result<…>

[…] / Option[…] / Result[…] (capitalise the type name)

The RS tell is the lowercase type name, not the angle brackets. Oracle-verified: Option<Int> passes — both <> and [ ] type-application parse (lib/parser.mly:486). Capitalising the name is the fix; bracket style is free choice ([ ] canonical per spec v2.0 / stdlib/traits.affine:89)

open Mod

use Mod::*;

lib/parser.mly:172 ImportGlob

type rec t = …

type t = …

AS type/enum decls are self-referential by default (stdlib/prelude.affine:19-21); drop rec

Note

The record-sigil row was discovered by oracle-peeling the four allegedly "quick-win" _gui.affine files: each is a *layered port (List(X) was only the first wall; #{ record literals + record patterns lie beneath). This corrects two earlier overclaims: (a) angle-brackets are not RS surface; (b) there are no trivial single-construct quick wins — even one-RS-flagged files are multi-layer.

Tier 2 — Semantic redesign (maps onto the affine model; per-case, NO blind codemod)

ReScript Why it is not mechanical

mutable record field

AffineScript is immutable-by-default with explicit mutation via ownership (docs/spec.md:38; mut τ docs/spec.md:563). There is no mutable field keyword; each record is re-expressed against the ownership model — a redesign, reviewed per record.

(~x) ⇒ labelled args

No labelled/named-argument syntax in the grammar or stdlib (stdlib is uniformly positional, e.g. stdlib/option.affine). Rewrite to positional — a call-convention change, reviewed per call site.

Belt. / Js. / Rescript.*

ReScript stdlib; no 1:1 AffineScript target. Per-symbol remap to the estate stdlib, semantic.

Tier 3 — Escalate as a language-side issue/ADR (bidirectional evidence — the #228 discipline)

These have no clean AffineScript target today. Per #229 step 3 they are escalated language-side rather than patched divergently across N repos — the same discipline that produced #228.

Esc Construct Finding

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) — LANDED

Dict.t (6)

stdlib/dict.affine: keyed container over the assoc-list shape [(String, V)] (the representation json::JObject already uses) — empty/from_pairs/get/contains/ size/insert/set/remove/keys/values; AOT-gated (#136). Dict.tdict ops over [(String, V)]. (#162 / STDLIB-03; the Http.affine:16 headers upgrade is now unblocked, additive.)

Tier 4 — Cross-unit gating (a sequencing finding, language-grounded)

import X as Y ports to use X as Y;. That parses and the alias registers (lib/parser.mly:168 ImportSimple(path, Some alias); lib/resolve.ml:787-797 registers the alias on lookup_qualified success). But qualified-value call sites Y.fn(x) hit the post-#228 INT-01 #178 qualified-value resolution gap (lib/resolve.ml:719,797 UndefinedModule) — the exact gap recorded as the next spine unit.

idaptik-dlc-vm (28 files, 33% of #229 scope) is gated on the INT-01 #178 qualified-value resolver. burble (rs-generic/array/mutable/%%raw-dominated, not module-coupled) and the three smallest non-coupled repos (git-scripts, game-server-admin, invariant-path) are not gated and proceed first. This does not reorder the mandate (#229 foundation first, as instructed); it is the foundation’s own finding that the idaptik-dlc-vm slice of the per-repo work naturally sequences after INT-01 #178.

Per-repo port plan

Each repo: oracle-validate locally first (estate CI does not compile .affine), one branch + squash-merge PR per repo, noreply author, Refs #229 (multi-repo, sequenced, human-gated — never Closes). Per-repo hands-off confirm before touching: policy hands-off is the ReScript ecosystem (.res); .affine-with-RS is an in-scope unfinished port — but confirm per repo it is an intended AffineScript target, not a deliberate interop artefact (burble’s standing caveat), before removal.

  1. Smallest non-coupled — DONE 2026-05-19: git-scripts (PR 9), game-server-admin (PR #14), invariant-path (PR #4). One file each; layered: List(X)[X] + expr record literals {{ (patterns/decls left { }) + ReScript string `→`+ (the latter two oracle-peeled, not scanner-visible). Each oracle-validated "Type checking passed" on main. panll excluded (no listed RS construct — scanner FP; see the lower-bound-triage warning).

  2. See §#229 closure status for the truthed disposition of burble, the small tail, and idaptik-dlc-vm.

#229 closure status (post-port truthing, 2026-05-19)

Doing the ports collapsed the apparent scope. The honest end-state:

Completable per-file ports — DONE

git-scripts 9 · game-server-admin #14 · invariant-path #4 · standards/session-management-standards/…​/system_update_gui.affine #142. Each oracle-validated "Type checking passed" on main. Transform: List(X)[X], expr record literals {{, ReScript string `→`+, ReScript import P [as A]use P [as A];, single block-module module P { body }module P;-header-first + dedented body.

Canonical-map additions (from doing the ports)

  • Structural Tier-1: ReScript import P[.Q] [as A]use P.Q [as A]; (parser.mly:166-169); single block-module module P { body }module P; header (hoisted before imports — parser.mly:130-134 requires the header first), braces dropped, body dedented one level. Oracle-verified to parse.

  • ESC-04 (#262) — SETTLED by ADR-017: AffineScript is strictly one-module-per-file. Doctrine: split, do not nest — each ReScript module X { body } (incl. each of N in a multi-block file like standards/lol/…​/OpenCyc.affine) becomes its own X.affine with a module X; header. No grammar change (ADR-011 file=module; a block-module form is the ADR-012-forbidden contortion). Full validation still needs the per-repo module graph (INT-02, Tier-4).

Reclassified OUT of #229 — permanently (scanner over-scope)

  • developer-ecosystem, proof-burrower, bofj-kitt: every flagged file is a vendored copy of the affinescript repo itself (affinescript/stdlib/effects.affine, affinescript/tests/conformance/run_all.affine, …/docs/guides/warmup/02_ownership.affine); the mutable hits are comments ("// State effect - mutable references", "// ── mut …"). Same discipline as the affinescript-own-corpus exclusion + the panll FP. 0 real #229 files in these three repos.

  • standards/a2ml/prototype/rescript/src/{A2ml,Json}.affine: inside a rescript/ ecosystem folder → policy hands-off (as .res), not a port target.

Gated remainder — precisely bounded (not open-ended)

Repo n Gate

idaptik-dlc-vm

28

RS surface mechanically eliminated (block-module + import-as transforms verified to parse), but every file then hits Resolve.UndefinedModule — the module-path↔file-layout (src/State.affine declaring module Vm.State;) does not match the loader. Cross-module graph coherence = INT-02 loader-bridge, explicitly out of #229’s per-file contract. Not a per-file RS issue.

burble

32

Tier-3 dominated: %%raw (ESC-01 #245), JSON.t (ESC-02 #246), Dict.t (ESC-03 #247), plus block-modules (ESC-04 #262) and Tier-2 mutable/labelled. Caveat cleared, but not completable until the ESCs land.

stapeln

3

module …; already file-header; not per-file completable — UndefinedVariable (AuthTypes), Unify(String,Int) (Metrics), parse-error 84 + use Json; (PolicyEngine, ESC-02-adjacent). Cross-module / deeper, not mechanical RS surface.

idaptik

3

%%raw (Main/StartupError) → ESC-01 #245; Boot = mutable comment-FP.

The remaining #229 work is now fully attributed to ESC-01..04 (#245/#246/#247/#262) + the INT-02 cross-module loader/layout concern — no unbounded "tail". This is the "once and for all" closure: completable done, false scope removed, residue precisely gated.

Reproduce / see also

  • tools/estate-rs-audit/ — harness + captured data + how-to.

  • ECOSYSTEM.adoc — the spine; #229 is its estate-port arm.

  • TECH-DEBT.adoc — coordination ledger.

  • #228 / ADR-014 — the unblocker.