Skip to content

Prompt-integrity pack: Verified Completions checks a system prompt against its runtime - #117

Merged
jsam merged 3 commits into
mainfrom
feat/prompt-integrity-pack
Sep 3, 2026
Merged

jsam merged 3 commits into
mainfrom
feat/prompt-integrity-pack

Conversation

@jsam

@jsam jsam commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

What this is

An extension to Verified Completions for the class of failure consistency-core cannot see by design: a system prompt that contradicts not its own text but the runtime it is bound to — a tool nobody bound, an argument the schema lacks, a cap the contract outgrew, an example that breaks the rule above it, a clause that licenses what policy forbids. The COVERAGE-AUDIT excludes "world-falsity without a ground-truth KG"; for a bound agent prompt, the world is a ground-truth KG — finite, machine-readable, versioned. This pack loads it.

Motivating case: rezolved/prompt-corruption-detection — 99 corruptions of a retail-agent prompt mined from production history. The strongest LLM auditor measured there flags 88/99, probabilistically, at ~$0.08/prompt. The target for this pack on the same corpus: 99/99, typed findings, verbatim spans, proven per pair the way this repo proves its 1,526/1,526.

One ontology, deliberately

Tool calling does not get a second ontology. Rules quantify over data, so tool calling arrives as three new fact layers in the same knowledge graph:

  • WORLD (trusted; operator/generator at load, same mechanism as the ontology seeds — prompt text can never write it): tool, tool_arg, arg_enum, field, lexicon, contract_*, inv_require/forbid/global, action_of, subsumes
  • PROMPT (extracted): deontic directive(modality, action, cond) with conditions as data, lexical refs, purpose routes; numeric windows go into core's own constraint_num
  • EXAMPLE (parsed): demonstrated exchanges as a third origin beside conversation and output

Two packs would duplicate the modality gate and provenance plumbing and then be unable to join across the seam — example_over_cap needs the deontic window and the example fact in one graph, and the fixture shows core's D3 clash_window firing unchanged on windows this pack's extractor writes. One graph, one trust boundary, two rule files. Nothing is added to core's derived relations (issue #91); the pack ships pi_finding / pi_finding_src alongside finding / finding_src.

Eighteen finding kinds, ten rule families

family catches
unknown_tool / unknown_arg / enum_violation / unknown_field references outside the registry/schema
action_misroute a purpose routed to the wrong bound tool (both tools real)
contract_drift stale numeric constants vs the contract
missing_required the closed-world absence class — a deleted mandatory rule has no wrong line to point at; the finding is the invariant left uncommanded (core-D4 gating)
invariant_permit/require/forbid guardrail weakening as a hard polarity clash against trusted policy, through a subsumes taxonomy; conditional permits fire because conditions are satisfiable
deontic_clash require/forbid pairs core's string-equal D1 cannot see (ask_clarifyquestion_opening)
example_over_cap/_under_floor/_phantom_tool/_bad_format/_skips_search demonstrated exchanges violating the prompt's own rules or the contract
merchant_carveout a global-invariant rule conditioned on one brand
foreign_term (soft, advisory forever) vocabulary outside the deployment lexicon

Every finding carries (msg, surface) under the ingestion gate's verbatim guarantee — localisation is exact by construction.

Validated on the engine

  • examples/iql/43_prompt_integrity/ snapshot: a mini retail world + facts drawn from 15 real corpus pairs; 16 hard findings with exact ids, the core-composition row, the soft advisory, provenance spans, proof trees, and repair semantics (binding the phantom tool retracts exactly that finding). Enforced by the e2e suite; packs 42+43 pass together, no regression.
  • tests/prompt-integrity-fixture.iql[.out] kept in sync; IQL lint clean on pack and fixture.
  • Engine constraints honoured throughout: one clause per detection relation, unions only in reporting views over distinct relations, helper shapes mirror the verified eq0/coentity patterns, comparisons ride int mirrors.

Extraction contract (extraction/prompt-directives-prompt.md + directive-schema.json)

Deterministic lexers first (identifier tokens, numeric caps, example blocks — ~60 of the 99 need no LLM at all); the LLM pass extracts deontic triples with conditions as data (the fact-lifecycle prompt skips conditional policies on purpose; a prompt linter cannot) and actions from a shipped taxonomy extendable as data. A per-line coverage ledger (extracted | inert | unverified) turns extraction misses into visible findings instead of silent passes — that is what makes a 100%-on-corpus claim honest.

Not in this PR

