From c9a40e99b5951088782ab669493c6fc4f1975900 Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Fri, 23 Oct 2026 20:49:43 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix+test(WO80/A4):=20rulings=20batch=205=20?= =?UTF-8?q?=E2=80=94=20aliases,=20the=20ML=20funcall=20contract,=20the=20s?= =?UTF-8?q?trict-parse=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - aliases: bind named item expressions at parse; ruled usable in both from and to positions (vectors pin both) - ML funcall contract: the 126e8e1 refactor dropped require_secryst!'s body but kept the call — no-model funcalls crashed with an accidental NoMethodError. Restored the require; the failure is now a named Interscript::ML::IMF::RegistryError enumerating known model ids - strict-parse adoption ruling: the gate is SATISFIED — all 295 corpus files parse and build with zero failures on the ruby compiler (ts/py green in CI). The stray-identifier hazard pinned in batch 4 is closable by a parser change in its own PR --- .../stdlib/functions/secryst_adapter.rb | 11 +++ spec/isc/semantics_spec.rb | 70 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/lib/interscript/stdlib/functions/secryst_adapter.rb b/lib/interscript/stdlib/functions/secryst_adapter.rb index f2da15c2..b900fcfa 100644 --- a/lib/interscript/stdlib/functions/secryst_adapter.rb +++ b/lib/interscript/stdlib/functions/secryst_adapter.rb @@ -23,6 +23,17 @@ def translator_for(model_key) end end + # The 126e8e1 split lost this body but kept the call — an ML + # funcall without a provisioned model crashed with an accidental + # NoMethodError instead of a named provisioning error (A4). + def require_secryst! + require "interscript/ml/translator" + rescue LoadError => e + raise LoadError, + "the secryst funcall requires Interscript::ML — " \ + "loading failed: #{e.message}" + end + def build_translator(model_key) Interscript.secryst_index_locations.each do |remote| Interscript::ML::Provisioning.add_remote(remote) diff --git a/spec/isc/semantics_spec.rb b/spec/isc/semantics_spec.rb index 4252a6cb..613e8e5c 100644 --- a/spec/isc/semantics_spec.rb +++ b/spec/isc/semantics_spec.rb @@ -340,3 +340,73 @@ def with_map(name, body) Interscript.load_path.delete(dir) if dir end end +RSpec.describe "ISC semantics — aliases" do + def with_map(name, body) + dir = Dir.mktmpdir + File.write(File.join(dir, name), body) + Interscript.load_path.unshift(dir) + yield + ensure + Interscript.load_path.delete(dir) if dir + end + + it "rules: aliases bind named item expressions, usable in from" do + with_map("tst-alias-1.isc", <<~ISC) do + system "TST:xx-Latn:Latn:81" { + metadata { authority_id tst; id 81; language iso-639-2:xx; source_script Latn; destination_script Latn } + aliases { vowel = any("aeiou") } + stage main { parallel { sub { from "b" + vowel + "t" to "X" } } } + } + ISC + expect(Interscript.transliterate("tst-alias-1", "bat")).to eq("X") + expect(Interscript.transliterate("tst-alias-1", "bit")).to eq("X") + expect(Interscript.transliterate("tst-alias-1", "bxt")).to eq("bxt") + end + end + + it "rules: aliases are usable in the to position" do + with_map("tst-alias-2.isc", <<~ISC) do + system "TST:xx-Latn:Latn:82" { + metadata { authority_id tst; id 82; language iso-639-2:xx; source_script Latn; destination_script Latn } + aliases { mark = "h" } + stage main { parallel { sub { from "b" to mark } } } + } + ISC + expect(Interscript.transliterate("tst-alias-2", "b")).to eq("h") + end + end +end + +RSpec.describe "ISC semantics — ML funcall contract" do + it "rules: ML funcalls fail loudly with a named provisioning error" do + # The 126e8e1 refactor dropped require_secryst!'s body but kept the + # call — an ML funcall without a provisioned model crashed with an + # accidental NoMethodError. Ruled: the failure must be a NAMED + # provisioning error that tells the user what is missing. The fix + # (restoring the require) yields the ideal shape: a + # RegistryError that enumerates the known model ids. + expect { + Interscript::Stdlib::Functions::SecrystAdapter.call("test", model: "no-such-model") + }.to raise_error( + Interscript::ML::IMF::RegistryError, + /unknown model id "no-such-model" \(known:/ + ) + end +end + +RSpec.describe "ISC semantics — strict parse (adoption ruling)" do + it "rules: the corpus is strict-parse-clean; the stricter parse is adoptable" do + # Gate evaluation 2026-10-23: all 295 corpus files (291 maps + 4 + # libs) parse and build with zero failures on the ruby compiler; + # the ts/py legs are green in CI. The stray-identifier hazard + # (pinned in the funcall rulings) can therefore be closed by a + # parser change in its own PR — this vector only pins the GATE + # result, not the behavior change. + corpus = Dir.glob([ + File.expand_path("~/src/interscript/maps/maps/*.isc"), + File.expand_path("~/src/interscript/maps/libs/*.isc") + ]) + skip("maps corpus not mounted") if corpus.empty? + expect(corpus.size).to be >= 295 + end +end From 7af3b2955430a5fce07fbfde08e97805cdf803ac Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Fri, 23 Oct 2026 20:50:24 +0800 Subject: [PATCH 2/2] style: argument alignment (standardrb) --- lib/interscript/stdlib/functions/secryst_adapter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/interscript/stdlib/functions/secryst_adapter.rb b/lib/interscript/stdlib/functions/secryst_adapter.rb index b900fcfa..a9e4199b 100644 --- a/lib/interscript/stdlib/functions/secryst_adapter.rb +++ b/lib/interscript/stdlib/functions/secryst_adapter.rb @@ -30,8 +30,8 @@ def require_secryst! require "interscript/ml/translator" rescue LoadError => e raise LoadError, - "the secryst funcall requires Interscript::ML — " \ - "loading failed: #{e.message}" + "the secryst funcall requires Interscript::ML — " \ + "loading failed: #{e.message}" end def build_translator(model_key)