uttrflow / uttrflow/uttrflow-swift
Design: the meaning guard cannot enforce the Tier 3 rules, because it compares bags of words instead of aligning them
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 4h 27m
- Merged PRs (30d)
- 286
Description
**P1 — design. The backstop for the one rule that outranks the others does not hold, and nine issues are symptoms of the same shape.**
`Docs/cleanup.md` names `MeaningPreservationGuard` as the mechanism behind the Tier 3
prohibitions: never shorten, never swap a synonym, never reorder, never invent, never change a
number, name or unit. The audit that produced #188–#196, #218 found that the guard cannot
enforce most of that, and the reason is one design choice rather than nine bugs.
### What the guard is today
An unordered multiset comparison between input and output, run once on the model's result,
with a stem-prefix match for equality. From that shape, each of the following follows
necessarily:
| Issue | Symptom | Why the shape causes it |
|---|---|---|
| #188 | An inserted negation or an invented clause passes | A set comparison asks what was *lost*; nothing asks what was *added* |
| #189 | Clause reordering passes | A set has no order |
| #189 | "confirm" → "confuse", "Aarav" → "Aaron" | A 3-character stem prefix is the equality test |
| #191 | there/their, on/one accepted | Offered readings are exempted wholesale |
| #193 | A candidate anywhere satisfies a span | The check is positionless |
| #194 | One preposition or modal swap per sentence passes | Churn is counted, not classified |
| #195 | A dropped `%`, currency symbol or operator is invisible | Symbols are stripped before the number check reads them |
| #196 | A dropped Hindi negation is not caught | The tokeniser is ASCII-only and `negatingWords` is English-only |
| #218 | A substitution the model was never offered passes | A whole-text substring test satisfies a per-span question |
Nine patches against nine rows would each be a special case bolted to a comparison that is
still unordered, still positionless, and still stem-prefixed. That is the accumulation this
repository's own design note argues against.
### The target shape
**An alignment, not a comparison.** Replace the multiset check with a token alignment between
the words that entered the stage and the words that left it, so every output token has exactly
one provenance: `kept`, `removed-by-`, `punctuation-added`, `case-changed`,
`offered-reading-taken(, )`. `Draft` already records what each pass removed or
rewrote — `Docs/cleanup-design.md` §3 — so the provenance exists and is thrown away at the
model boundary. The guard's question stops being "are these bags similar" and becomes "does
every output token have a provenance the formatter permits", which is answerable, positional,
and language-neutral.
That single change answers eight of the nine rows: an added word has no provenance (#188), a
moved word has a provenance at the wrong index (#189), an offered reading is only accepted at
the span it was offered for (#191, #193, #218), and a substitution is distinguishable from an
addition because they are different provenances (#194).
**Equality that is not a prefix.** Stem-prefix matching exists to let `ContractionsPass` and
`IrregularVerbForms` through. Those are enumerable morphological relations, so enumerate them:
a token pair is equal when it is identical, or when the pair appears in the irregular-form
table, or when a documented inflection rule relates them. "contract"/"contact" is then a
substitution because no rule relates them, and no threshold has to be tuned to say so (#189).
**Tokens that carry their symbols.** Tokenise once, keeping `%`, currency, operators and
Devanagari, and let the number and negation checks read the same tokens everything else reads
(#195, #196). One tokeniser, used by the guard, the passes and the corpus scorer.
### What this is not
Not a second model, not a second round trip, not a network call — the alignment is arithmetic
over two token lists. `Docs/cleanup-design.md` §8's rule holds unchanged.
### Principles
Single responsibility: the guard's job is to decide whether a transformation was permitted, and
it cannot do that from a bag of words that has discarded who did what. Dependency inversion:
passes already produce provenance; the guard should depend on that record rather than
re-deriving intent from the text. DRY: one tokeniser and one equality relation, instead of a
stem heuristic in the guard, a different fold in `FillersPass` and a third in the scorer. It
removes the class rather than the instances — after this, a new pass cannot introduce a
meaning change the guard is structurally unable to see.
### Sequencing
Provenance through the model boundary first, then the alignment, then the enumerated equality,
then the tokeniser. Each step lands with corpus cases for the rows it closes, measured with
`make bakeoff`; the restraint corpus needs sentences longer than the blast-radius cap before
any of this is measurable at all (#190).
---
Design issue for the cluster #188, #189, #190, #191, #193, #194, #195, #196, #218. Traced in
the source, not reproduced at runtime.
Contributor guide
Research direction
Start with Docs/cleanup.md and Docs/cleanup-design.md §3 and §8, then trace MeaningPreservationGuard, Draft, and the cleanup passes in the Swift source. Work through the stated sequence—provenance, alignment, enumerated equality, then tokenisation—and use corpus cases with make bakeoff; done means the listed rows are covered without a second model or network call.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- desktop
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100