uttrflow / uttrflow/uttrflow-swift
Rules fallback returns the same unauthorized word removal that made the model guard reject the answer
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
## Reproduced behavior
On main `5c310667cd35c44abd8bbd26c19b0905471287b4`, the meaning guard correctly refuses a model answer that preserves an unauthorized removal made by a deterministic pass. `TransformerRouter` then tries `RuleBasedTransformer`, which runs the same destructive pass without consulting the removal audit and returns the omitted-word result successfully.
A production-router test with a controlled `FakeCleanupModel` reproduced both cases:
```text
Input: we rushed him to ER before midnight
Model rejection: the fillers step took out 'ER' and the rewrite does not put it back
Router result: rules — We rushed him to before midnight.
Input: tell the landlord no, the landlord has to wait
Model rejection: the selfCorrection step took out 'no' and the rewrite does not put it back
Router result: rules — Tell the landlord has to wait.
```
The full transformer is called directly to establish each rejection, then the actual router is called with `[modelTransformer, RuleBasedTransformer()]`. Both preservation assertions fail for the returned rules result. These are controlled model responses; no actual model hallucination or external-app insertion is claimed.
The existing 48 filler, contraction, repetition, self-correction, stammer and removal-audit tests pass. Existing `RemovalAuditTests.refusesThroughTransformer` stops at the transformer exception and therefore does not test the fallback result.
## Cause
- [MeaningPreservationGuard.swift](https://github.com/uttrflow/uttrflow-swift/blob/5c310667cd35c44abd8bbd26c19b0905471287b4/Sources/UttrflowAI/MeaningPreservationGuard.swift#L41) consumes `RemovalAudit.unauthorised` to require restoration of words the passes were not authorized to remove.
- [RuleBasedTransformer.swift](https://github.com/uttrflow/uttrflow-swift/blob/5c310667cd35c44abd8bbd26c19b0905471287b4/Sources/UttrflowAI/RuleBasedTransformer.swift#L32-L39) runs its pipeline and returns `draft.text` without auditing those removals.
- [TransformerRouter.swift](https://github.com/uttrflow/uttrflow-swift/blob/5c310667cd35c44abd8bbd26c19b0905471287b4/Sources/UttrflowAI/TransformerRouter.swift) selects the first successful result. The rules engine succeeds, so the refusal is recorded but the word is still lost.
The rules-only route has the same unchecked removals, even without an earlier model rejection. Priority P1: this defeats an explicit preservation check and can delete a dictated negation.
## Acceptance criteria
- Enforce the removal grants at the deterministic output boundary as well as the model boundary, with a word-preserving recovery when a grant is violated.
- Cover both direct rules output and the model-rejection-to-rules route for the acronym and negation cases above.
- Preserve the Latin-output contract and legitimate filler/repetition/restatement removal. Validate any restoration strategy against the cleanup corpus; returning an unaudited transformed string cannot be the safe floor.
- Keep refusal diagnostics accurate after recovery.
Closed #202 addressed mention-sensitive filler matching; closed #198 addressed coordinated-list detection. These examples are already explicitly recognized as unauthorized by the current audit. This issue concerns the unguarded deterministic/fallback boundary that lets those recognized losses reach the final cleaning result.
Contributor guide
Assessment
This issue has not been assessed yet.