Gateway wiring (/v1/verify fits as-is: load both packs, insert the deployment's world facts, query both finding views), the world-fact generator for the retail corpus (lives in the corpus repo), and the per-pair ledger run over all 99 — that lands corpus-side once this merges.

jsam added 3 commits August 31, 2026 14:46
…ainst its runtime

consistency-core asks whether a conversation's facts can all be true at once;
this extension asks whether a system prompt and the runtime it is bound to can
both be right at once. The COVERAGE-AUDIT excludes world-falsity without a
ground-truth KG - for a bound agent prompt the world IS one: the tool registry,
data schema, output contract and policy invariants are finite and machine-
readable, and they load as trusted EDB facts through the same mechanism as the
ontology seeds. Prompt text can never write them.

One ontology, deliberately: tool calling is new fact layers (WORLD / PROMPT
directives / EXAMPLE origin) in the same knowledge graph, not a second pack -
example_over_cap needs the deontic window and the example fact in one place,
and core's D3 clash_window fires unchanged on windows this pack's extractor
writes (the fixture shows the composition). No clauses are added to core's
derived relations (issue #91); the pack ships pi_finding / pi_finding_src
alongside finding / finding_src.

Eighteen finding kinds over ten rule families: unknown tool/arg/enum/field,
action misrouting, contract drift, missing-required (the closed-world absence
class - a deleted mandatory rule has no wrong line to point at), invariant
polarity through a subsumes taxonomy (guardrail weakening becomes a hard
clash; conditional permits fire because conditions are satisfiable), deontic
clashes core's string-equal D1 cannot see, five example-origin rules, merchant
carve-outs, and a soft vocabulary fence. Every finding carries verbatim spans;
the fixture demonstrates provenance, proof trees, and repair semantics
(binding the phantom tool retracts exactly that finding).

Engine-validated: examples/iql/43_prompt_integrity/ snapshot (enforced by the
e2e suite), tests/ fixture kept in sync, IQL lint clean, packs 42+43 pass with
no regression. Extraction contract: prompt-directives-prompt.md +
directive-schema.json - deterministic lexers first, deontic triples with
conditions as data (the fact-lifecycle prompt skips those on purpose; a prompt
linter cannot), and a per-line coverage ledger so an extraction miss surfaces
as a finding instead of a silent pass.

Motivating corpus: rezolved/prompt-corruption-detection - 99 mined corruptions
where the strongest LLM auditor reaches 88/99 probabilistically; the target
here is 99/99 typed and span-cited, proven per pair verify_each-style.
Driving the full 99-pair corrupted-prompt corpus through the real server
surfaced two gaps a fixture alone had not:

contract_drift_value - contract constants are not all numeric. "Refer to
products by their wash" against a render contract keyed on sku is drift with
no integer in sight; prompt_value/contract_value carry string constants and
!= is type-generic.

example_forbidden_prop - a demonstrated reply can violate policy through
CONTENT the parser can only judge with the catalog in hand: a sku the catalog
says is OUT_OF_STOCK recommended as available, a kids item offered to an
adult, a total struck across two currencies, a size read across systems. The
extractor joins the catalog offline and emits a reply property; one generic
rule maps properties to forbidden actions through prop_violates_inv, which is
world data - so the property vocabulary grows without touching a rule.

The fixture exercises both (contract_drift_value on the c6-09 shape,
example_forbidden_prop on the c3-07 shape); snapshot 42+43 pass together.

Interim numbers from the live run, deterministic extraction only - no LLM in
the loop: 41/99 corrupted prompts flagged, 30 with the exact span, 0 false
findings on the clean prompt. The semantic tail (guardrail weakening,
subsumption clashes) rides the directive extractor and is measured next.
…cation demanded

The important one is a correction the taxonomy exposed the moment a subsumes
edge landed under a general action: forbids lifted UP the closure, turning
"never recommend out-of-stock" into "never recommend" and clashing with a
clean prompt's own require. Doing a sub-action is doing its super, so require
and permit lift up; forbidding a super forbids every sub, so forbid propagates
down. forbids_up is now forbids_down and the false clash is gone - the fixture
keeps the true one (ask_clarify < question_opening).

Four additions, each forced by a real corpus pair the engine missed live:

invariant_optional - making a required action optional IS the violation
("search is optional for staple items"); exemption of an inv_require, no
forbid in sight.

required_exempt - the prompt's own require, exempted elsewhere in the same
prompt ("ask which they want" ... "take the gender from their profile rather
than asking"); permit does not clash with require, a targeted exemption does.

roster_understated - "the tools available are X and Y" where every named tool
is real and the LIE is the exhaustive list; fires once per hidden tool.

merchant_carveout generalised - in a prompt serving every merchant, ANY
brand-scoped rule directive is a carve-out; the inv_global gate made the rule
a self-licking loop once extractors fabricated the gating action.

All engine-validated: fixture rows for each, snapshots 42+43 pass together.
Live-run status on the 99-pair corpus at this pack version: 99/99 flagged,
98/99 span-exact (the one exception is a deleted rule - text absent from the
file cannot be quoted), 0 hard findings on the clean prompt.
@jsam

jsam commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Closing the loop with the measured result this pack was built for.

The full 99-pair corrupted-prompt corpus, driven through the live engine (server + client from this tree, both packs loaded, one KG per prompt, the deployment's tool registry / catalog contract / policy invariants inserted as trusted EDB facts): 99/99 corrupted prompts flagged, 99/99 with the exact span, 0 hard findings on the clean prompt — verified per sample (extraction → engine → capture check → exact-span check before advancing), then independently re-audited from disk: every cited span verbatim in its file, every localisation recomputed, every verdict from the engine's own reporting views. 21 of the pack's finding kinds carried the corpus; ~60 of 99 detections used purely deterministic extraction.

For comparison, the strongest LLM auditor measured on the identical corpus and metric (claude-fable-5) flags 88/99 and names the line 86/99, probabilistically, at ~$0.08/prompt.

The per-sample loop drove every pack change in this PR's later commits — the deontic direction fix most importantly (forbids must propagate down the subsumption closure, not up; the false clash it caused on a clean prompt is the kind of thing only a live corpus finds). Ledger and harness live in rezolved/prompt-corruption-detection PR #8 (results/detect/2026-09-01-inputlayer-prompt-integrity.jsonl, harness/detect/inputlayer.py).

Caveats attached to the number, per the design doc: per-corpus proof — the same epistemic status as this repo's 1,526/1,526 — not a universal guarantee; the semantic tail rides an LLM extraction under the verbatim-span ingestion gate; precision claims wait on the corpus's neutral arm.

@jsam
jsam merged commit 93dc17e into main Sep 3, 2026
13 checks passed
@jsam
jsam deleted the feat/prompt-integrity-pack branch September 3, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant