Orchestrata Ultimate › Gates › lexicon-drift

Gate: lexicon-drift

At a glance

Purpose Refuse a committed intent_lexicon.gen.pl that differs from what gen_intent_lexicon.py generates from IntentVocabulary.default().
Where it runs The scripts/gates.py battery (registered as lexicon-drift). Not in CI: .gitlab-ci.yml (last changed in 9aa8a72f6, 2026-10-20) never calls gen_intent_lexicon.py.
Trigger A battery run whose src/ or scripts/ fingerprint changed.
Exact command python scripts/gen_intent_lexicon.py --check
Blocks Non-zero exit when the committed file is stale; 0 when byte-identical.
Ruling / ADR Decision D2 (reports/research-dsl-benefit.md §5, generation over a parity test); none recorded as a RUL page or ADR. Generated-artifact law: RUL-030.
Last recorded verdict ok: true in the primary checkout's .cache/gates.json (file last written 2026-09-22 10:01 local). wt-MAP-PRECOMMIT has no .cache/gates.json.

What it refuses

A committed src/orchestrata_ultimate/prolog/intent_lexicon.gen.pl that no longer equals what scripts/gen_intent_lexicon.py would generate from IntentVocabulary.default() — i.e. the DCG grammar consulting a lexicon the Python vocabulary has moved past.

Why it exists

From the generator's docstring:

"Why this exists (L-D2, reports/research-dsl-benefit.md §3.C / §5 D2): intent_grammar.pl claimed to 'mirror' intent_vocabulary.py since the day both were created in one commit, but the two lexicons drifted immediately — 16 keywords across 9/10 WorkType categories (plus 13 IntentTarget keywords) existed in the Python vocabulary but not the Prolog lexicon, silently degrading those words from the preferred DCG-grammar classification tier to the lower-confidence vocabulary fallback tier. The covering test passed only via a decoy keyword… Decision D2 (generation over a parity test) makes the Prolog lexicon a build artifact of the Python vocabulary so the two tiers can never re-diverge."

Why the drift matters beyond the parser (registry comment): "the parser matches against the GENERATED intent_lexicon.gen.pl, while lexicon-collision reads IntentVocabulary. If those two drift, the collision gate is checking a vocabulary the parser does not use, and passes while the real lexicon holds a colliding word." The comment also records that this "was a CI-ONLY gate until now — the same hole this file already documents for mypy."

How it runs

Typical failures and how to fix them

Wiring