uttrflow / uttrflow/uttrflow-swift

The guard's ASCII-only tokeniser leaves Devanagari to the base checks, and negatingWords holds no Hindi, so a dropped Hindi negation is caught by nothing

Open Beginner friendly
#196 4 comments 0 reactions 0 assignees View on GitHub
area:dictation bug claimed P2
Dominant language
Swift
Stars
4
Forks
17
Avg merge
3h 32m
Merged PRs (30d)
277

Description

**P2 — meaning change.**

### What happens

A Hindi dictation is checked only for emptiness, a preamble, a growth ratio and invented digits. A model that translates a clause, drops one, or turns "नहीं" into its opposite while romanising is accepted and inserted.

### Why

The tokeniser is ASCII-shaped, so a script it cannot split is skipped rather than handled, and `GuardVerdict` has no value meaning "I could not judge this" — only `accepted` and `rejected`.

### What the code shows

The code says what the finding says. `isPlain` is `matching.allSatisfy(\.isASCII)` (:121), the pool filters on it (:131) and the kept-token loop requires it (:137-138), so a Devanagari draft contributes no tokens to either side and `grammarVerdict` reaches `.accepted` having compared nothing; `functionWordChurn` (:218) filters on `isPlain` too, so the churn is zero as well. `negatingWords` (:208-212) holds no Hindi word in either script. The prompt does ask for romanisation (PromptContract.swift:18-20), which is the most invasive thing the model does to the text. What the finding does not say is that this is deliberate and tested: the comment at :120 states it, and `skipsDevanagari` (Tests/UttrflowAITests/MeaningPreservationGuardTests.swift:362-365) asserts the acceptance. So it is a known limitation rather than an oversight, which is why the finding's proposed remedy — make the guard fail closed — is the wrong shape: fail-closed on an unreadable script would reject every Hindi rewrite until a transliteration table existed, on a path where a rejection costs the whole model answer. One part of the finding is a genuinely cheap fix, and worth separating out: `negators(in:)` (:203-205) does NOT filter on `isPlain`, so adding the Devanagari and romanised Hindi negations to `negatingWords` closes the negation half immediately, in both directions, with no tokeniser change at all.

### Where

- `Sources/UttrflowAI/MeaningPreservationGuard.swift:121`
- `Sources/UttrflowAI/MeaningPreservationGuard.swift:131`
- `Sources/UttrflowAI/MeaningPreservationGuard.swift:208`
- `Sources/UttrflowAI/PromptContract.swift:18`
- `Tests/UttrflowAITests/MeaningPreservationGuardTests.swift:362`

### Fix direction

Split this into the part that is safe now and the part that needs measuring. Now: add the Hindi negations in both scripts to `MeaningPreservationGuard.negatingWords` — नहीं, ना, मत, nahi, nahin, na, mat. `negators(in:)` reads `matching` without an `isPlain` filter, so this works for a Devanagari draft against a romanised rewrite with no other change, and it needs only corpus cases for a dropped and a kept Hindi negation. Later, and only with the Hindi corpus in front of you: replace the `isPlain` predicate scattered through three call sites with Unicode word segmentation in the tokeniser, so Devanagari tokens exist and can be aligned, and judge a transliteration as a `.formChanged` edit under a `Transliteration` relation living in one table beside `IrregularVerbForms`. Do NOT make an unmappable token an automatic rejection and do not make `.undecided` mean rejection by default — decide that from the bake-off, per language, because on-device transcription and the Hindi path are the product's differentiator and a guard that refuses what it cannot read would silently disable the model for every Hindi user.

### Principles

Single responsibility: script handling belongs in the tokeniser, not as an `isPlain` filter at three call sites. Open/closed: a new script becomes a new relation table rather than a new branch. Fail-closed is right as a default only where a corpus says the cost is acceptable.

---

Found by a code-path audit of the dictation pipeline. Traced in the source and checked against `Docs/` by a second reader; **not reproduced at runtime**, which is why this carries no `confirmed` label. Any rule or prompt change proposed above is measured against the corpus with `make bakeoff` before it lands.

Contributor guide

Open the contributing guide

Research direction

Start with negatingWords and negators(in:) in Sources/UttrflowAI/MeaningPreservationGuard.swift, then read the existing cases in Tests/UttrflowAITests/MeaningPreservationGuardTests.swift. Add corpus cases for dropped and preserved Hindi negations in Devanagari and romanised forms, and run the relevant test suite. Done means these negations are detected without changing the ASCII tokeniser.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
ai, testing-qa
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.