RFC 0015: Deprecate `fail_on_violation`
Comments on this RFC: join the discussion — or browse all RFC discussions.
- Status: Implemented
- Author(s): Charlie Holland
- Created: 2026-08-31
- Updated: 2026-08-31
- Discussion: RFC Comments
- Related Issues: #46
Summary
Deprecate fail_on_violation on $defs/Validator, moving validators to always-enforce semantics. Declaring a validator will mean “rewrite or block the response on a hit,” with no configuration that makes it do nothing. Authors who want observation rather than enforcement declare an eval and assert on its score. The field is marked deprecated in spec v1.7.0 and removed in v2.0.0, no earlier than 2027-08-31 per the twelve-month minimum in the versioning policy.
Motivation
The specification currently defines fail_on_violation: false as “violations are logged but allowed.” That single flag conflates two things the rest of the specification keeps carefully apart:
- A guardrail is an enforcement rule. It mutates the assistant message on a hit — truncating, replacing, or blocking. It belongs to production.
- An eval is a measurement. It records a score. It belongs to tests, and optionally to telemetry.
A validator configured with fail_on_violation: false is an eval wearing a guardrail’s clothes. It is declared in the pack as a validator, it runs through the guardrail hook chain, and it produces no behavior. That is confusing on its own, but the real problem is the direction of the confusion: a pack author reading the spec reasonably assumes that a validator with enabled: true validates. The escape hatch makes that assumption silently wrong, and it fails open — the case where a security-shaped feature quietly does nothing is the worst available default.
There is also a live divergence to close. The PromptKit reference runtime already implements always-enforce semantics: runtime/hooks/guardrails/adapter.go unmarshals fail_on_violation for spec compliance and then ignores the value, with an inline comment recording the deliberate deviation. Today the specification documents a flag the reference implementation disregards, which is the worst of both worlds — implementers who follow the spec produce behavior that differs from the reference, and the spec offers a guarantee nothing honors.
Goals
- Make the validator contract unambiguous: declaring one means it enforces.
- Give observation a correct home —
evals— rather than a degenerate guardrail. - Close the standing divergence between the specification and the reference runtime.
- Remove the field on the schedule the versioning policy already mandates, with the deprecation window running its full length.
Non-Goals
- Renaming
ValidatortoGuardrail. That collision with input-validation terminology is real, but it is a separate change with a separate migration cost, and bundling it here would make a narrow deprecation into a broad rename. See Unresolved Questions. - Changing what validators do on a hit. Truncation and replacement semantics are untouched.
- Changing eval semantics. This RFC points authors at
evals; it does not modify them.
Detailed Design
fail_on_violation is marked deprecated in v1.7.0. The field remains present, remains schema-valid, and remains parseable for the entire deprecation window. What changes in v1.7.0 is what the specification says it means, and what tooling reports.
From v1.7.0:
- A validator that triggers MUST be enforced, regardless of the value of
fail_on_violation. The value is no longer consulted. - The
enabledproperty is unaffected and remains the supported way to turn a validator off.enabled: falsedisables the validator entirely; it does not run and cannot trigger. This is the only remaining way to have a declared validator produce no behavior, and it is honest about it. - Validation tooling SHOULD emit a deprecation warning when
fail_on_violationis present, naming the migration.
In v2.0.0 the property is removed from the schema. A pack still carrying it then fails validation under additionalProperties: false on Validator.
The timeline is set by the versioning policy’s twelve-month minimum, not by this RFC: deprecated 2026-08-31, removable no earlier than 2027-08-31, and shipping in whichever v2.0.0 follows that date.
Schema Changes
$defs/Validator.fail_on_violation gains a deprecated annotation and a description that states the replacement:
{ "fail_on_violation": { "type": "boolean", "deprecated": true, "default": false, "description": "DEPRECATED (v1.7.0), removed in v2.0.0. Ignored — validators always enforce. A triggered validator rewrites or blocks the assistant message regardless of this value. To disable a validator, use 'enabled: false'. For observation without enforcement, declare an eval and assert on its score instead. See RFC 0015." }}deprecated is a standard JSON Schema 2020-12 annotation, so this carries no validation change — a pack with the field remains valid, and generic tooling that understands the keyword surfaces the warning without any PromptPack-specific knowledge.
Specification Impact
docs/spec/structure.md— the validator property table marks the field deprecated.docs/spec/schema-guide.md— the guardrails section states always-enforce, and carries the migration.docs/spec/overview.md— the v1.7.0 “What’s New” box notes the deprecation, since it is the first deprecation the spec has issued.docs/spec/versions.md— a deprecation row with the v2.0.0 removal target.
Validation Rules
fail_on_violationremains schema-valid through the v1.x series. Its presence MUST NOT cause validation failure before v2.0.0.- A runtime MUST NOT consult the value. A triggered validator is enforced whether the field says
true, saysfalse, or is absent. - Validation tooling SHOULD warn when the field is present, and the warning SHOULD name
evalsas the replacement for thefalsecase specifically. - From v2.0.0 the property is removed and its presence is a validation error under
Validator’sadditionalProperties: false.
Runtime Support Levels
- Level 0 — Ignore. Parse and discard the field. Since the specified behavior is now to ignore it, Level 0 and Level 2 agree on execution; a Level 0 runtime simply does not warn. This is the one deprecation where doing nothing is conformant.
- Level 1 — Validate and surface. Additionally warn that the field is deprecated, and report packs carrying
fail_on_violation: falseas needing migration, since those are the packs whose behavior changes. - Level 2 — Enforce. Always enforce a triggered validator, and treat
enabled: falseas the only supported way to disable one.
Examples
YAML shown for readability (per RFC 0002). Equally valid as JSON.
Example 1: Basic Usage
A pack that used the flag for its intended purpose needs no change — the validator already enforced, and now says so without the redundant flag.
# Before (v1.6.0)prompts: support: validators: - type: banned_words fail_on_violation: true message: "Response contains banned words" params: words: [inappropriate, banned]
# After (v1.7.0) — the flag is simply droppedprompts: support: validators: - type: banned_words message: "Response contains banned words" params: words: [inappropriate, banned]Example 2: Advanced Usage
The case that actually migrates: a validator used for observation becomes an eval, and the assertion moves to the test scenario where it belongs.
# Before (v1.6.0) — a guardrail that does nothing in productionprompts: support: validators: - type: length_check fail_on_violation: false params: max_characters: 1000
# After (v1.7.0) — measurement declared as measurementprompts: support: evals: - id: response_length type: length_check trigger: on_response params: max_characters: 1000 metric: within_limit threshold: 0.95The eval scores every response and reports; nothing is blocked. A test scenario asserts on response_length to fail the build when the pack regresses. The observation that used to happen silently in production is now explicit in both places, and the production path no longer carries a guardrail that never fires.
Drawbacks
- Packs relying on
fail_on_violation: falsechange behavior. Any pack that used the flag to park a rule in observe-only mode will start enforcing it. On a runtime that already ignored the field this is not a change at all, but on a spec-faithful runtime it is a real behavior difference, and it arrives as blocked or rewritten responses rather than as an error. This is the sharpest edge in the RFC. - The migration is not mechanical. Moving a validator to
evalsmeans choosing ametric, athreshold, and a trigger, and then writing a scenario assertion. It is not a rename. - It removes a genuinely convenient switch. Toggling
fail_on_violationwas a fast way to stage a new guardrail — deploy it observing, watch the logs, then turn it on. That workflow now spans two declarations. - Two-step removal is a long tail. The field lives on for at least a year, so the spec documents something inert for the whole v1.x series.
Alternatives
Alternative 1: Keep the field and make it work
Require runtimes to honor fail_on_violation: false properly, and fix the reference runtime to match the spec rather than the other way around.
Rejected because it preserves the conflation rather than resolving it. A conforming runtime would then have two mechanisms for measurement — evals, and validators-that-do-not-validate — with different declaration sites, different result shapes, and no guidance on which to reach for. It also entrenches the fail-open reading of a security-shaped feature.
Alternative 2: Redefine false as “warn but continue”
Keep the field and give it real, non-degenerate semantics: on a hit, emit a warning to telemetry and let the response through unmodified.
Rejected because that is what an eval is. The distinguishing property of a guardrail is that it changes the message; a rule that only reports is a measurement no matter which array it is declared in. This alternative renames the problem.
Alternative 3: Remove it immediately in v1.7.0
Drop the property now rather than deprecating it.
Rejected because the versioning policy forbids it. Removing a field is a breaking change that belongs in a MAJOR release, and the deprecation process sets a twelve-month minimum window with migration guidance. Fast-tracking the RFC’s approval does not fast-track a removal the policy schedules.
Adoption Strategy
Nothing breaks on upgrade to v1.7.0 — the field stays valid and packs keep loading. Authors see a deprecation warning from validation tooling and migrate at their own pace over the window.
Two populations, with very different work:
fail_on_violation: true, or the field absent. No change required, ever. The validator already enforces. Delete the line when convenient; it is inert either way.fail_on_violation: false. This is the migration. Move the rule toevalson the same prompt, keeping the sametypeandparams, choose a metric and threshold, and add an assertion on the eval score in the test scenarios that covered it. Then delete the validator.
Tooling should make the second population findable — a pack scan for the literal fail_on_violation: false identifies every affected declaration, which is why rule 3 asks validators to report that case specifically rather than warning uniformly.
Backward Compatibility
- Fully backward compatible
- Requires migration (describe migration path)
- Breaking change (describe impact and migration)
The v1.7.0 change is backward compatible in the schema sense: every pack that validated before still validates, and the reference runtime’s behavior does not change at all. The subsequent removal in v2.0.0 is the breaking change, and it is scoped to that release.
Migration Path
For fail_on_violation: false:
- Copy the validator’s
typeandparamsinto a new entry underevalson the same prompt. - Give it an
id, ametric, athreshold, and atrigger. - Add an assertion on that eval’s score in the test scenarios that relied on the validator.
- Delete the validator entry.
For fail_on_violation: true or absent: delete the line. No other change.
Unresolved Questions
- Should
Validatorbe renamed toGuardrail— and thevalidatorskey toguardrails— in v2.0.0? “Validator” collides with input-validation concepts in most frameworks, and “guardrail” matches how the industry now talks about this. The rename would be natural to bundle with the removal, since both are breaking and both land in the same release. Deliberately left open here: it deserves its own RFC and its own migration tooling rather than riding along on a deprecation. - Are there production packs depending on
fail_on_violation: false? The question was asked when #46 was filed and never answered. If any exist, the twelve-month window is where they get found; a registry-wide scan for the literal would answer it sooner. - Should the deprecation warning be an error under a strict validation mode? A
--strictflag that promotes deprecation warnings would let teams enforce migration ahead of v2.0.0, but no such mode exists in the spec today.
Revision History
- 2026-08-31: Initial version, authored at Implemented under the fast-track path in GOVERNANCE.md (deprecation annotation shipped in spec v1.7.0; removal targeted at v2.0.0, no earlier than 2027-08-31).
References
- RFC 0006: Evals Extension — where observation belongs
- Versioning policy — the twelve-month deprecation minimum and the MAJOR-release removal rule
- Issue #46 — original proposal, including the PromptKit divergence