// SPDX-License-Identifier: CC-BY-SA-4.0
// SPDX-FileCopyrightText: 2024-2026 hyperpolymath
= Estate -surface elimination — authoritative ledger (issue #229)
:toc: macro
:toclevels: 3
:icons: font

[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
link:CAPABILITY-MATRIX.adoc[CAPABILITY-MATRIX.adoc]; the coordination ledger
remains link:TECH-DEBT.adoc[TECH-DEBT.adoc]; the spine remains
link:ECOSYSTEM.adoc[ECOSYSTEM.adoc]. Reproducer:
`tools/estate-rs-audit/`.
====

toc::[]

== What #229 is

A subset of estate `.affine` files are unfinished →AffineScript
hand-ports: the file declares itself AffineScript (`module …;`) but the body
still carries  surface syntax. Such a file is neither valid 
nor valid AffineScript — debt in limbo that cannot be oracle-certified. The
committed goal is *zero  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/`.

[cols="2,1,4"]
|===
|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  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)

[cols="2,1,4"]
|===
|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.affine` — `List(X)` +
record sigil (layered)
|`git-scripts` |1 |`src/ui/tea/git_scripts_gui.affine` — `List(X)` +
record sigil (layered)
|`game-server-admin` |1 |`src/ui/tea/gsa_gui.affine` — `List(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- 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)

[cols="2,2,3"]
|===
| |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.
| 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)

[cols="2,3"]
|===
| |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.*` / `.*` | 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.

[cols="1,2,4"]
|===
|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.t` → `dict` 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 
*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.

. *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 `{ }`) +  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).
. 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 `{`→`#{`,  string `+`→`++`,
 `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:*  `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 
  `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//src/{A2ml,Json}.affine`: inside a
  `/` *ecosystem* folder → policy hands-off (as `.res`), not a
  port target.

=== Gated remainder — precisely bounded (not open-ended)

[cols="2,1,4"]
|===
|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.
* link:ECOSYSTEM.adoc[ECOSYSTEM.adoc] — the spine; #229 is its estate-port arm.
* link:TECH-DEBT.adoc[TECH-DEBT.adoc] — coordination ledger.
* #228 / link:specs/SETTLED-DECISIONS.adoc[ADR-014] — the unblocker.
