uttrflow / uttrflow/uttrflow-swift

Rules fallback returns the same unauthorized word removal that made the model guard reject the answer

Open
#817 0 comments 0 reactions 1 assignee Claimed by @naveen-bhatt View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.