Skip to main content
All notable changes to the QWED platform, listed by release.
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.
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 fieldsjurisdiction_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 startupAuditLogger() 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

PRChangeAudit findings
#21Fail-closed enforcement in OpenResponsesIntegration + AML high-risk country list unified across all pathsS-01, S-02, S-06
#22BondGuard._parse_rate() heuristic removed; returns DecimalC-04, H-01, S-05
#23floatDecimal/mpmath migration for BondGuard, DerivativesGuard, RiskGuardC-01, C-02, C-03
#24Integration-layer Black-Scholes unified with DerivativesGuard; verify_compound_interest fail-closed on unknown frequenciesN-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 fieldsprice 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

FindingSeverityDescriptionFix
N-01HIGHIntegration-layer Black-Scholes used float while DerivativesGuard used mpmath, producing path-dependent outputsDelegate _verify_option_price to DerivativesGuard.verify_black_scholes
N-04MEDIUMverify_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.
  • DerivativesGuardmpmath (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.
  • RiskGuardDecimal.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

FindingDescriptionFix
C-01BondGuard float Newton-Raphson and accumulationFull Decimal arithmetic
C-02DerivativesGuard math.* transcendentalsmpmath at 30 dp
C-03RiskGuard math.sqrt and float covariance cancellationDecimal.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 typeBeforeAfter
Registered engine action (e.g. verify_math)engine: "math", normal risk pathUnchanged
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 levelDENIED 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 stateCodeBeforeAfter
AvailableAnyVerified by CodeVerifierVerified by CodeVerifier (unchanged)
ImportErrorprint("hello")Allowed via basic keyword checkBlocked (CodeVerifier unavailable)
ImportErrorimport os; os.system("ls")Blocked by basic keyword checkBlocked (CodeVerifier unavailable; Advisory-only fallback also flagged: dangerous operation)
Runtime exception in verify_codeAnyPropagated as an unhandled exceptionBlocked (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_apiCOMPUTED results output "status": "computed_only" and "verified": false in the streaming payload, preventing downstream agents from trusting unverified computations.
  • Error boundaryverification_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 guardprice_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

FindingDescriptionFix
S-01handle_tool_call() fail-open defaultAPPROVEDREJECTED
S-02Built-in functions hardcoded verified: TrueAPPROVEDCOMPUTED
S-04float(npv) precision leakDecimal.quantize()
S-06AML 5 vs 14 country mismatchDelegate 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".
InputBeforeAfter
"5.25%"0.05250.0525
"0.0525"0.05250.0525
"1.5"0.0151.5

Audit references

FindingDescriptionFix
C-04_parse_rate silent heuristicRemoved val < 1
H-01verify_irr guessingRemoved llm_rate > 1
S-05Cross-guard inconsistencyUnified 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"])).
  • DTAAGuardallowable_credit and excess_tax_lapsed changed from float to Decimal string (e.g., "150.0" instead of 150.0).
  • TransferPricingGuardsafe_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.
  • PoEMGuardmetrics.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

ArtifactPreviousThis release
qwed (PyPI)5.1.05.1.1
qwed_sdk (Python)5.1.05.1.1
@qwed-ai/sdk (npm)5.1.05.1.1
qwed (Rust crate)5.1.05.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-blockedCodeExecutor.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-deniedToolApprovalSystem 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 toleranceverify_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 closedverify_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 rejectedverify_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 rejectedPOST /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-closedSchemaVerifier 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.05.1.0
  • qwed_sdk (Python): 5.0.05.1.0
  • @qwed-ai/sdk (npm): 5.0.05.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 authenticationGET /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 limitingPOST /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.

Trust boundary metadata

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.15.0.0
  • qwed_sdk (Python): 2.1.0-dev5.0.0
  • @qwed-ai/sdk (npm): 4.0.15.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 checksSecureCodeExecutor.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 brokenqwed_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 ChecksPOST /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 PrecisionRAGVerifyRequest.max_drm_rate changed from float | strstr 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.