August 8, 2026 · 6 min read

When the Guardrail Deleted the Proof

While building VyomaVeda, I ran into a validation failure I was not expecting.

I compute the birth chart in code and use the model only to turn the result into natural language. Then another layer of code checks the model's answer before it reaches the user. If the model names a chart fact the payload did not support, the validator removes it.

That was supposed to protect the reading from invented astrology.

It did.

But it also did something else.

It deleted the proof.

Between August 3 and August 7, I looked at 81 chat replies. Eighteen had validator strip events. Sixteen of those eighteen hit the evidence block. Eleven stripped evidence and shipped no evidence block at all.

That means the user often kept the conclusion and lost the reasoning underneath it.

One career session lost its evidence block five turns in a row.

That is not a harmless deletion. If the answer still says the current period is active for career, but the dasha evidence explaining why is gone, the reply becomes more confident and less accountable.

The bug was not where I expected

My first assumption was that the model had written unsupported astrology and the validator was doing its job.

Some of that was true.

But not all of it.

Here is the simplified shape of the bug. The engine computed a dasha date and the payload printed it:

payload_text = "Current mahadasha: Sun (through 2030-06-30)"

allowed = {
    "timing": set(),
}

The model saw the date and wrote:

Sun mahadasha runs through June 2030.

That sentence was supported by the engine. The date was in the payload. But the validator did not know it was allowed, because the code path that assembled the payload text and the code path that registered allowed timing claims had drifted apart.

So the model did not hallucinate the date.

The engine computed it.

The payload printed it.

The validator deleted it.

That is a different problem from model hallucination. It is a broken contract between two deterministic layers.

The evidence block had a second weakness

The next issue was smaller, but it made the damage larger.

Evidence blocks are written as bullets. Bullets do not always end with periods. My validator split prose mostly by sentence punctuation:

units = re.split(r"(?<=[.!?])\s+", why_block)

That works reasonably well for paragraphs. It is fragile for bullet lists.

If the model wrote:

- Current dasha activates career through 2030
- Saturn supports sustained effort
- A transit sentence outside the allowed date scope

those three bullets could collapse into one validation unit. One unsupported line could take valid sibling lines down with it.

Evidence survival was depending on punctuation style.

That is not a safety system. That is a formatting accident.

Measure the guardrail, not just the model

The fix was not to make the prompt more forceful.

The fix was to measure the validator.

The existing telemetry told me that something had been stripped. It did not tell me whether the stripped thing was false, or true-but-rejected by an overbroad rule.

So I added a shadow-counterfactual pass. It validates the same raw answer twice. The live pass uses the normal rules. The counterfactual pass relaxes only broad suppressive rules while keeping the truth checks on.

live = validate_answer(
    raw_answer,
    allowed=allowed,
    relaxations=set(),
)

counterfactual = validate_answer(
    raw_answer,
    allowed=allowed,
    relaxations={"why_block_suppression"},
)

deleted_but_still_supported = counterfactual.kept - live.kept

The counterfactual answer is not shipped. It is a measuring instrument.

Without it, every deletion looks the same. A validator deleting a fabricated placement and a validator deleting correct evidence both appear as "strip happened."

That is not enough information.

The fix moved the allow-list closer to the source

The timing bug came from asking every payload builder to remember two jobs: print the fact and register the same fact as allowed.

That split is easy to get wrong. The safer place to register payload dates is after the payload text exists, because at that point the system can see exactly what engine-generated dates the model was shown.

def register_payload_dates(payload_text: str, allowed: dict) -> None:
    for iso_date in find_iso_dates(payload_text):
        allowed["timing"].add(iso_date)
        allowed["timing"].add(month_year_label(iso_date))

This does not let the model invent dates. The payload is produced by the engine. A date in that payload came from code, not from model prose.

The validator still strips a date the payload never printed. It just no longer strips a date the engine computed and handed to the model.

Validate bullets as bullets

The evidence-block fix was to treat lines as the first boundary, then sentence-split inside each line.

def validation_units(block: str) -> list[str]:
    units = []

    for line in block.splitlines():
        line = line.strip()
        if line:
            units.extend(split_sentences(line))

    return units

That means a bad bullet can be removed without taking its valid siblings with it.

The block still collapses if every bullet is unsupported. That is correct. What changed is that one bad line no longer destroys the proof around it.

The part that matters

A validator can fail in two directions.

It can let false claims through.

It can also delete true evidence and make a correct answer less transparent.

The second failure is easy to miss because it looks conservative. But in a product like VyomaVeda, the evidence block is not decoration. It is the trust contract.

If the verdict survives and the reasoning disappears, the answer becomes exactly the kind of confident AI output I was trying to avoid.

The model did not make the interesting mistake here. The software around the model did.

That is the uncomfortable lesson. Deterministic code can still remove the wrong thing. A guardrail is only trustworthy when you can measure what it removes, why it removed it, and whether the removed thing was actually wrong.

This is one piece of the larger split behind VyomaVeda: deterministic code owns the chart facts, the model narrates them, and code checks the claims before they reach you.

But the contract itself has to be checked too. Otherwise the system may stop hallucinations in one place while quietly deleting the evidence in another.