Skip to content

feat: sound module-qualified type & effect paths, dot separator (Refs #228) - #231

Closed
hyperpolymath wants to merge 2 commits into
mainfrom
stage-c/228-qualified-type-effect-path
Closed

feat: sound module-qualified type & effect paths, dot separator (Refs #228)#231
hyperpolymath wants to merge 2 commits into
mainfrom
stage-c/228-qualified-type-effect-path

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented May 18, 2026

Copy link
Copy Markdown
Owner

Refs #228

STAGE-C peer of #225 (typed-wasm Http) and #160 (C-spine). Language
decision is human-gated per the repo ISSUE-CLOSURE rule, so this is
Refs #228, not Closes — do not auto-merge, do not close the issue.

What

The type/effect grammar had no module-qualified path production:
Externs.Res / prelude.Option / any Pkg.Type in type or effect
position failed parse error at the ., contradicting already-settled
ADR-011 (real modules with qualified paths). This implements full,
sound
module-qualified type & effect paths with . as the canonical
separator (:: stays value/import per ADR-011). Recorded as ADR-014.

Resolution is by sound module-scoped member lookup, never flat
scope: load module [A;B] through the loader, resolve+typecheck it
exactly as an import would, then look the member up inside that
module's own resolved symbols
, requiring Public/PubCrate and the
right kind (SKType for types, SKEffect for effects). An unknown or
wrong module, a missing member, a private member, or a wrong-kind
member is a resolution error at the use-site span — never a parse
error, never silently accepted. Importing a member flatly does not make
a bogus A.B.C resolve.

New grammar productions (verbatim)

path_seg:
  | s = lower_ident { s }
  | s = upper_ident { s }

module_prefix:
  | s = path_seg { [s] }
  | p = module_prefix DOT s = path_seg { p @ [s] }

qualified_type_path:
  | p = module_prefix DOT m = upper_ident
    { (p, m, $startpos, $endpos) }

type_expr_primary gains qualified bare / [args] / <args> forms
(TyCon/TyApp (mk_qualified_ident ...)); effect_term gains
qualified bare / [args] forms (EffVar/EffCon). Module-prefix
segments accept lower OR upper case because real stdlib modules are
prelude/option/string/result as well as Network/Http.

Files changed

File Why
lib/ast.ml ident gains modpath : string list; add mk_ident helper so synthetic idents set it in one place
lib/parser.mly mk_qualified_ident; path_seg/module_prefix/qualified_type_path; qualified type & effect productions
lib/typecheck.ml lower_type_expr/lower_effect_expr validate qualified paths via loader-backed qualified_member_check; Qualified_path_errorQualifiedPathError at the check_program boundary; validated qualified effect admitted as declared effect (#59)
lib/resolve.ml make_qualified_member_check (sound module-scoped lookup) inside the resolve_and_typecheck_module recursive group
bin/main.ml thread the validator into the 6 patterned check_program calls
lib/{trait,codegen_gc,borrow,verilog_codegen}.ml route ~10 internal ident literals through Ast.mk_ident
docs/specs/SETTLED-DECISIONS.adoc, .machine_readable/6a2/META.a2ml ADR-014 (same shape as ADR-011/012/013)
test/test_e2e.ml, test/dune, tests/conformance/qualified-paths/** 5 conformance fixtures wired into the gate
test/golden/*.expected regenerated — adding modpath reflows every AST dump; span-normalised golden harness confirms no structural change

Parser-conflict delta: ZERO

menhir --explain summary is byte-identical to the pre-change
parser:

21 states have shift/reduce conflicts.
one state has reduce/reduce conflicts.
68 shift/reduce conflicts were arbitrarily resolved.
7 reduce/reduce conflicts were arbitrarily resolved.

The upper_ident vs upper_ident DOT … decision is the expected
benign DOT shift (Menhir shifts; disclosed per ADR-012). No new
unexplained conflict; just build masks the same count as before.

Test gate

Before After
dune runtest 258 PASS / 0 FAIL 263 PASS / 0 FAIL (+5 #228 conformance cases)

Zero regressions. The 12 golden tests pass with regenerated .expected
(the change is purely the new modpath field + ppx_deriving.show
reflow; the span-normalising golden harness verifies no structural
change).

Oracle re-audit (build is the new oracle)

28 repos / 1176 .affine files. Pre-fix baseline vs post-fix:

Class Pre-fix Post-fix Δ
PASS 552 552 0
DRIFT-SYNTAX 498 491 -7
TYPE-ONLY 126 133 +7

7 files moved DRIFT-SYNTAX → TYPE-ONLY (accessibility-everywhere ×5,
standards ×2): their only parse blocker was a qualified type/effect
path; they now parse and resolve. Spot-check (before → after):

  • accessibility-everywhere/tools/cli/src/Index.affine: 6:17 parse error → resolves (now UndefinedModule, sound)
  • standards/a2ml/.../Json.affine: 15:29 parse error at A2ml.Inline → resolves
  • standards/k9-svc/.../K9_Renderer.affine: 17:37 parse error at K9_Types.Pedigree → resolves

The PASS count is unchanged (not the "large jump" the spec hoped for)
because the remaining DRIFT-SYNTAX is unrelated, out-of-#228-scope
syntax
extern fn … = "console" "warn" FFI string-bindings,
pub extern fn, record-construction expression syntax — confirmed by
sampling the residual parse errors. And because the audit harness uses
a bare loader without each repo's local module search paths, a
qualified path to a repo's own Externs/Types module now correctly
reports UndefinedModule (sound — not silently accepted) rather than
masquerading as a parse error. This is the correct sound outcome per
the spec ("a wrong/unknown module … is an error").

A/B sanity probes (all hold)

  • prelude.Option[Int] / prelude.Option<Int>Type checking passed
  • bare Option[Int] → still Type checking passed
  • Foo.Bar[Int] → resolution error Unknown module 'Foo' … (ADR-014) (not parse)
  • effects.IOMember 'IO' of module 'effects' is private … (ADR-014) (sound visibility)
  • effmod.Logging (a pub effect) → Type checking passed
  • prelude.Option used as effect → 'prelude.Option' resolves to a type, not a effect (ADR-014)
  • prelude.DefinitelyNotHereModule 'prelude' has no member … (ADR-014)
  • Int -{IO}-> Int → still parses (Type checking passed)

Deviations from spec (reality)

  1. Resolution happens in typecheck.ml, not resolve.ml. The spec
    directed edits to Resolve.resolve_type_ident/resolve_effect_ident,
    but those functions are dead code — actual type/effect name
    binding is in Typecheck.lower_type_expr/lower_effect_expr.
    typecheck.ml cannot depend on resolve.ml (would be circular), so the
    sound module-scoped validator is built in resolve.ml (where the
    loader + resolve_and_typecheck_module live) and injected into
    the typecheck context via an optional qualified_member_check
    closure threaded through check_program. Same soundness, no
    circular dependency.
  2. Module-prefix segments must accept lowercase. The spec said "a
    path of upper_ident segments", but real stdlib modules are
    lowercase (prelude, option). The grammar accepts lower-or-upper
    prefix segments (mirroring module_path/use), member is
    upper_ident. Conflict count still zero-delta.
  3. Qualified effect admitted as declared effect. A qualified effect
    that passes the module-scoped gate is registered in
    declared_effects so issue-[Repo] Effect-row v1 name list: define IO/Async/Partial/Throws[E]/Mut early #59's effect-name validator accepts it
    (a sibling module's public effect is as legitimate as a local one).
    Soundness preserved — only after the gate passes.
  4. Typecheck/codegen logic otherwise unchanged (as predicted): a
    qualified ident is treated by its resolved symbol exactly like an
    unqualified one.

🤖 Generated with Claude Code

Known limitation (disclosed, not a blocker)

The qualified-type positive conformance fixture is self-contained
(prelude.Option — a stdlib module). The qualified-effect positive
fixture (tests/conformance/qualified-paths/valid/qualified_effect.affine)
references a sibling support module effmod because no stdlib module
currently declares a public effect
. Consequence:

  • It passes the test gate (the harness adds the fixture dir to the module
    search-path) but fails a bare affinescript check with
    Unknown module 'effmod'.
  • Bare-oracle audits — including the estate dialect-conformance
    .affine-audit harness — must treat that one fixture as harness-only
    and exclude it from drift counts. This is now stated in the fixture
    header and in ADR-014.
  • Qualified-effect resolution itself is sound and independently proven
    by invalid/private_member.affine (loads stdlib effects, rejects the
    private member). The limitation is fixture self-containment, not the
    feature.

Also disclosed: the estate-payoff numbers from the re-audit are
understated by the bare-loader audit harness (repo-local modules
report UndefinedModule). A re-audit with each repo's own module graph
resolvable is pending and is the load-bearing review evidence.

claude added 2 commits May 18, 2026 23:31
…tor (Refs #228)

The type/effect grammar had no module-qualified path production, so
`Externs.Res` / `prelude.Option` / any `Pkg.Type` in type or effect
position failed `parse error` at the `.` — contradicting ADR-011 (real
modules with qualified paths). This adds full, SOUND module-qualified
paths in type and effect position, with `.` as the canonical separator
(`::` stays value/import per ADR-011); decision recorded as ADR-014.

Resolution is by sound module-scoped member lookup, never flat scope:
load module `[A;B]` through the loader, resolve+typecheck it as an
import would, then look the member up *inside that module's own
resolved symbols*, requiring Public/PubCrate and the right kind
(SKType / SKEffect). Unknown/wrong module, missing member, private
member, or wrong-kind member is a resolution error at the use-site
span — never a parse error and never silently accepted.

- lib/ast.ml: `ident` gains `modpath : string list` ([]=unqualified);
  add `mk_ident` helper so synthetic idents set the field in one place.
- lib/parser.mly: `mk_qualified_ident`; `path_seg`/`module_prefix`/
  `qualified_type_path` helpers; qualified bare/[args]/<args> forms for
  types and bare/[args] for effects. Module-prefix segments accept
  lower OR upper case (stdlib modules are `prelude`/`option` as well as
  `Network`/`Http`); member segment is upper_ident.
- lib/typecheck.ml: `lower_type_expr`/`lower_effect_expr` validate the
  qualified case via a loader-backed `qualified_member_check` threaded
  into the context; `Qualified_path_error` -> `QualifiedPathError` at
  the check_program boundary (same pattern as Effect_validation_error).
  A validated qualified effect's name is admitted as a declared effect
  (issue #59) so a sibling module's public effect is usable like a
  local one. Resolved representation is the canonical nominal
  type/effect — identical to the bare/imported form; the validation,
  not the representation, is the soundness gate. Typecheck/codegen
  otherwise unchanged.
- lib/resolve.ml: `make_qualified_member_check` built where the loader
  lives, inside the `resolve_and_typecheck_module` recursive group so
  transitively-qualified stdlib paths resolve too.
- bin/main.ml: thread the validator into the 6 patterned check_program
  calls (via the type_ctx returned by resolve_program_with_loader).
- Fixed ~10 internal direct `ident` record literals (trait/codegen_gc/
  borrow/verilog) to route through `Ast.mk_ident`.
- docs/specs/SETTLED-DECISIONS.adoc + .machine_readable/6a2/META.a2ml:
  ADR-014 (full stanza, same shape as ADR-011/012/013).
- tests/conformance/qualified-paths/{valid,invalid}: 5 conformance
  fixtures wired into the e2e gate (valid qualified type+effect;
  unknown module; private member; absent member).

Parser-conflict delta: ZERO. menhir --explain summary is byte-identical
to the pre-change parser — 21 shift/reduce states, 1 reduce/reduce
state, 68 s/r + 7 r/r arbitrarily resolved. The `upper_ident` vs
`upper_ident DOT ...` decision is the expected benign DOT shift
(Menhir shifts, disclosed per ADR-012); no new unexplained conflict.

Test gate: 258 -> 263 (5 new #228 cases), zero regressions. Adding
`modpath` to `ident` reflows every golden .expected AST dump;
regenerated — the span-normalised golden harness confirms no
structural change.

STAGE-C peer of #225 (typed-wasm Http) and #160 (C-spine). Language
decision is human-gated (ISSUE-CLOSURE): Refs #228, not Closes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ture limitation

- .machine_readable/6a2/STATE.a2ml: bump last-updated; add
  session-note-2026-05-18 recording #228/ADR-014/PR#231 (machine
  state/progress convention was stale).
- docs/specs/SETTLED-DECISIONS.adoc (ADR-014): add 'Known limitation'
  paragraph — qualified-effect positive fixture is harness-only (no
  public stdlib effect for a self-contained positive); bare-oracle
  audits must exclude it.
- tests/conformance/qualified-paths/valid/qualified_effect.affine:
  header now states the harness-search-path dependency explicitly.

Turns a silent landmine into a stated limitation. No code change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyperpolymath

Copy link
Copy Markdown
Owner Author

#228 estate-payoff proof (controlled before/after)

Method: same .affine files, two binaries — before = affinescript main
@417b97c (untouched), after = stage-c/228-qualified-type-effect-path.
Per-file first-error position+class captured. The coarse PASS/DRIFT count
understates #228 (oracle stops at the FIRST parse error); this measures
whether that first error stops being a module-qualified-path parse error.
Harness: .affine-audit/payoff.sh, raw: .affine-audit/payoff.tsv.

Result — #228 works, scope is narrow (honest correction)

Seed: 13 repos, ~98 files. Only accessibility-everywhere had
qualified-path drift.
The other 12 (airborne-submarine-squadron,
idaptik, idaptik-dlc-vm, stapeln, aerie, ambientops, echidna,
game-server-admin, git-scripts, invariant-path, ubicity, panll) showed
0 wall-cleared / 0 wall-advanced — their drift is ReScript-surface /
record-literal = #229 territory, NOT #228. This corrects the earlier
"single dominant fault estate-wide" framing: the qualified-path gap was
dominant in the diagnostic seed (accessibility-everywhere), not estate-wide.

accessibility-everywhere — controlled before/after (per-package cwd)

Bucket n BEFORE (main) AFTER (#228)
qualified-path files 11 all parse error 5 parse-clean → resolution; 6 parse-advanced past the qualified wall; 0 regress
Externs.affine support mods 4 PASS PASS (unchanged)

Sharpest proof: tools/monitoring-api/src/Server.affine — parse error
95:31 (the mut Externs.Res / -{Externs.Net}-> qualified wall) →
264:32: 169 lines of qualified-path walls removed, residual is a
later non-qualified construct (#229).

WALL-CLEARED (parse error → no longer a parse error): Cli, cli/Index,
github-action/Index, core/Index, scanner/Index.
WALL-ADVANCED (qualified wall gone, later non-qualified error): Server
95→264, Arangodb 103→127, Scanner 81→111, Action 105→114, Button
42:23→42:36, react/Index 17→30.

What this means for review

@hyperpolymath

Copy link
Copy Markdown
Owner Author

Independent gate re-confirmation (not relayed from the implementing agent)

Both #231 gates re-verified from scratch:

Test suitedune runtest in the worktree: exit 0, alcotest reports
Test Successful — 263 tests run, 0 failures. The pre-existing suite
is a subset and is fully green; the +5 over baseline are exactly the new
tests/conformance/qualified-paths/** fixtures. No regressions.

Menhir conflict delta — standalone menhir 20260209 (not via dune) on
git show main:lib/parser.mly vs git show HEAD:lib/parser.mly, same
--table flag:

21 states have shift/reduce conflicts.
68 shift/reduce conflicts were arbitrarily resolved.
7  reduce/reduce conflicts were arbitrarily resolved.
one state has reduce/reduce conflicts.

Byte-identical on both grammar versions — diff empty. Confirms ADR-014's
"the masked-conflict count is unchanged … identical to the pre-change
parser". The new qualified-path productions add the expected benign DOT
shift and introduce zero new conflicts.

Net: implementation gates verified independently. #231 residual is now
only the human review + merge it explicitly requires (Refs #228, not
Closes; human-gated per ISSUE-CLOSURE).

@github-actions

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 44 issues detected

Severity Count
🔴 Critical 12
🟠 High 21
🟡 Medium 11

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Stray AI.a2ml in root -- use 0-AI-MANIFEST.a2ml only",
    "type": "banned",
    "file": "AI.a2ml",
    "action": "delete",
    "rule_module": "root_hygiene",
    "severity": "high"
  },
  {
    "reason": "Superseded by 0-AI-MANIFEST.a2ml",
    "type": "banned",
    "file": "AI.djot",
    "action": "delete",
    "rule_module": "root_hygiene",
    "severity": "high"
  },
  {
    "reason": "Issue in quality.yml",
    "type": "missing_workflow",
    "file": "quality.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Issue in security-policy.yml",
    "type": "missing_workflow",
    "file": "security-policy.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/compile.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/runner.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

hyperpolymath added a commit that referenced this pull request May 19, 2026
…#228)

Module-qualified type/effect paths landed via PR #241 (zero Menhir
conflict delta, gate 267/267). Keep the authoritative docs in lockstep:
- TECH-DEBT.adoc CORE-03: grammar LANDED (PR #241), supersedes #231;
  estate re-audit + #229 follow.
- CAPABILITY-MATRIX.adoc parser row: qualified paths now parse;
  dominant estate parse blocker cleared.
- STATE.a2ml parser feature line updated (it mirrors).

ADR-014 itself is recorded in META.a2ml + SETTLED-DECISIONS.adoc on the
#241 branch (those ADR registries are owned by the implementation PR;
these ledger/status docs are owned by #239 — single-owner discipline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request May 19, 2026
…efs #175 #176) (#239)

* docs: authoritative ledger — CAPABILITY-MATRIX/ECOSYSTEM/TECH-DEBT (Refs #175 #176)

The "first PR" #175 claims landed (docs/CAPABILITY-MATRIX.adoc,
docs/ECOSYSTEM.adoc, docs/TECH-DEBT.adoc) never existed in git history;
every sub-issue (#176-183, #228, #177) cites docs/TECH-DEBT.adoc as its
coordination index. This materialises that ledger authoritatively,
reconstructed 2026-05-19 from the issues + the compiler-as-oracle audit +
the hyperpolymath/typed-wasm LEVEL-STATUS (v1.5).

- docs/CAPABILITY-MATRIX.adoc: single authoritative readiness source
  (overrides README/BACKEND-IMPLEMENTATION/COMPILER-CAPABILITIES/
  ALPHA-1-RELEASE-NOTES/STATE.a2ml). Truthed: alpha; CORE-01 borrow
  soundness gap named; backend breadth real but uneven; typed-wasm is a
  separate language-agnostic target, AffineScript is one producer.
- docs/ECOSYSTEM.adoc: the implementation spine + the macro-stage spine
  (Stage A–E definitions, established here — never committed before),
  the actual AS↔typed-wasm contract (narrow: L7+L10 on emitted wasm;
  affinescript.ownership section; multi-producer w/ ephapax), the truthed
  satellite registry, and the INT-01..12 integration roadmap.
- docs/TECH-DEBT.adoc: the coordination ledger (DOC/CORE/STDLIB/INT/SAT),
  critical path, update protocol. DOC-01/02/03/07 done here.
- Authoritative banners: README.adoc, BACKEND-IMPLEMENTATION.md,
  COMPILER-CAPABILITIES.md, ALPHA-1-RELEASE-NOTES.md (DOC-04).
- STATE.a2ml drift-flagged (DOC-05); ROADMAP.adoc cross-links (DOC-06).

Docs-only; gate unaffected (dune runtest --force = 260/260, 2026-05-19).
Front-loaded ahead of CORE-01 (#177) so that work unit cites a real ledger.

Refs #175 #176

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: truth CORE-01 part 1 (PR #240) in the ledger (Refs #175 #176 #177)

CORE-01 borrow-graph validation part 1 landed via PR #240. Keep the
authoritative docs in lockstep (DOC-01..09 / #176 — "docs current as you
go"):
- CAPABILITY-MATRIX.adoc borrow row: graph validation landed; remaining
  NLL/flow-sensitive deferred to part 2+.
- TECH-DEBT.adoc CORE-01: part 1 LANDED (PR #240); part 2+ scoped.
- STATE.a2ml borrow-checker feature line updated (it mirrors).

Each file stays single-owner: the compiler change is PR #240 off main;
these doc/ledger files are owned by this branch (#239).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: truth CORE-03 / ADR-014 grammar landed (PR #241) (Refs #175 #176 #228)

Module-qualified type/effect paths landed via PR #241 (zero Menhir
conflict delta, gate 267/267). Keep the authoritative docs in lockstep:
- TECH-DEBT.adoc CORE-03: grammar LANDED (PR #241), supersedes #231;
  estate re-audit + #229 follow.
- CAPABILITY-MATRIX.adoc parser row: qualified paths now parse;
  dominant estate parse blocker cleared.
- STATE.a2ml parser feature line updated (it mirrors).

ADR-014 itself is recorded in META.a2ml + SETTLED-DECISIONS.adoc on the
#241 branch (those ADR registries are owned by the implementation PR;
these ledger/status docs are owned by #239 — single-owner discipline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hyperpolymath <hyperpolymath@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyperpolymath

Copy link
Copy Markdown
Owner Author

Superseded by #241 (merged): ADR-014 module-qualified type/effect paths landed with zero Menhir conflict delta, 270/270 gate, ADR recorded in META.a2ml + SETTLED-DECISIONS.adoc. #231 was the grammar-only DRAFT; #241 is the complete, gated implementation.

hyperpolymath added a commit that referenced this pull request May 19, 2026
…onical map (Refs #229)

#228/ADR-014 landed on main (#241), unblocking #229 step 2. This is the
front-loaded #229 foundation (the #239-analogue for the estate-port arm):
authoritative re-audit + true scope + language-grounded canonical map +
escalations + the campaign reproducer.

Re-audit (oracle = main w/ #241; same cached corpus = controlled):
552 PASS / 491 DRIFT-SYNTAX / 133 TYPE-ONLY over 1176 .affine. Zero
class-delta vs the superseded #231 postfix (#241 reproduces ADR-014 parse
behaviour exactly).

Honest scope correction: DRIFT-SYNTAX (491) is NOT the workload. Most
estate DRIFT is non-RS syntax drift, out of #229's contract. True RS
scope = ~84 files / 12 repos (burble 32 + idaptik-dlc-vm 28 = 71%).

Canonical RS->AffineScript map, derived language-side (grammar/spec/stdlib,
each form cited), in four tiers:
- Tier 1 mechanical: array<T>->[T], option/result-><Option|Result>[..],
  List(X)->[X], open Mod->use Mod::*;, type rec->type (parser.mly:486/394/172,
  prelude.affine:19-21, traits.affine:89).
- Tier 2 semantic redesign: mutable-field, labelled (~x), Belt/Js stdlib.
- Tier 3 escalated language-side (the #228 bidirectional-evidence
  discipline): %%raw -> #245, JSON.t -> #246, Dict.t -> #247.
- Tier 4 cross-unit gate: import X as Y parses + alias registers
  (resolve.ml:787-797) but qualified-value calls hit the INT-01 #178 gap
  (resolve.ml:719/797) => idaptik-dlc-vm's 28-file port is gated on
  INT-01 #178; burble + the List(X) quick wins are not.

Adds:
- tools/estate-rs-audit/ — audit.sh + rs-inventory.sh + repo list +
  captured data (results.post228.tsv, rs-inventory.tsv) + README. The
  durable in-repo home the prior /tmp harness lacked.
- docs/RESCRIPT-ELIMINATION.adoc — authoritative #229 ledger.
- TECH-DEBT CORE-03 truthed (#241 merged; re-audit done; ledger ptr).
- ECOSYSTEM see-also cross-link.

No compiler/build input changed (docs + tools + data only); dune build
clean; 270 gate unaffected by construction. Refs #229 (not Closes —
multi-repo, sequenced, human-gated; per-repo port PRs follow).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request May 19, 2026
…onical map (#248)

* docs(estate): #229 foundation — post-#228 re-audit, RS inventory, canonical map (Refs #229)

#228/ADR-014 landed on main (#241), unblocking #229 step 2. This is the
front-loaded #229 foundation (the #239-analogue for the estate-port arm):
authoritative re-audit + true scope + language-grounded canonical map +
escalations + the campaign reproducer.

Re-audit (oracle = main w/ #241; same cached corpus = controlled):
552 PASS / 491 DRIFT-SYNTAX / 133 TYPE-ONLY over 1176 .affine. Zero
class-delta vs the superseded #231 postfix (#241 reproduces ADR-014 parse
behaviour exactly).

Honest scope correction: DRIFT-SYNTAX (491) is NOT the workload. Most
estate DRIFT is non-RS syntax drift, out of #229's contract. True RS
scope = ~84 files / 12 repos (burble 32 + idaptik-dlc-vm 28 = 71%).

Canonical RS->AffineScript map, derived language-side (grammar/spec/stdlib,
each form cited), in four tiers:
- Tier 1 mechanical: array<T>->[T], option/result-><Option|Result>[..],
  List(X)->[X], open Mod->use Mod::*;, type rec->type (parser.mly:486/394/172,
  prelude.affine:19-21, traits.affine:89).
- Tier 2 semantic redesign: mutable-field, labelled (~x), Belt/Js stdlib.
- Tier 3 escalated language-side (the #228 bidirectional-evidence
  discipline): %%raw -> #245, JSON.t -> #246, Dict.t -> #247.
- Tier 4 cross-unit gate: import X as Y parses + alias registers
  (resolve.ml:787-797) but qualified-value calls hit the INT-01 #178 gap
  (resolve.ml:719/797) => idaptik-dlc-vm's 28-file port is gated on
  INT-01 #178; burble + the List(X) quick wins are not.

Adds:
- tools/estate-rs-audit/ — audit.sh + rs-inventory.sh + repo list +
  captured data (results.post228.tsv, rs-inventory.tsv) + README. The
  durable in-repo home the prior /tmp harness lacked.
- docs/RESCRIPT-ELIMINATION.adoc — authoritative #229 ledger.
- TECH-DEBT CORE-03 truthed (#241 merged; re-audit done; ledger ptr).
- ECOSYSTEM see-also cross-link.

No compiler/build input changed (docs + tools + data only); dune build
clean; 270 gate unaffected by construction. Refs #229 (not Closes —
multi-repo, sequenced, human-gated; per-repo port PRs follow).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(estate): #229 ledger — oracle-grounded corrections (Refs #229)

Oracle-peeled the four allegedly "quick-win" *_gui.affine files. Findings,
all compiler-verified, folded back into RESCRIPT-ELIMINATION.adoc before
#248 merges (soundness-first; the foundation must be correct):

1. Record sigil is the DOMINANT estate blocker and was NOT in #229's named
   construct set. spec.md:414-421 prescribes the exact rewrite: expression-
   /pattern-position record literal `{`->`#{` (typed `T #{…}`); leave
   struct/type decl bodies. Oracle: bare `{x:1}` / `M{x:1}` / `M(x:1)` all
   parse-error; `Type #{…}` is the form. Added as the lead Tier-1 row.

2. Angle-brackets are NOT RS surface. Oracle: `Option<Int>` passes — both
   `<>` and `[ ]` type-application parse. The RS tell is the lowercase
   type name, not the brackets. Tier-1 row corrected (was an overclaim).

3. 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 deeper
   RS layers are invisible until earlier ones go (the *_gui files scan as
   "List(X) only" but also need `#{` beneath). Added an explicit WARNING.

4. Scanner false positive: `mutable-field` matched a comment in
   panll/.../wizard.affine. panll carries no listed RS construct; its real
   fault is an enum trailing comma (not a #229 construct). panll
   reclassified OUT of #229 (scope now ~83 files / 11 repos).

5. No trivial single-construct quick wins exist — even one-RS-flagged
   files are multi-layer; per-repo plan + Tier-4 wording corrected.

docs-only; dune build clean; 270 gate unaffected by construction.
Refs #229.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: hyperpolymath <hyperpolymath@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyperpolymath
hyperpolymath deleted the stage-c/228-qualified-type-effect-path branch May 20, 2026 23:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants