Companion commit to b254ee9 (Prolog Form 4 self_grounding) and 6b96250
(Prolog regression canary). Closes the Elixir half of Pass 2 finding L.
THE GAP:
The Prolog halt rules validate coupling traces as Prolog terms: a
trace whose anchor is prior_decision/1, system_view/1, consensus/1,
or interpretation/1 fires the self_grounding halt. But L3 stores
traces as a list of strings (schema: {:array, :string}), serialized
from Prolog. Inert string traces in L3 cannot be re-checked by Prolog
halts. So the safety property the Prolog rules enforce stops at the
Erlang/Elixir boundary — once a trace serializes into L3, no rule
re-validates it.
Worse: L3 is APPEND-ONLY (Architecture §3.7). Once a trace is in L3
it is permanent oral tradition; there is no path to retroactive
correction. A future reader who pulls an L3 record and trusts the
trace must be able to trust it was valid at the moment it became
permanent.
THE FIX:
VavPipeline.L3.CouplingTrace (new) — Elixir-side validator that
mirrors is_l1_grounded_coupling/1 in talmudic_engine/src/halt/
avodah_zarah.pl. Parses the string form (with a small balanced-paren
parser so nested subjects like root(בער) don't break splitting) and
recognizes exactly the three valid shapes:
coupled(_, verse(BOOK, CHAPTER, VERSE))
coupled(_, mitzvah(ID))
coupled(_, mitzvah(ID), via_verse(BOOK, CHAPTER, VERSE))
Anything else fails. Empty traces are allowed at this layer (the
empty-trace case is the golden_calf halt's domain in Prolog Form 2;
L3 may legitimately store empty-trace records).
L3.Store.record_interpretation/1 — gated. The new write path:
case CouplingTrace.validate(coupling_trace) do
:ok -> %Interpretation{} |> changeset(attrs) |> Repo.insert()
{:error, :ungrounded, bad} -> {:error, {:ungrounded_coupling, bad}}
end
Validation fires BEFORE Repo.insert. An ungrounded trace can never
become permanent oral tradition.
CROSS-LANGUAGE INVARIANT:
CouplingTrace.l1_grounded?/1 (Elixir) and is_l1_grounded_coupling/1
(Prolog) MUST stay aligned. If Prolog adds a new recognized shape,
Elixir must add it too. Both module docstrings name this dependency
explicitly. The next time CouplingTraceTest runs after a Prolog
schema change, the Elixir tests catch the drift.
ONE BUG CAUGHT WHILE WRITING:
The first parser cut Hebrew root subjects mid-character because it
used String.slice/3 with byte_size/1 as the codepoint length. Fixed
to use binary_part/3 (since the trailing `)` is always one byte and
the rest is opaque UTF-8). Test for Hebrew-root anchors now passes;
documented inline so the trap is visible to future readers.
TESTS:
test/coupling_trace_test.exs (new, 22 tests):
- all 3 valid shapes (verse, mitzvah, via_verse) including
Hebrew root subject (catches the byte/codepoint bug)
- all the predictable ungrounded shapes
- malformed inputs (missing parens, wrong functor, wrong arity,
empty string, non-string)
- validate/1 list semantics (empty allowed, first-failure
reported, mixed-trace caught)
- parser robustness with deeply-nested subject parens
test/l3_store_test.exs (extended, 5 new :l3-tagged tests):
- record_interpretation refuses ungrounded trace
- record_interpretation refuses mixed (good + bad) trace
- record_interpretation accepts empty trace
- record_interpretation accepts mitzvah-anchored trace
- record_interpretation accepts 3-arg via_verse trace
Full vav_pipeline suite: 315 tests, 0 failures, 24 excluded
(24 = 19 pre-existing :l3 + 5 new :l3 store-gate tests; both
need PostgreSQL). The 22 CouplingTrace unit tests run
unconditionally and all pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>