All notable changes to the QWED platform, listed by release.
QWED-Legal v0.4.0 — verification traces across all guards + fail-closed hardening
Released: May 30, 2026
Every guard now returns an auditable verification_trace with explicit evidence types, and a batch of trust-boundary fixes stop guards from presenting heuristic or unsupported results as proof. Closes 8 hardening issues (#11, #12, #16, #17, #18, #19, #20, #21).
Verification traces (new)
verification_trace on every guard — an ordered list of VerificationStep records that make each decision auditable. Not a narrative explanation.
evidence_type taxonomy — each step is DETERMINISTIC, PARSED, INFERRED, HEURISTIC, or UNSUPPORTED. VerificationStep.is_proven() returns True only for DETERMINISTIC.
to_dict() / trace_to_dict() — JSON-safe serialization for audit logs. Non-serializable inputs are stringified (no silent data loss); each serialized step carries an explicit is_proven flag.
Trust-boundary changes
FairnessGuard never returns verified=True (#18) — fairness cannot be proven by counterfactual text substitution and string equality. A consistent outcome is UNVERIFIABLE_FAIRNESS; a differing outcome is a HEURISTIC_BIAS_SIGNAL for human review. Malformed swap input (empty, non-string values, case-colliding keys) fails closed.
CitationGuard separates format from authority (#17) — verified is always False; a format match returns status="unverifiable_authority". Format validity is never treated as proof a cited authority exists.
IRACGuard fail-closed reasoning boundary (#12) — structure checks are INFERRED and the reasoning conclusion is UNSUPPORTED; a passing result is unverifiable_reasoning, never proof of correct legal reasoning.
ClauseGuard non-operative keyword handling (#11) — heuristic conclusions are INFERRED; limited or ambiguous coverage fails closed instead of returning a misleading “consistent”.
StatuteOfLimitationsGuard and ContradictionGuard reclassified as MIXED — deterministic core (date arithmetic / Z3 SAT-UNSAT) over a parsed lookup; unmodeled inputs fail closed.
Security hardening
JurisdictionGuard fail-closed on ambiguity and empty parties (#16, #20) — unresolved warnings fail verification; empty parties_countries no longer returns a false verified=True (all([]) fail-open fixed) in both verify_choice_of_law and check_convention_applicability.
LiabilityGuard exact-match caps (#19) — cap, tiered, and indemnity checks require exact match after currency rounding; approximate tolerance is not accepted as proof.
DeadlineGuard — business-day results fail closed when the requested holiday calendar cannot be built; on date parse failure, dates are None (not datetime.min).
SDK changes (TypeScript @qwed-ai/legal)
- Every result interface now exposes
verification_trace.
- Added
FairnessVerifier reflecting the fail-closed contract; verified is hardcoded false at the boundary.
CitationResult exposes format_valid / status / verified: false; ClauseResult.conflicts typed as [number, number, string][].
- The fairness client factory is resolved via a validated
importlib descriptor (no raw code execution).
- npm version reconciled from
1.0.0 to 0.4.0 to match the Python package.
Breaking changes
FairnessGuard.verify_decision_fairness no longer returns verified=True. Migration: treat the output as a signal requiring human review, consuming status / risk instead of verified.
QWED-Verification — context-bound verification cache (replay prevention)
Released: May 17, 2026
VerificationCache now keys every entry on both the normalized query and the trust-bound context: provider, model, policy version, tenant or session, and environment fingerprint. A mismatch on any context dimension is a deterministic cache miss. This prevents cross-context replay of VERIFIED results.
What changed
- New
CacheContext datatype — Required argument to every get() and set() call. Carries provider, model, and policy_version (required), plus optional tenant_id and env_fingerprint.
- Composite cache key — Each key combines
SHA-256(normalized_query) with SHA-256(canonical_context_json). Identical queries under different contexts never collide.
- New
cache_v2 schema — The on-disk SQLite table uses a composite PRIMARY KEY (key, context_fingerprint) and stores the fingerprint per entry. The cache re-validates the fingerprint on every read as defence-in-depth.
- Legacy entries are never returned — Pre-v2 cache rows have no context fingerprint. No code path reads them, and the cache treats them as
UNVERIFIABLE.
Breaking changes
VerificationCache.get(query) and VerificationCache.set(query, result) now require a CacheContext argument. Calls without one raise TypeError.
- Results stored under one
(provider, model, policy_version, tenant_id) tuple are not returned to a caller using a different tuple, even when the query string is identical.
Example
from qwed_sdk.cache import CacheContext, VerificationCache
cache = VerificationCache()
ctx = CacheContext(
provider="openai",
model="gpt-4o",
policy_version="v1",
tenant_id="tenant-alpha", # optional
env_fingerprint="sha256-...", # optional
)
# First call: miss, then populate
result = cache.get("2+2", ctx) # None
cache.set("2+2", {"verified": True}, ctx)
# Same query + same context: hit
cache.get("2+2", ctx) # {"verified": True}
# Same query, different provider: deterministic miss (replay prevention)
ctx2 = CacheContext(provider="anthropic", model="claude-opus-4-5", policy_version="v1")
cache.get("2+2", ctx2) # None
See Python SDK — Verification cache for the full API.
QWED-Legal — StatuteOfLimitationsGuard fail-closed on unknown jurisdictions and claim types
Released: May 10, 2026
Hardens StatuteOfLimitationsGuard so unknown jurisdictions and claim types return an UNVERIFIABLE result instead of fabricating a limitation period from a partial string match or a default fallback table.
What changed
- Exact jurisdiction match only — Lookups uppercase and trim the input, then require an exact key in the rule table. Substring matches (for example,
"CALIF" → "CALIFORNIA", "NEW" → "NEW YORK") are no longer accepted.
- No default fallback for unknown jurisdictions — Unsupported jurisdictions previously fell back to a generic
DEFAULT_LIMITATIONS table. They now return a StatuteResult with verified=False, jurisdiction_matched=False, all date and period fields set to None, and a message that lists the supported jurisdictions.
- No silent default for unknown claim types — Unsupported claim types previously defaulted to a 3-year period. They now return
verified=False, claim_type_matched=False, and a message that lists the supported claim types for the jurisdiction.
get_limitation_period() returns Optional[float] — Returns None when the jurisdiction or claim type is unsupported, instead of always returning a number.
compare_jurisdictions() returns Dict[str, Optional[float]] — Unsupported jurisdictions in the input list map to None in the result.
- New
StatuteResult fields — jurisdiction_matched: bool and claim_type_matched: bool let callers distinguish “claim is time-barred” from “we cannot determine the limit”.
Breaking changes
StatuteResult.incident_date, filing_date, limitation_period_years, expiration_date, and days_remaining are now Optional and are None when the input is unverifiable. Code that does arithmetic or comparisons on these fields must check result.verified (and the new *_matched flags) first.
get_limitation_period() now returns Optional[float]. Code that previously assumed a numeric result must handle None.
- Callers that relied on partial jurisdiction matching (for example, passing
"Calif" and expecting California rules) must update their inputs to a fully spelled, supported jurisdiction.
Example
from qwed_legal import StatuteOfLimitationsGuard
guard = StatuteOfLimitationsGuard()
result = guard.verify(
claim_type="breach_of_contract",
jurisdiction="Atlantis",
incident_date="2023-01-15",
filing_date="2026-06-01",
)
print(result.verified) # False
print(result.jurisdiction_matched) # False
print(result.limitation_period_years) # None
# result.message starts with "⚠️ UNVERIFIABLE: Jurisdiction 'Atlantis' ..."
See Guards — StatuteOfLimitationsGuard and Troubleshooting — StatuteOfLimitationsGuard issues for migration guidance.
QWED-Verification — audit logger fail-closed and per-organization chains
Released: May 8, 2026 · GitHub PR #179
Hardens the cryptographic audit logger so signing-key, payload, and chain-continuity failures fail closed instead of silently writing or “verifying” a broken audit trail.
What changed
- Signing key required at startup —
AuditLogger() now raises SecurityError when QWED_AUDIT_SECRET_KEY is unset or empty. The previous insecure dev_only_change_in_production fallback was removed.
- Per-organization hash chains — Each organization now has its own isolated audit chain. Tampering or gaps in one tenant’s chain cannot affect another tenant’s verification.
- Hash coverage extended to
raw_llm_output — New entries hash and verify the raw LLM output. Legacy entries created before this change are still accepted by the verifier via a backward-compatible canonical payload.
- Stronger pre-append checks — Before writing a new entry, the logger verifies the current persisted chain head and refuses to append on top of malformed payloads, missing hashes, or in-memory/persisted divergence. Appends are serialized with
BEGIN IMMEDIATE on SQLite (or SELECT … FOR UPDATE on other backends).
- Constant-time HMAC comparison — Signature checks use
hmac.compare_digest to prevent timing side channels.
Action required
Set QWED_AUDIT_SECRET_KEY in every environment that runs the verification service (including CI and local development). The service will not start without it.
export QWED_AUDIT_SECRET_KEY="your-secure-production-key-here"
See Compliance — Audit trail setup for the updated configuration steps and failure modes.
QWED-Verification — fail-closed denial of unknown agent action types
Released: May 6, 2026 · GitHub PR #176
Hardens AgentService.verify_action so action types with no registered engine or tool risk binding are denied instead of routed through a permissive default.
What changed
verify_action denies unregistered action types — Calls whose action_type is not in ACTION_ENGINES or TOOL_RISK_LEVELS now return decision: "DENIED" with error code QWED-AGENT-ACTION-001. The verification kernel no longer falls back to a "security" engine label for unknown actions.
- Risk assessment is now deterministic —
_assess_risk raises ValueError if it is invoked for an unregistered action_type. Risk levels must come from explicit registration.
- Step reservation released on action denial — A
QWED-AGENT-ACTION-001 denial releases the in-flight step reservation, so the agent can retry the same (conversation_id, step_number) pair with a registered action type.
Example
result = client.verify_action(
agent_id="agent_abc123",
action={"type": "transfer_funds_internal_v2", "query": "Move funds between ledgers"},
context={"conversation_id": "conv_xyz", "step_number": 1},
)
# {
# "decision": "DENIED",
# "error": {
# "code": "QWED-AGENT-ACTION-001",
# "message": "Unknown action_type 'transfer_funds_internal_v2' cannot be verified without explicit registered semantics"
# }
# }
Why
Unknown actions are not verifiable actions. If QWED has no explicit semantics for an action type, it must not approve it or mark it as verified.
See Agent verification — Unknown action types denied and the Agent spec error code table.
QWED-Verification — fail-closed on unknown agent actions
Released: May 6, 2026
The agent service now rejects verification requests whose action_type has no explicit registered semantics. Previously, an unrecognized action could pass through risk assessment and receive an APPROVED decision with a generic "security" engine fallback.
Security
Unknown agent action_type values are rejected. Agents that previously relied on the implicit "security" engine fallback for ad-hoc action types must now register them in ACTION_ENGINES (engine binding) or TOOL_RISK_LEVELS (tool-control binding). Unregistered actions return QWED-AGENT-ACTION-001.
References
QWED-Finance v2.1.0 — security audit hardening
Released: May 2, 2026 · GitHub PR #25
Roll-up release that consolidates all P0/P1 findings from the deterministic security audit (Issue #16). Combines the four hardening PRs (#21–#24) into a single tagged release, bumps pyproject.toml from 2.0.1 to 2.1.0, and promotes mpmath to an explicit runtime dependency.
What’s in this release
| PR | Change | Audit findings |
|---|
| #21 | Fail-closed enforcement in OpenResponsesIntegration + AML high-risk country list unified across all paths | S-01, S-02, S-06 |
| #22 | BondGuard._parse_rate() heuristic removed; returns Decimal | C-04, H-01, S-05 |
| #23 | float → Decimal/mpmath migration for BondGuard, DerivativesGuard, RiskGuard | C-01, C-02, C-03 |
| #24 | Integration-layer Black-Scholes unified with DerivativesGuard; verify_compound_interest fail-closed on unknown frequencies | N-01, N-04 |
Breaking changes
Greeks are returned as Decimal-quantized strings, not float. verify_black_scholes and the OpenResponses price_option tool both produce result.greeks with each Greek as a string (e.g. "0.4502"). Cast explicitly when you need a numeric value:from decimal import Decimal
delta = Decimal(greeks["delta"])
exposure = delta * Decimal(str(notional))
BondGuard._parse_rate() returns Decimal instead of float. Downstream consumers that performed float-based arithmetic on the parsed rate must wrap inputs with Decimal(str(value)) or migrate to Decimal math.
Unknown compounding frequencies now raise ValueError. FinanceVerifier.verify_compound_interest() previously fell back silently to annual compounding for unrecognized values. It now raises with the accepted list (annual, semi-annual, quarterly, monthly, daily).
Unregistered tools in OpenResponsesIntegration are rejected. Any tool call that does not match an explicitly registered tool is denied. Previously, unknown tools were auto-approved through a permissive default path.
Dependency changes
mpmath>=1.3.0 is now a declared runtime dependency in pyproject.toml. It was already available transitively via sympy, so existing installs require no extra step.
Test coverage
- 150 tests passing — zero regressions
- 23 new float-contamination tests (
TestBondGuardDecimal, TestDerivativesGuardMpmath, TestRiskGuardDecimal, TestNoFloatInOutput)
- N-04 regression test asserting unknown compounding frequencies raise
ValueError
Upgrade
pip install --upgrade qwed-finance==2.1.0
GitHub Action consumers should update the pinned ref:
- uses: QWED-AI/qwed-finance@v2.1.0
with:
test-script: tests/verify_agent.py
See the per-PR entries below for full implementation detail: N-01/N-04 release blockers, Decimal/mpmath migration, OpenResponses fail-closed, and rate parsing heuristic removed.
QWED-Finance — release blockers N-01 and N-04 resolved (NO-GO → GO)
Released: May 2, 2026 · GitHub PR #24
Closes the two release-blocking findings from the post-fix deterministic audit. The integration layer now shares a single Black-Scholes implementation with DerivativesGuard, and FinanceVerifier.verify_compound_interest no longer silently defaults unknown compounding frequencies.
What changed
OpenResponsesIntegration._verify_option_price — Removed the duplicate IEEE-754 math.log/exp/sqrt/erf Black-Scholes routine. The price_option tool now delegates to DerivativesGuard.verify_black_scholes(), so OpenResponses-mediated pricing and direct guard pricing are bit-identical.
- Tool result fields —
price and delta are now sourced from the guard’s mpmath-backed, Decimal-quantized output instead of the integration’s own float computation.
FinanceVerifier.verify_compound_interest — Unknown values for compounding now raise ValueError listing the accepted frequencies (annual, semi-annual, quarterly, monthly, daily). Previously, unknown values silently fell back to annual compounding.
- Test suite — 150 tests pass (including a regression test that asserts unknown compounding frequencies raise
ValueError); zero regressions.
Breaking changes
verify_compound_interest no longer accepts arbitrary compounding strings. Calls that previously relied on the silent annual-compounding fallback will now raise:# Before — silent fallback to annual
verifier.verify_compound_interest(
principal=10000, rate=0.05, periods=10,
compounding="weekly", # treated as "annual"
llm_amount="$16,288.95",
)
# After — fail-closed
verifier.verify_compound_interest(
principal=10000, rate=0.05, periods=10,
compounding="weekly", # ValueError
llm_amount="$16,288.95",
)
# ValueError: Unknown compounding frequency: 'weekly'.
# Accepted values: annual, daily, monthly, quarterly, semi-annual
Migrate by passing one of the supported frequencies, or pre-compute an equivalent annualized rate.
price_option now returns Greeks as Decimal-quantized strings, matching DerivativesGuard.verify_black_scholes. If your downstream code expected delta as a float, cast explicitly:from decimal import Decimal
delta = Decimal(result.result["delta"])
Audit references
| Finding | Severity | Description | Fix |
|---|
| N-01 | HIGH | Integration-layer Black-Scholes used float while DerivativesGuard used mpmath, producing path-dependent outputs | Delegate _verify_option_price to DerivativesGuard.verify_black_scholes |
| N-04 | MEDIUM | verify_compound_interest silently defaulted unknown frequencies to annual (fail-open) | Raise ValueError listing accepted frequencies |
Release-blocking checklist (post-fix)
- No fail-open execution paths
- No heuristic → verified transitions
- All financial math deterministic
- All parsing fail-closed
- Integration layer enforces verification
- Output status consistent and truthful
- No silent fallback logic
149 tests passed with zero regressions. Release status: NO-GO → GO.
See Open Responses — Black-Scholes single source of truth for the integration-layer change and Derivatives Guard for the underlying mpmath implementation.
QWED-Finance — decimal/mpmath migration for BondGuard, DerivativesGuard, and RiskGuard
Released: May 2, 2026 · GitHub PR #23
Eliminates IEEE-754 float from every financial guard. BondGuard, DerivativesGuard, and RiskGuard now match the precision standard already used by TradingGuard and FinanceVerifier: exact arithmetic with Decimal and arbitrary-precision transcendentals with mpmath.
What changed
- BondGuard — Newton-Raphson YTM solving, duration, and convexity now run in
Decimal with getcontext().prec = 50. tolerance_pct is stored as Decimal; verify_ytm returns quantized percentage strings.
- DerivativesGuard —
mpmath (30 dp) replaces math.log/exp/sqrt/erf in verify_black_scholes, _norm_cdf, _norm_pdf, and verify_put_call_parity. verify_margin_call compares Decimal values.
- RiskGuard —
Decimal.sqrt() replaces math.sqrt() in verify_var and verify_sortino_ratio. verify_beta accumulates covariance in Decimal to eliminate catastrophic cancellation. Z_SCORES is now Decimal-typed.
- Test coverage — 23 new tests in
TestBondGuardDecimal, TestDerivativesGuardMpmath, TestRiskGuardDecimal, and a cross-guard TestNoFloatInOutput IEEE-754 artifact scan.
Breaking changes
Greeks are now str (Decimal-quantized), not float. verify_black_scholes returns result.greeks with each Greek as a quantized string. Cast explicitly when arithmetic is needed:# Before
delta = greeks["delta"] # 0.4502 (float)
exposure = delta * notional
# After
from decimal import Decimal
delta = Decimal(greeks["delta"]) # "0.4502" → Decimal
exposure = delta * Decimal(str(notional))
mpmath is now a runtime dependency. It was already available transitively via sympy, so existing installs require no extra step.
Audit references
| Finding | Description | Fix |
|---|
| C-01 | BondGuard float Newton-Raphson and accumulation | Full Decimal arithmetic |
| C-02 | DerivativesGuard math.* transcendentals | mpmath at 30 dp |
| C-03 | RiskGuard math.sqrt and float covariance cancellation | Decimal.sqrt() and Decimal accumulation |
See Bond Guard, Derivatives Guard, and Risk Guard for the updated guard documentation.
Jump to: QWED-Verification fail-closed unknown agent actions · QWED-Finance v2.1.0 — security audit hardening · QWED-Finance N-01/N-04 release blockers · QWED-Finance Decimal/mpmath migration · QWED-Verification SecureCodeExecutor fail-closed fallback · QWED-Finance security audit — OpenResponses fail-closed · QWED-Finance security audit — rate parsing · QWED-Verification symbolic verifier fail-closed · QWED-Verification strict additionalProperties · QWED-Tax example redaction · QWED-Tax decimal hardening · v5.1.0 · v5.0.0 · v4.0.5 · v4.0.4 · v4.0.3 · v4.0.2 · v4.0.1 · v4.0.0 · v3.0.1 · v2.4.1
QWED-Verification — fail-closed unknown agent actions
Released: May 6, 2026 · GitHub PR #176
Closes a fail-open gap in AgentService.verify_action(). Action types that are not bound to a verification engine or a governed tool are now denied with a dedicated error code instead of silently routing through a permissive default engine.
What changed
AgentService.verify_action — Action types not present in ACTION_ENGINES or TOOL_RISK_LEVELS now return decision: "DENIED" with error.code: "QWED-AGENT-ACTION-001". The reserved conversation step is released so the agent can retry with a registered action type.
AgentService._assess_risk — Raises ValueError for unregistered action types instead of falling back to RiskLevel.MEDIUM. This prevents the deterministic risk path from inferring a default for actions QWED has no registered semantics for.
- Engine resolution — New
_resolve_action_engine helper distinguishes engine-backed actions (math, logic, sql, etc.) from tool-controlled actions, which now report engine: "tool_control" in the verification response. The previous "security" default for unknown actions has been removed.
_run_verification_checks — When an action has no registered engine, an action_registered check is recorded as failed before any other check runs, making the denial reason visible in the checks_failed array.
Behavior comparison
| Action type | Before | After |
|---|
Registered engine action (e.g. verify_math) | engine: "math", normal risk path | Unchanged |
Governed tool (e.g. read_database) | engine: "security" (default) | engine: "tool_control" |
Unregistered action (e.g. do_arbitrary_thing) | engine: "security", MEDIUM risk, may APPROVE/PENDING by trust level | DENIED with QWED-AGENT-ACTION-001 |
Upgrade notes
If your agents emit action_type values that are not in QWED’s engine map or tool registry, they will now be denied. Audit production traffic for non-standard action types and either bind them to an engine or register them as governed tools with an explicit risk level before upgrading.
See Agent Verification — Registered action enforcement for the full denial flow and migration guidance, and API errors for the new error code.
QWED-Verification — SecureCodeExecutor fail-closed when CodeVerifier is unavailable
Released: May 2, 2026
Closes a fail-open gap in SecureCodeExecutor._is_safe_code(). When the primary CodeVerifier cannot be imported or raises at runtime, execution is now blocked instead of falling back to a permissive keyword check that could authorize otherwise-unsafe code.
What changed
SecureCodeExecutor._is_safe_code — An ImportError on CodeVerifier no longer routes authorization to _basic_safety_check(). The method returns (False, "CodeVerifier unavailable; cannot validate code safety.") and execution does not reach containers.run().
- Runtime exceptions in
CodeVerifier.verify_code — Any unexpected exception raised by the verifier (for example, an internal engine failure) is now caught and routed to the same fail-closed denial. The exception message is sanitized before logging and is not returned to callers.
- Shared denial helper — Both branches go through
_build_fail_closed_safety_denial(code), producing the same deterministic (False, "CodeVerifier unavailable; …") result regardless of whether the verifier was missing or crashed mid-call.
- Advisory-only heuristic — The basic keyword scan is retained as advisory context. If it would have flagged the code, its reason is appended to the error message (
"Advisory-only fallback also flagged: <reason>"), but it never authorizes execution on its own.
execute() surface — Calls into SecureCodeExecutor.execute(code, context) now return (success=False, error="Code safety validation failed: CodeVerifier unavailable; cannot validate code safety.", result=None) whenever CodeVerifier is missing or fails, regardless of the input.
Behavior comparison
CodeVerifier state | Code | Before | After |
|---|
| Available | Any | Verified by CodeVerifier | Verified by CodeVerifier (unchanged) |
ImportError | print("hello") | Allowed via basic keyword check | Blocked (CodeVerifier unavailable) |
ImportError | import os; os.system("ls") | Blocked by basic keyword check | Blocked (CodeVerifier unavailable; Advisory-only fallback also flagged: dangerous operation) |
Runtime exception in verify_code | Any | Propagated as an unhandled exception | Blocked (CodeVerifier unavailable); error sanitized in logs |
Upgrade notes
Self-hosted deployments that relied on the implicit basic-keyword fallback when qwed_new.core.code_verifier failed to import — or that swallowed verifier exceptions upstream — must now ensure CodeVerifier is installed, importable, and operational. While the verifier is unavailable or failing, both /verify/stats and /verify/consensus Python execution paths return a safety-check failure with no container run.If you see Code safety validation failed: CodeVerifier unavailable in logs, fix the underlying import error or runtime failure in qwed_new.core.code_verifier rather than disabling the safety gate.
See Security hardening — Fail-closed code execution for the updated fallback behavior.
QWED-Finance — OpenResponses fail-closed and COMPUTED status
Released: April 30, 2026 · GitHub PR #21
Closes the most critical finding from the QWED-Finance security audit: the OpenResponses integration no longer auto-approves tools without a verification function, and built-in tool calls no longer falsely claim verified: true.
What changed
- Fail-closed default — Tools registered without a
verification_fn now return ToolCallStatus.REJECTED instead of ToolCallStatus.APPROVED. A cryptographic receipt is generated for every rejection, ensuring compliance audit trail coverage.
ToolCallStatus.COMPUTED — New enum member distinguishing “we computed this result deterministically” from “we verified an LLM claim.” All four built-in tools (calculate_npv, calculate_loan_payment, check_aml_compliance, price_option) now return COMPUTED.
format_for_responses_api — COMPUTED results output "status": "computed_only" and "verified": false in the streaming payload, preventing downstream agents from trusting unverified computations.
- Error boundary —
verification_fn execution is wrapped in try/except. If a custom verifier raises, it returns ToolCallStatus.ERROR instead of crashing the agent loop.
- AML country consolidation —
_verify_aml now delegates to ComplianceGuard.high_risk_countries (14 countries) instead of a hardcoded subset (5 countries).
- Black-Scholes input guard —
price_option rejects zero/negative values for spot_price, strike_price, time_to_expiry, and volatility before computing.
- Precision preservation — NPV and loan outputs use
Decimal.quantize() instead of float(), preventing IEEE 754 artifacts in monetary results.
Breaking changes
ToolCallStatus.COMPUTED is new. Consumers checking status == APPROVED must also handle COMPUTED as a valid non-error state.Tools registered without verification_fn now return REJECTED. Add a verification_fn to all registered tools.
Audit references
| Finding | Description | Fix |
|---|
| S-01 | handle_tool_call() fail-open default | APPROVED → REJECTED |
| S-02 | Built-in functions hardcoded verified: True | APPROVED → COMPUTED |
| S-04 | float(npv) precision leak | Decimal.quantize() |
| S-06 | AML 5 vs 14 country mismatch | Delegate to ComplianceGuard |
QWED-Finance — rate parsing heuristic removed
Released: April 30, 2026 · GitHub PR #22
Removes silent rate format guessing from BondGuard._parse_rate() and FinanceVerifier.verify_irr(). Both functions used different heuristics that produced 100x errors for the same input.
What changed
BondGuard._parse_rate() — Removed val < 1 heuristic. Bare numbers without % are now treated as decimal fractions (e.g., "1.5" → 1.5, not 0.015).
FinanceVerifier.verify_irr() — Removed llm_rate > 1 heuristic. Uses same explicit logic: % suffix → divide by 100, otherwise use as-is.
_solve_ytm clamp — Widened Newton-Raphson convergence clamp from 1.0 (100%) to 10.0 (1000%) to support distressed debt scenarios.
Breaking changes
Code that passes bare numbers ≥ 1 to _parse_rate() expecting auto-division will break. Use explicit % suffix: "5.25%" instead of "5.25".
| Input | Before | After |
|---|
"5.25%" | 0.0525 ✅ | 0.0525 ✅ |
"0.0525" | 0.0525 ✅ | 0.0525 ✅ |
"1.5" | 0.015 ❌ | 1.5 ✅ |
Audit references
| Finding | Description | Fix |
|---|
| C-04 | _parse_rate silent heuristic | Removed val < 1 |
| H-01 | verify_irr guessing | Removed llm_rate > 1 |
| S-05 | Cross-guard inconsistency | Unified parsing logic |
QWED-Verification — symbolic verifier fail-closed without proof
Released: April 30, 2026
Fix in SymbolicVerifier.verify_code() that closes a fail-open gap where absence of symbolic proof could be reported as a successful verification. The verifier now fails closed unless a real CrossHair proof was actually performed.
What changed
- Code with no functions now returns
is_verified: false and status: "no_verifiable_functions" instead of a pass-like no_functions_to_check result.
- Untyped functions are reported as
skipped and unverifiable, never as verified. CrossHair requires type annotations.
- Mixed typed/untyped code stays unverifiable: a single skipped function is enough to set
is_verified: false and status: "unverifiable".
- Aggregation now distinguishes
functions_discovered, functions_checked, functions_verified, functions_skipped, functions_unverifiable, and counterexamples_found.
is_verified: true now requires at least one function actually checked, all checked functions proven, no skipped or unverifiable functions, and no counterexamples.
- The result dict now also exposes
is_safe and verified as aliases for is_verified, and pre-verification exits (crosshair_not_available, syntax_error) return the same fail-closed shape with zeroed counters.
Result shape
{
"is_verified": false,
"status": "unverifiable",
"message": "Symbolic verification was incomplete; at least one function could not be proven.",
"functions_discovered": 2,
"functions_checked": 1,
"functions_verified": 1,
"functions_skipped": 1,
"functions_unverifiable": 1,
"counterexamples_found": 0,
"issues": [
{
"type": "unverifiable",
"function": "untyped_add",
"description": "Function skipped: no type annotations for symbolic verification"
}
]
}
See Symbolic Limits — Fail-closed verification results for full field documentation.
Upgrade notes
Callers that previously treated status: "no_functions_to_check" as success must now treat the new status: "no_verifiable_functions", "unverifiable", and "verification_error" values as failures. The is_verified boolean is the safest single indicator of a real proof.
Callers that previously treated is_verified: true as proof of correctness without inspecting functions_checked may have been accepting unverified code. Re-run any cached verification results, and ensure downstream gates require functions_checked > 0 in addition to is_verified.
QWED-Verification — strict additionalProperties fail-closed
Released: April 30, 2026
Patch in SchemaVerifier that closes a fail-open gap when strict=True and a schema sets "additionalProperties": false. Payloads with undeclared properties are now rejected as invalid instead of silently passing with an advisory warning.
What changed
SchemaVerifier._validate_object — Undeclared properties under "additionalProperties": false in strict mode are now recorded with severity: "ERROR" instead of "WARNING". Because overall validity is computed from ERROR-severity issues, the payload now correctly returns is_valid: false and status: "INVALID".
- Nested objects with
"additionalProperties": false propagate the same fail-closed behavior at any depth (for example, $.user.role).
- Non-strict mode (
strict=False) is unchanged: additionalProperties: false remains permissive and does not block validation.
Issue shape
{
"path": "$.role",
"issue_type": "additional_property",
"expected": "no additional properties",
"actual": "role",
"severity": "ERROR",
"message": "Additional property 'role' not allowed"
}
See Schema Verifier — additionalProperties: false for the full strict and nested examples.
Upgrade notes
If you previously relied on strict-mode payloads passing despite undeclared properties, those payloads will now fail validation. Either remove the extra fields, declare them in properties, or call verify_schema with strict=False for advisory-only behavior.
QWED-Tax — redacted preflight block details in examples
Released: April 24, 2026
Example scripts bundled with qwed-tax no longer print raw TaxPreFlight block reasons. Demo output is reduced to a high-level allow/block outcome so that running the examples cannot incidentally leak field values from the sample intents.
examples/verify_tax_expansion.py — Replaces direct prints of report["blocks"] with a _print_outcome helper that emits only the pass/fail verdict and a fixed "Verification details intentionally redacted." notice on blocks.
- Guidance — New Redacting block reasons in untrusted contexts section in the Tax integration reference documents the expected handling for
report["blocks"] outside a trusted audit boundary.
No runtime behavior in TaxPreFlight.audit_transaction changes; the report shape (allowed, action, blocks, checks_run, advisories) is unchanged.
QWED-Tax — Decimal hardening in financial guards
Released: April 24, 2026
Patch release across the QWED-Tax cross-border and financial guards. Hardens decimal parsing, fails closed on malformed numeric inputs, and stabilizes JSON output so monetary and ratio fields survive round-trips through non-Python consumers without float drift.
Fail-closed numeric parsing
All financial guards below now route numeric fields through a shared parse_decimal_input helper that rejects booleans, NaN, infinite values, and non-numeric strings before any math runs. Previously, a bare Decimal(str(value)) call would either propagate a raw InvalidOperation or silently produce NaN/Infinity that corrupted downstream comparisons.
DTAAGuard.verify_foreign_tax_credit — Rejects non-numeric, non-finite, boolean, and negative values for foreign_income, foreign_tax_paid, home_tax_rate, and foreign_tax_limit_rate. Returns {"verified": false, "message": "<field> must be a ... numeric value.", "allowable_credit": "0", "excess_tax_lapsed": "0"} on invalid input.
TransferPricingGuard.verify_arms_length_price — Invalid transaction_price, benchmark_price, or tolerance_percent now return {"verified": false, "risk": "INVALID_NUMERIC_INPUT", "safe_harbour_range": [], "potential_adjustment": "0"} instead of raising.
PoEMGuard.determine_residency — Malformed turnover/asset/payroll values, negative values, and impossible invariants (assets_outside_india > assets_total, etc.) now return {"verified": false, "residency": "UNVERIFIABLE", "reason": "..."}. Employee counts must be non-negative and employees_outside_india cannot exceed employees_total.
Additional guards hardened with the same numeric contract: indirect_tax_guard, nexus_guard, related_party_guard, remittance_guard, speculation_guard, tds_guard, and the US withholding_guard.
Breaking changes
Monetary and ratio fields are now returned as plain-text Decimal strings, not Python floats. If you previously read result["allowable_credit"] as a number, update your consumers to parse the string (for example, Decimal(result["allowable_credit"])).
DTAAGuard — allowable_credit and excess_tax_lapsed changed from float to Decimal string (e.g., "150.0" instead of 150.0).
TransferPricingGuard — safe_harbour_range changed from [float, float] to [str, str]. potential_adjustment (renamed from the legacy adjustment_required on the verified path) is now a Decimal string. The tolerance_percent default is now Decimal("3.0") instead of 3.0.
PoEMGuard — metrics.assets_outside_ratio, metrics.employees_outside_ratio, and metrics.payroll_outside_ratio changed from float to Decimal string, quantized to four decimal places. The ABOI decision continues to use the unrounded ratios internally.
PoEM validation flow
PoEMGuard.determine_residency was refactored to separate parsing, validation, ratio computation, and ratio quantization. The split is internal, but two observable effects follow:
- Validation errors are surfaced as structured
UNVERIFIABLE responses rather than Python exceptions.
- Reported ratios are rounded for display only; the
is_aboi decision is still computed from the raw ratios, so no boundary cases flip from RESIDENT to NON-RESIDENT purely from rounding.
Example — migration
from decimal import Decimal
from qwed_tax.guards.dtaa_guard import DTAAGuard
guard = DTAAGuard()
result = guard.verify_foreign_tax_credit(
foreign_income=1000,
foreign_tax_paid=200,
home_tax_rate=15.0,
)
# Before
# allowable_credit = result["allowable_credit"] # 150.0 (float)
# After
allowable_credit = Decimal(result["allowable_credit"]) # Decimal("150.0")
See the QWED-Tax guards reference for full parameter and return-shape details for each guard.
v5.1.1 — Trust Boundary Hardening
Released: May 22, 2026 · GitHub Release · Patch
Packages the post-v5.1.0 trust-boundary and fail-closed corrections into a coherent release. Changes are correctness and security class — not cosmetic.
Cache trust-context binding
PRs #178, #192 · qwed_sdk/cache.py, qwed_sdk/qwed_local.py
Verification cache keys are now bound to the full trust context — provider, model, policy version, and session ID. A cache miss is forced when any of these change, even if the query string matches. Prevents cross-context replay in multi-tenant and multi-provider deployments.
Attestation fail-closed hardening
PRs #188, #194 · src/qwed_new/core/attestation.py
create_verification_attestation() now returns an explicit AttestationResult on all code paths. AttestationStatus is now an enum. is_issued property enforces the fail-closed contract. IssuerKeyPair gains generated_at epoch and key_continuity_policy with an allowlist.
Audit logger fail-closed
PR #179 · src/qwed_new/core/audit.py
Malformed audit payloads now fail closed. Organization-level audit chains are isolated. SQLite writes use BEGIN IMMEDIATE transactions — no partial-write records.
Proof-path and reasoning corrections
PRs #177, #180, #161
Reasoning acceptance requires proof prerequisites. Batch math simplification is separated from the proof path. Symbolic verifier returns BLOCKED when no proof exists.
Agent and executor boundary fixes
PRs #176, #168
Unknown agent actions explicitly denied and logged. Secure executor hard-blocked on unrecognized input shapes.
Schema validation tightening
PR #160
additionalProperties: false enforcement is now strict.
Version table
| Artifact | Previous | This release |
|---|
qwed (PyPI) | 5.1.0 | 5.1.1 |
qwed_sdk (Python) | 5.1.0 | 5.1.1 |
@qwed-ai/sdk (npm) | 5.1.0 | 5.1.1 |
qwed (Rust crate) | 5.1.0 | 5.1.1 |
pip install qwed==5.1.1
docker pull qwedai/qwed-verification:5.1.1
npm install @qwed-ai/sdk@5.1.1
cargo add qwed@5.1.1
go get github.com/QWED-AI/qwed-verification/sdk-go@v5.1.1
v5.1.0 — agent state governance and fail-closed hardening
Released: April 19, 2026 · GitHub Release
Minor release expanding QWED from action verification into state governance while closing adversarial fail-open gaps identified after v5.0.0. Includes AgentStateGuard plus a focused hardening wave across execution, tool governance, mathematical verification, API semantics, and schema validation.
New capability
- AgentStateGuard — Deterministic state verification with strict structural validation, semantic transition checks, and governed atomic state commits. Extends QWED from action-only verification to state and memory governance. See the AgentStateGuard guide for usage details.
Fail-closed hardening
- Legacy
CodeExecutor hard-blocked — CodeExecutor.execute() now raises a RuntimeError on every call, closing the final in-process raw execution path. Migrate any direct imports to SecureCodeExecutor. See the security hardening guide for the migration example.
- Unknown tools default-denied —
ToolApprovalSystem now blocks all unknown tools by default, regardless of heuristic risk score. Tool calls not explicitly allowlisted must be approved through an explicit policy path.
- Bounded math tolerance —
verify_math() rejects oversized, negative, non-finite, and malformed tolerances instead of letting callers weaken correctness checks. See math engine tolerance bounding for details.
- Legacy logic path fails closed —
verify_logic_rule() now raises NotImplementedError instead of returning None. Migrate to LogicVerifier.
# Before (no longer works)
from qwed_new.core.verifier import VerificationEngine
engine = VerificationEngine()
result = engine.verify_logic_rule(rule="if x > 5 then allow", context={"x": 10})
# After
from qwed_new.core.logic_verifier import LogicVerifier
verifier = LogicVerifier()
result = verifier.verify_conclusion(
premises=["x is 10", "x > 5 implies allow"],
conclusion="allow"
)
- Identity sampling rejected —
verify_identity() now returns BLOCKED with is_equivalent: false and method: "numerical_sampling_rejected" when numerical sampling matches but no formal proof exists.
- Ambiguous math API rejected —
POST /verify/math now blocks ambiguous implicit-multiplication expressions (e.g., 1/2(3+1)) with is_valid: false, status: "BLOCKED", and warning: "ambiguous".
{
"is_valid": false,
"result": false,
"status": "BLOCKED",
"warning": "ambiguous",
"message": "Expression may be ambiguous due to implicit multiplication after division",
"simplified": "..."
}
- Schema uniqueness fail-closed —
SchemaVerifier now emits a uniqueness_validation_error when uniqueItems cannot be proven deterministically, instead of silently passing. See the schema verifier uniqueItems section for details.
{
"path": "$.tags",
"issue_type": "uniqueness_validation_error",
"expected": "provably unique items",
"actual": "uniqueness check could not be completed",
"message": "uniqueItems could not be verified deterministically: <error details>"
}
Runtime and security
- Progress-aware doom loop guard — Added LOOP-004 state-aware replay protection for repeated actions on unchanged state. Requires
pre_action_state_hash and state_source in the agent context.
- Security and infrastructure hardening across configs and CI.
- Stats verifier edge-case coverage expansion.
- CodeQL and cleanup follow-ups.
Upgrade notes
Several behaviors changed from ambiguous or silent-pass to explicit failure. Review these before upgrading.
CodeExecutor is no longer usable as a legacy execution path. Migrate any direct imports to SecureCodeExecutor.
- Unknown tools now require explicit allowlisting and are no longer auto-approved at low heuristic risk.
verify_math() may return BLOCKED for tolerances that exceed the deterministic policy bound.
verify_logic_rule() no longer returns an ambiguous non-result; callers must migrate to LogicVerifier.
- Sampling-only
verify_identity() matches now return BLOCKED, not UNKNOWN.
- Ambiguous
/verify/math expressions now return BLOCKED with is_valid: false.
uniqueItems validation failures are now explicit schema errors instead of silent passes.
SDK version bumps
qwed (PyPI): 5.0.0 → 5.1.0
qwed_sdk (Python): 5.0.0 → 5.1.0
@qwed-ai/sdk (npm): 5.0.0 → 5.1.0
Install
pip install qwed==5.1.0
docker pull qwedai/qwed-verification:5.1.0
npm install @qwed-ai/sdk@5.1.0
v5.0.0 — enforcement boundary hardening
Released: April 4, 2026 · GitHub Release
Major release making QWED’s verification boundary fail-closed, deterministic about what it proves, and harder to bypass under adversarial conditions. Consolidates 98 commits and 20 merged PRs since v4.0.1, including the full enforcement hardening series.
Breaking changes
INCONCLUSIVE is a new verification status — Natural-language math responses now return INCONCLUSIVE when verifying LLM-translated expressions, because the translation step is non-deterministic. Downstream consumers must handle this status alongside VERIFIED, ERROR, and BLOCKED.
BLOCKED and UNKNOWN are explicit outcomes — These are no longer treated as generic failures. Consumers should map them to distinct UI states or retry logic.
ActionContext is mandatory for agent verification — All verify_action requests must include conversation_id and step_number. Requests without them are rejected with QWED-AGENT-CTX-001.
security_checks field removed — Exfiltration and MCP poison checks are now server-enforced and unconditional. The security_checks request field and the TypeScript SDK checkExfiltration/checkMcpPoison options no longer exist.
/metrics endpoints require admin authentication — GET /metrics and GET /metrics/prometheus now require an authenticated admin or owner role. Update monitoring integrations accordingly.
- Docker required for stats and consensus verification — The in-process execution fallbacks have been removed. If Docker is unavailable,
/verify/stats and /verify/consensus return HTTP 503.
Security
- Fail-closed verification boundary — Disabled unsafe in-process execution fallbacks; stats and consensus paths now require the secure Docker sandbox.
- Logic verifier eval() removed — The raw
eval() fallback in logic constraint parsing has been removed. If SafeEvaluator is unavailable, the verifier raises a RuntimeError.
- Consensus rate limiting —
POST /verify/consensus now enforces per-tenant rate limiting, matching other verification endpoints.
- Consensus fact self-attestation removed — The fact engine no longer participates in automatic consensus engine selection, preventing self-referential verification loops.
- Redis fail-closed — The sliding window rate limiter now denies requests on Redis errors instead of allowing them.
- Timing-safe token verification — Agent token comparison uses
hmac.compare_digest to prevent timing side-channel attacks.
- Environment integrity enforcement — The API server runs
verify_environment_integrity() at startup before database initialization.
API responses now include a trust_boundary object that describes exactly what was verified and what was not. This gives you machine-readable transparency into the verification scope.
{
"trust_boundary": {
"query_interpretation_source": "llm_translation",
"query_semantics_verified": false,
"verification_scope": "translated_expression_only",
"deterministic_expression_evaluation": true,
"formal_proof": false,
"overall_status": "INCONCLUSIVE"
}
}
See the POST /verify/natural_language endpoint reference for full field descriptions.
Agent hardening
- Action context mandatory — All
verify_action requests must include conversation_id and step_number. Requests without them are rejected with QWED-AGENT-CTX-001.
- Replay detection — Reusing a
(conversation_id, step_number) pair is blocked (QWED-AGENT-LOOP-002).
- Loop detection — Repeating the same action 3+ consecutive times triggers a denial (
QWED-AGENT-LOOP-003).
- In-flight step reservations — Prevents race conditions when multiple agent calls run concurrently.
- Budget denial isolation — Budget-exceeded denials do not consume conversation state, so the agent can retry after the budget resets.
- Stats exception masking — The Stats Engine no longer leaks internal exception details to API callers.
SDK version bumps
qwed (PyPI): 4.0.1 → 5.0.0
qwed_sdk (Python): 2.1.0-dev → 5.0.0
@qwed-ai/sdk (npm): 4.0.1 → 5.0.0
- TypeScript SDK: Removed
security_checks from agent verification helpers; tool_schema remains.
Upgrade
pip install qwed==5.0.0
docker pull qwedai/qwed-verification:5.0.0
npm install @qwed-ai/sdk@5.0.0
If you use POST /verify/natural_language and check the top-level status field, you must now handle INCONCLUSIVE as a valid outcome. Use the trust_boundary object to inspect the detailed verification breakdown. For fully deterministic results, use POST /verify/math directly.
If you use agent verification, all requests must now include conversation_id and step_number in the context. See the agent verification guide for details.
v4.0.5 — stats exception masking
Released: April 3, 2026 · GitHub PR #118
Prevents the Stats Engine from leaking internal exception details to API callers. Clients now receive a generic error message when stats code generation fails.
Security
- Stats translation error masking — When stats code generation fails, the API now returns
"Internal verification error" instead of the raw exception message. This prevents accidental exposure of file paths, API keys, or other sensitive data that may appear in exception text.
- Server-side logging preserved — The exception type is still logged server-side for operator debugging, but the log no longer includes the full exception message.
v4.0.4 — fail-closed execution enforcement
Released: April 3, 2026 · GitHub PR #117
Removes unsafe in-process execution fallbacks from statistical and consensus verification. All model-generated Python now runs exclusively in the secure Docker sandbox.
Breaking changes
- Docker required for stats verification — The Wasm and restricted Python fallback execution paths have been removed from the Stats Engine. If Docker is unavailable,
/verify/stats returns HTTP 503 instead of executing code in-process.
- Consensus Python engine uses Docker — The Python verification engine within consensus verification now runs through
SecureCodeExecutor instead of the in-process CodeExecutor. If Docker is unavailable during high or maximum mode, /verify/consensus returns HTTP 503.
Security
- Fail-closed sandbox gating — Both the Stats Engine and the Consensus Engine refuse to execute model-generated code when the Docker sandbox is not available, preventing any downgrade to in-process execution.
- Live Docker health checks —
SecureCodeExecutor.is_available() now pings the Docker daemon on each request instead of relying on cached startup state, catching mid-operation Docker failures.
API changes
POST /verify/stats now returns HTTP 503 when the secure runtime is unavailable, and HTTP 403 when generated code is blocked by security policy.
POST /verify/consensus now returns HTTP 503 when secure execution is required but Docker is unavailable.
v4.0.3 — server-enforced agent security
Released: April 2, 2026 · GitHub PR #116
Closes critical verification boundary gaps by enforcing agent security checks server-side, adding rate limiting to consensus verification, and removing unsafe fallback paths.
Breaking changes
security_checks removed from agent verification — Exfiltration and MCP poison checks are now enforced server-side on every request. The security_checks request field and the TypeScript SDK checkExfiltration/checkMcpPoison options have been removed. tool_schema remains available to trigger MCP poison inspection.
Security
- Logic verifier fail-closed — The raw
eval() fallback in logic constraint parsing has been removed. If SafeEvaluator is unavailable, the verifier raises an error instead of falling back to unrestricted evaluation.
- Consensus rate limiting — The
POST /verify/consensus endpoint is now rate-limited per API key, matching other verification endpoints.
- Consensus fact self-attestation removed — The Fact engine is no longer automatically selected during consensus verification. It produced self-referential results without external context.
v4.0.2 — security cleanup
Released: April 1, 2026 · GitHub PR #114
Hardens expression evaluation, improves error handling across the SDK and consensus engine, and removes unused code flagged by CodeQL.
Security
- eval() fully eliminated — SymPy and Z3 expression evaluation now uses a custom AST-walking interpreter instead of
compile() + eval(). The evaluator validates every AST node against a strict allow-list, then interprets the tree directly in a restricted namespace.
- CodeGuard integration — Expressions are screened by
CodeGuard (when available) as a second defense layer between AST validation and evaluation.
- Keyword unpacking blocked — Call nodes with
**kwargs-style keyword unpacking are now rejected during AST validation.
Improvements
- Exact SymPy arithmetic — Numeric literals are coerced to
sympy.Integer / sympy.Float during evaluation, preventing floating-point drift in intermediate math comparisons.
- Consensus failure recording — Unexpected errors during async engine aggregation are now logged and recorded as an
EngineResult entry instead of being silently dropped.
- Telemetry initialization — Replaced the
_initialized flag with @lru_cache for one-time cached initialization.
- SDK import cycle broken —
qwed_sdk.cache no longer imports from qwed_sdk.qwed_local for terminal colors; it uses colorama directly with a plain-text fallback.
- Integration imports hardened — Optional framework imports (LangChain, CrewAI, LlamaIndex) now default to
None explicitly instead of using bare except: pass.
Chores
- Removed unused locals and globals across SDK, core modules, examples, and scripts.
- Replaced module-level
print() calls in database.py with logger.debug().
- Added
ast.Tuple and ast.List to the safe SymPy node type allow-list.
v4.0.1 — Sentinel guard sync
Released: March 23, 2026 · GitHub Release · PyPI
Patch release aligning the TypeScript SDK, backend API schemas, and security guard integrations introduced in v4.0.0.
New endpoints
POST /verify/process — Glass-box reasoning process verifier with IRAC structural compliance and custom milestone validation.
- Agent Security Checks —
POST /agents/{id}/verify now accepts security_checks: { exfiltration, mcp_poison } to run ExfiltrationGuard and MCPPoisonGuard before verification.
Security fixes
- Information Disclosure — Removed raw exception messages from
/verify/rag error responses; clients receive only INTERNAL_VERIFICATION_ERROR.
- Symbolic Precision —
RAGVerifyRequest.max_drm_rate changed from float | str → str with field_validator enforcing Fraction-compatible values.
- Response Consistency — RAG error responses now return
"verified": false matching the success path schema.
SDK changes (@qwed-ai/sdk@4.0.1)
- Added
verifyProcess() method for IRAC/milestone validation.
verifyRAG() — maxDrmRate type changed from number to string.
verifyAgent() — Payload aligned with backend schema, agent IDs URL-encoded.
- New types:
Process, RAG, Security in VerificationType enum.
Tests
test_api_phase17_endpoints.py — covers /verify/process, /verify/rag exception masking, and agent security check blocking.
pip install qwed==4.0.1
docker pull qwedai/qwed-verification:4.0.1
npm install @qwed-ai/sdk@4.0.1
v4.0.0 — Sentinel edition
Released: March 12, 2026 · GitHub Release · PyPI
147 commits since v3.0.1 — the largest update in QWED history.
v4.0.0 patch — provider fallbacks, .env loading, and Gemini stability
- Native Gemini provider — Gemini now runs through a dedicated provider with native support for math, logic, stats, fact, and image verification. All API calls enforce a 30-second timeout and automatically strip Markdown code fences from responses.
- Centralized .env loading — Environment variables now load in a deterministic priority order: project
.env first, then ~/.qwed/.env. This prevents configuration drift between CLI and server contexts.
- Gemini AST stability — Fixed intermittent JSON parse failures when Gemini returns code-fenced responses during logic translation.
Agentic security guards (Phase 17)
A brand-new guard subsystem for securing AI agent tool chains and RAG pipelines:
- RAGGuard — Detects prompt injection, data poisoning, and context manipulation in RAG pipelines. IRAC-compliant reporting.
- ExfiltrationGuard — Prevents data exfiltration through agent tool calls by analyzing output patterns and destination validation.
- MCP Poison Guard — Detects poisoned or tampered MCP tool definitions before agent execution.
All three guards went through five rounds of security review via CodeRabbit and SonarCloud.
New standalone guards
- SovereigntyGuard — Enforces data residency policies and local routing rules (GDPR, data localization).
- ToxicFlowGuard — Stateful detection of toxic tool-chaining patterns across multi-step agent workflows.
- SelfInitiatedCoTGuard (S-CoT) — Verifies self-initiated Chain-of-Thought logic paths for reasoning integrity.
Process determinism
A new class of deterministic verification:
- ProcessVerifier — IRAC/milestone-based process verification with decimal scoring, budget-aware timeouts, and structured compliance reporting. Ensures AI-driven workflows follow deterministic process steps — not just correct answers, but correct procedures.
Critical security fixes
- Replaced direct
eval() with AST-validated execution (Code Injection Prevention). Further hardened in v4.0.2 with a full AST-walking interpreter.
- Patched critical sandbox escape and namespace mismatch.
- Hardened SymPy input parsing against injection.
- Fixed URL whitespace bypass and protocol wildcard bypass.
- Resolved CVE-2026-24049 (Critical), CVE-2025-8869, and HTTP request smuggling.
- Fixed all 19 Snyk Code findings.
- Secured exception handling across
verify_logic, ControlPlane, verify_stats, agent_tool_call.
Docker hardening
- Pinned base image digests with hash-verified requirements
- Non-root user execution with
gosu/runuser
- Automated Docker Hub publishing on release
- SBOM generation (SPDX) and Docker Scout scanning
docker pull qwedai/qwed-verification:4.0.0
CI/CD infrastructure
- Sentry SDK — Error tracking and monitoring.
- CircleCI — Python matrix testing (3.10, 3.11, 3.12).
- SonarCloud — Code quality and coverage.
- Snyk — Security scanning with SARIF output.
- Docker Auto-Publish — Automated image push on every release.
Documentation and badges
- OpenSSF Best Practices badge (Silver)
- Snyk security badge and partner attribution
- Docker Hub pulls badge and BuildKit badge
- 11 verification engines across all docs
v3.0.1 — ironclad update
Released: February 4, 2026 · GitHub Release
Critical security hardening
- CodeQL Remediation: Resolved 50+ alerts including ReDoS, Clear-text Logging, and Exception Exposure.
- Workflow Permissions: Enforced
permissions: contents: read across all GitHub Actions to adhere to Least Privilege.
- PII Protection: Implemented
redact_pii logic in all API endpoints and exception handlers.
Compliance
- Snyk Attribution: Added Snyk attribution to README and Documentation footer for Partner Program compliance.
Bug fixes
- API Stability: Fixed unhandled exceptions in
verify_logic and agent_tool_call endpoints.
v2.4.1 — the reasoning engine
Released: January 20, 2026 · GitHub Release
New features
- Optimization Engine (
verify_optimization): Added LogicVerifier support for Z3’s Optimize context.
- Vacuity Checker (
check_vacuity): Added logical proof to detect “Vacuous Truths”.
Enterprise updates
- Dockerized GitHub Action: The main
qwed-verification action now runs in a Docker container.
Fixes and improvements
- Updated
logic_verifier.py with additive, non-breaking methods.
- Replaced shell-based
action_entrypoint.sh with Python handler action_entrypoint.py.