QWED-Tax — fail-closed on unknown or unmodeled tax rules
Released: June 19, 2026 · GitHub PR #41Closes a fail-open gap across six QWED-Tax guards. Inputs that hit an unmodeled rule path (unknown service type, state, asset class, payment type, address jurisdiction, or ITC category) no longer return a quietverified=True. They now returnverified=Falsewith a structured error, so an upstream LLM cannot route a payment through a category the guard has not been configured to evaluate.
What changed
TDSGuard.calculate_deduction— Unknownservice_typereturns{"verified": False, "error": "No TDS rule configured for service type '<TYPE>'. Cannot verify — block pending rule configuration."}. Previously returned{"verified": True, "deduction": "0"}, whichTaxPreFlight._check_invoice_tdsaccepted as a clean payment with zero withholding.CapitalGainsGuard.verify_tax_rate— Unknown(asset_type, term)pair returns{"verified": False, "error": "No statutory rate configured for <asset>_<term>. Cannot verify claimed rate."}. The “no hard constraint, assume verified” branch has been removed.NexusGuard.check_nexus_liability— Unconfiguredstatereturns{"verified": False, "error": "State <CODE> not in configured nexus threshold table. Cannot verify nexus liability — block pending rule configuration."}instead ofverified=True“not in high-risk list”.AddressGuard.verify_address— Unmodeled state returns{"verified": False, "message": "State <CODE> not in validation database. Address cannot be auto-verified — manual review required."}instead ofverified=True“assumed valid”.Form1099Guard.verify_filing_requirement— Unmodeledpayment_typereturns{"filing_required": "UNVERIFIABLE", "form": None, "reason": "No filing rule configured for payment type '<TYPE>'. Cannot verify filing requirement — manual determination required."}. Previously returnedfiling_required=False.InputCreditGuard— ITC under GST is “allowed unless specifically blocked”, so the default verdict for unknown categories is stillverified=True. The response now also carries"unverified_category": Trueandaudit_trace.inputs.category_match: "default_allow"so consumers can distinguish a known-eligible category from a default-allow fallback.
Why this matters
TDSGuard was the live exploitation path. An AI agent that classified a vendor payment under an unrecognized service category produced verified=True, deduction="0", which TaxPreFlight._check_invoice_tds treated as “no block needed” — the payment executed with zero withholding. All six guards above now refuse to sign off on rule paths they cannot independently evaluate.
Behavior comparison
Breaking changes
Migration
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_traceon every guard — an ordered list ofVerificationSteprecords that make each decision auditable. Not a narrative explanation.evidence_typetaxonomy — each step isDETERMINISTIC,PARSED,INFERRED,HEURISTIC, orUNSUPPORTED.VerificationStep.is_proven()returnsTrueonly forDETERMINISTIC.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 explicitis_provenflag.
Trust-boundary changes
FairnessGuardnever returnsverified=True(#18) — fairness cannot be proven by counterfactual text substitution and string equality. A consistent outcome isUNVERIFIABLE_FAIRNESS; a differing outcome is aHEURISTIC_BIAS_SIGNALfor human review. Malformed swap input (empty, non-string values, case-colliding keys) fails closed.CitationGuardseparates format from authority (#17) —verifiedis alwaysFalse; a format match returnsstatus="unverifiable_authority". Format validity is never treated as proof a cited authority exists.IRACGuardfail-closed reasoning boundary (#12) — structure checks areINFERREDand the reasoning conclusion isUNSUPPORTED; a passing result isunverifiable_reasoning, never proof of correct legal reasoning.ClauseGuardnon-operative keyword handling (#11) — heuristic conclusions areINFERRED; limited or ambiguous coverage fails closed instead of returning a misleading “consistent”.StatuteOfLimitationsGuardandContradictionGuardreclassified asMIXED— deterministic core (date arithmetic / Z3 SAT-UNSAT) over a parsed lookup; unmodeled inputs fail closed.
Security hardening
JurisdictionGuardfail-closed on ambiguity and empty parties (#16, #20) — unresolved warnings fail verification; emptyparties_countriesno longer returns a falseverified=True(all([])fail-open fixed) in bothverify_choice_of_lawandcheck_convention_applicability.LiabilityGuardexact-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 areNone(notdatetime.min).
SDK changes (TypeScript @qwed-ai/legal)
- Every result interface now exposes
verification_trace. - Added
FairnessVerifierreflecting the fail-closed contract;verifiedis hardcodedfalseat the boundary. CitationResultexposesformat_valid/status/verified: false;ClauseResult.conflictstyped as[number, number, string][].- The fairness client factory is resolved via a validated
importlibdescriptor (no raw code execution). - npm version reconciled from
1.0.0to0.4.0to match the Python package.
Breaking changes
FairnessGuard.verify_decision_fairnessno longer returnsverified=True. Migration: treat the output as a signal requiring human review, consumingstatus/riskinstead ofverified.
QWED-Verification — context-bound verification cache (replay prevention)
Released: May 17, 2026VerificationCachenow 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 ofVERIFIEDresults.
What changed
- New
CacheContextdatatype — Required argument to everyget()andset()call. Carriesprovider,model, andpolicy_version(required), plus optionaltenant_idandenv_fingerprint. - Composite cache key — Each key combines
SHA-256(normalized_query)withSHA-256(canonical_context_json). Identical queries under different contexts never collide. - New
cache_v2schema — The on-disk SQLite table uses a compositePRIMARY 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)andVerificationCache.set(query, result)now require aCacheContextargument. Calls without one raiseTypeError.- 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
QWED-Legal — StatuteOfLimitationsGuard fail-closed on unknown jurisdictions and claim types
Released: May 10, 2026
HardensStatuteOfLimitationsGuardso unknown jurisdictions and claim types return anUNVERIFIABLEresult 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_LIMITATIONStable. They now return aStatuteResultwithverified=False,jurisdiction_matched=False, all date and period fields set toNone, 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()returnsOptional[float]— ReturnsNonewhen the jurisdiction or claim type is unsupported, instead of always returning a number.compare_jurisdictions()returnsDict[str, Optional[float]]— Unsupported jurisdictions in the input list map toNonein the result.- New
StatuteResultfields —jurisdiction_matched: boolandclaim_type_matched: boollet callers distinguish “claim is time-barred” from “we cannot determine the limit”.
Breaking changes
StatuteResult.incident_date,filing_date,limitation_period_years,expiration_date, anddays_remainingare nowOptionaland areNonewhen the input is unverifiable. Code that does arithmetic or comparisons on these fields must checkresult.verified(and the new*_matchedflags) first.get_limitation_period()now returnsOptional[float]. Code that previously assumed a numeric result must handleNone.- 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
QWED-Verification — audit logger fail-closed and per-organization chains
Released: May 8, 2026 · GitHub PR #179Hardens 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 raisesSecurityErrorwhenQWED_AUDIT_SECRET_KEYis unset or empty. The previous insecuredev_only_change_in_productionfallback 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 IMMEDIATEon SQLite (orSELECT … FOR UPDATEon other backends). - Constant-time HMAC comparison — Signature checks use
hmac.compare_digestto prevent timing side channels.
Action required
SetQWED_AUDIT_SECRET_KEY in every environment that runs the verification service (including CI and local development). The service will not start without it.
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_actiondenies unregistered action types — Calls whoseaction_typeis not inACTION_ENGINESorTOOL_RISK_LEVELSnow returndecision: "DENIED"with error codeQWED-AGENT-ACTION-001. The verification kernel no longer falls back to a"security"engine label for unknown actions.- Risk assessment is now deterministic —
_assess_riskraisesValueErrorif it is invoked for an unregisteredaction_type. Risk levels must come from explicit registration. - Step reservation released on action denial — A
QWED-AGENT-ACTION-001denial releases the in-flight step reservation, so the agent can retry the same(conversation_id, step_number)pair with a registered action type.
Example
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, 2026The agent service now rejects verification requests whoseaction_typehas no explicit registered semantics. Previously, an unrecognized action could pass through risk assessment and receive anAPPROVEDdecision with a generic"security"engine fallback.
Security
References
QWED-Finance v2.1.0 — security audit hardening
Released: May 2, 2026 · GitHub PR #25Roll-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, bumpspyproject.tomlfrom2.0.1to2.1.0, and promotesmpmathto an explicit runtime dependency.
What’s in this release
Breaking changes
Dependency changes
mpmath>=1.3.0is now a declared runtime dependency inpyproject.toml. It was already available transitively viasympy, 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
QWED-Finance — release blockers N-01 and N-04 resolved (NO-GO → GO)
Released: May 2, 2026 · GitHub PR #24Closes the two release-blocking findings from the post-fix deterministic audit. The integration layer now shares a single Black-Scholes implementation withDerivativesGuard, andFinanceVerifier.verify_compound_interestno longer silently defaults unknown compounding frequencies.
What changed
OpenResponsesIntegration._verify_option_price— Removed the duplicate IEEE-754math.log/exp/sqrt/erfBlack-Scholes routine. Theprice_optiontool now delegates toDerivativesGuard.verify_black_scholes(), so OpenResponses-mediated pricing and direct guard pricing are bit-identical.- Tool result fields —
priceanddeltaare now sourced from the guard’smpmath-backed,Decimal-quantized output instead of the integration’s own float computation. FinanceVerifier.verify_compound_interest— Unknown values forcompoundingnow raiseValueErrorlisting 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
Audit references
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
QWED-Finance — decimal/mpmath migration for BondGuard, DerivativesGuard, and RiskGuard
Released: May 2, 2026 · GitHub PR #23Eliminates IEEE-754floatfrom every financial guard.BondGuard,DerivativesGuard, andRiskGuardnow match the precision standard already used byTradingGuardandFinanceVerifier: exact arithmetic withDecimaland arbitrary-precision transcendentals withmpmath.
What changed
- BondGuard — Newton-Raphson YTM solving, duration, and convexity now run in
Decimalwithgetcontext().prec = 50.tolerance_pctis stored asDecimal;verify_ytmreturns quantized percentage strings. - DerivativesGuard —
mpmath(30 dp) replacesmath.log/exp/sqrt/erfinverify_black_scholes,_norm_cdf,_norm_pdf, andverify_put_call_parity.verify_margin_callcomparesDecimalvalues. - RiskGuard —
Decimal.sqrt()replacesmath.sqrt()inverify_varandverify_sortino_ratio.verify_betaaccumulates covariance inDecimalto eliminate catastrophic cancellation.Z_SCORESis nowDecimal-typed. - Test coverage — 23 new tests in
TestBondGuardDecimal,TestDerivativesGuardMpmath,TestRiskGuardDecimal, and a cross-guardTestNoFloatInOutputIEEE-754 artifact scan.
Breaking changes
Audit references
See Bond Guard, Derivatives Guard, and Risk Guard for the updated guard documentation.
Jump to: QWED-Tax fail-closed unknown tax rules · 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 inACTION_ENGINESorTOOL_RISK_LEVELSnow returndecision: "DENIED"witherror.code: "QWED-AGENT-ACTION-001". The reserved conversation step is released so the agent can retry with a registered action type.AgentService._assess_risk— RaisesValueErrorfor unregistered action types instead of falling back toRiskLevel.MEDIUM. This prevents the deterministic risk path from inferring a default for actions QWED has no registered semantics for.- Engine resolution — New
_resolve_action_enginehelper distinguishes engine-backed actions (math,logic,sql, etc.) from tool-controlled actions, which now reportengine: "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, anaction_registeredcheck is recorded as failed before any other check runs, making the denial reason visible in thechecks_failedarray.
Behavior comparison
Upgrade notes
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, 2026Closes a fail-open gap inSecureCodeExecutor._is_safe_code(). When the primaryCodeVerifiercannot 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— AnImportErroronCodeVerifierno longer routes authorization to_basic_safety_check(). The method returns(False, "CodeVerifier unavailable; cannot validate code safety.")and execution does not reachcontainers.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 intoSecureCodeExecutor.execute(code, context)now return(success=False, error="Code safety validation failed: CodeVerifier unavailable; cannot validate code safety.", result=None)wheneverCodeVerifieris missing or fails, regardless of the input.
Behavior comparison
Upgrade notes
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_fnnow returnToolCallStatus.REJECTEDinstead ofToolCallStatus.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 returnCOMPUTED.format_for_responses_api—COMPUTEDresults output"status": "computed_only"and"verified": falsein the streaming payload, preventing downstream agents from trusting unverified computations.- Error boundary —
verification_fnexecution is wrapped intry/except. If a custom verifier raises, it returnsToolCallStatus.ERRORinstead of crashing the agent loop. - AML country consolidation —
_verify_amlnow delegates toComplianceGuard.high_risk_countries(14 countries) instead of a hardcoded subset (5 countries). - Black-Scholes input guard —
price_optionrejects zero/negative values forspot_price,strike_price,time_to_expiry, andvolatilitybefore computing. - Precision preservation — NPV and loan outputs use
Decimal.quantize()instead offloat(), preventing IEEE 754 artifacts in monetary results.
Breaking changes
Audit references
QWED-Finance — rate parsing heuristic removed
Released: April 30, 2026 · GitHub PR #22Removes silent rate format guessing fromBondGuard._parse_rate()andFinanceVerifier.verify_irr(). Both functions used different heuristics that produced 100x errors for the same input.
What changed
BondGuard._parse_rate()— Removedval < 1heuristic. Bare numbers without%are now treated as decimal fractions (e.g.,"1.5"→1.5, not0.015).FinanceVerifier.verify_irr()— Removedllm_rate > 1heuristic. Uses same explicit logic:%suffix → divide by 100, otherwise use as-is._solve_ytmclamp — Widened Newton-Raphson convergence clamp from1.0(100%) to10.0(1000%) to support distressed debt scenarios.
Breaking changes
Audit references
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: falseandstatus: "no_verifiable_functions"instead of a pass-likeno_functions_to_checkresult. - Untyped functions are reported as
skippedandunverifiable, never as verified. CrossHair requires type annotations. - Mixed typed/untyped code stays unverifiable: a single skipped function is enough to set
is_verified: falseandstatus: "unverifiable". - Aggregation now distinguishes
functions_discovered,functions_checked,functions_verified,functions_skipped,functions_unverifiable, andcounterexamples_found. is_verified: truenow 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_safeandverifiedas aliases foris_verified, and pre-verification exits (crosshair_not_available,syntax_error) return the same fail-closed shape with zeroed counters.
Result shape
Upgrade notes
QWED-Verification — strict additionalProperties fail-closed
Released: April 30, 2026
Patch inSchemaVerifierthat closes a fail-open gap whenstrict=Trueand 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": falsein strict mode are now recorded withseverity: "ERROR"instead of"WARNING". Because overall validity is computed fromERROR-severity issues, the payload now correctly returnsis_valid: falseandstatus: "INVALID".- Nested objects with
"additionalProperties": falsepropagate the same fail-closed behavior at any depth (for example,$.user.role). - Non-strict mode (
strict=False) is unchanged:additionalProperties: falseremains permissive and does not block validation.
Issue shape
additionalProperties: false for the full strict and nested examples.
Upgrade notes
QWED-Tax — redacted preflight block details in examples
Released: April 24, 2026Example scripts bundled withqwed-taxno longer print rawTaxPreFlightblock 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 ofreport["blocks"]with a_print_outcomehelper 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.
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, 2026Patch 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 sharedparse_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 forforeign_income,foreign_tax_paid,home_tax_rate, andforeign_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— Invalidtransaction_price,benchmark_price, ortolerance_percentnow 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 andemployees_outside_indiacannot exceedemployees_total.
indirect_tax_guard, nexus_guard, related_party_guard, remittance_guard, speculation_guard, tds_guard, and the US withholding_guard.
Breaking changes
DTAAGuard—allowable_creditandexcess_tax_lapsedchanged fromfloatto Decimal string (e.g.,"150.0"instead of150.0).TransferPricingGuard—safe_harbour_rangechanged from[float, float]to[str, str].potential_adjustment(renamed from the legacyadjustment_requiredon the verified path) is now a Decimal string. Thetolerance_percentdefault is nowDecimal("3.0")instead of3.0.PoEMGuard—metrics.assets_outside_ratio,metrics.employees_outside_ratio, andmetrics.payroll_outside_ratiochanged fromfloatto 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
UNVERIFIABLEresponses rather than Python exceptions. - Reported ratios are rounded for display only; the
is_aboidecision is still computed from the raw ratios, so no boundary cases flip fromRESIDENTtoNON-RESIDENTpurely from rounding.
Example — migration
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 returnsBLOCKED 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 #160additionalProperties: false enforcement is now strict.
Version table
v5.1.0 — agent state governance and fail-closed hardening
Released: April 19, 2026 · GitHub ReleaseMinor 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
CodeExecutorhard-blocked —CodeExecutor.execute()now raises aRuntimeErroron every call, closing the final in-process raw execution path. Migrate any direct imports toSecureCodeExecutor. See the security hardening guide for the migration example. - Unknown tools default-denied —
ToolApprovalSystemnow 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 raisesNotImplementedErrorinstead of returningNone. Migrate toLogicVerifier.
- Identity sampling rejected —
verify_identity()now returnsBLOCKEDwithis_equivalent: falseandmethod: "numerical_sampling_rejected"when numerical sampling matches but no formal proof exists. - Ambiguous math API rejected —
POST /verify/mathnow blocks ambiguous implicit-multiplication expressions (e.g.,1/2(3+1)) withis_valid: false,status: "BLOCKED", andwarning: "ambiguous".
- Schema uniqueness fail-closed —
SchemaVerifiernow emits auniqueness_validation_errorwhenuniqueItemscannot be proven deterministically, instead of silently passing. See the schema verifieruniqueItemssection for 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_hashandstate_sourcein the agent context. - Security and infrastructure hardening across configs and CI.
- Stats verifier edge-case coverage expansion.
- CodeQL and cleanup follow-ups.
Upgrade notes
CodeExecutoris no longer usable as a legacy execution path. Migrate any direct imports toSecureCodeExecutor.- Unknown tools now require explicit allowlisting and are no longer auto-approved at low heuristic risk.
verify_math()may returnBLOCKEDfor tolerances that exceed the deterministic policy bound.verify_logic_rule()no longer returns an ambiguous non-result; callers must migrate toLogicVerifier.- Sampling-only
verify_identity()matches now returnBLOCKED, notUNKNOWN. - Ambiguous
/verify/mathexpressions now returnBLOCKEDwithis_valid: false. uniqueItemsvalidation failures are now explicit schema errors instead of silent passes.
SDK version bumps
qwed(PyPI):5.0.0→5.1.0qwed_sdk(Python):5.0.0→5.1.0@qwed-ai/sdk(npm):5.0.0→5.1.0
Install
v5.0.0 — enforcement boundary hardening
Released: April 4, 2026 · GitHub ReleaseMajor 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
INCONCLUSIVEis a new verification status — Natural-language math responses now returnINCONCLUSIVEwhen verifying LLM-translated expressions, because the translation step is non-deterministic. Downstream consumers must handle this status alongsideVERIFIED,ERROR, andBLOCKED.BLOCKEDandUNKNOWNare explicit outcomes — These are no longer treated as generic failures. Consumers should map them to distinct UI states or retry logic.ActionContextis mandatory for agent verification — Allverify_actionrequests must includeconversation_idandstep_number. Requests without them are rejected withQWED-AGENT-CTX-001.security_checksfield removed — Exfiltration and MCP poison checks are now server-enforced and unconditional. Thesecurity_checksrequest field and the TypeScript SDKcheckExfiltration/checkMcpPoisonoptions no longer exist./metricsendpoints require admin authentication —GET /metricsandGET /metrics/prometheusnow 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/statsand/verify/consensusreturn 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. IfSafeEvaluatoris unavailable, the verifier raises aRuntimeError. - Consensus rate limiting —
POST /verify/consensusnow 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_digestto 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 atrust_boundary object that describes exactly what was verified and what was not. This gives you machine-readable transparency into the verification scope.
POST /verify/natural_language endpoint reference for full field descriptions.
Agent hardening
- Action context mandatory — All
verify_actionrequests must includeconversation_idandstep_number. Requests without them are rejected withQWED-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.0qwed_sdk(Python):2.1.0-dev→5.0.0@qwed-ai/sdk(npm):4.0.1→5.0.0- TypeScript SDK: Removed
security_checksfrom agent verification helpers;tool_schemaremains.
Upgrade
v4.0.5 — stats exception masking
Released: April 3, 2026 · GitHub PR #118Prevents 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 #117Removes 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/statsreturns HTTP 503 instead of executing code in-process. - Consensus Python engine uses Docker — The Python verification engine within consensus verification now runs through
SecureCodeExecutorinstead of the in-processCodeExecutor. If Docker is unavailable duringhighormaximummode,/verify/consensusreturns 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/statsnow returns HTTP 503 when the secure runtime is unavailable, and HTTP 403 when generated code is blocked by security policy.POST /verify/consensusnow 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 #116Closes 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_checksremoved from agent verification — Exfiltration and MCP poison checks are now enforced server-side on every request. Thesecurity_checksrequest field and the TypeScript SDKcheckExfiltration/checkMcpPoisonoptions have been removed.tool_schemaremains available to trigger MCP poison inspection.
Security
- Logic verifier fail-closed — The raw
eval()fallback in logic constraint parsing has been removed. IfSafeEvaluatoris unavailable, the verifier raises an error instead of falling back to unrestricted evaluation. - Consensus rate limiting — The
POST /verify/consensusendpoint 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 #114Hardens 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.Floatduring 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
EngineResultentry instead of being silently dropped. - Telemetry initialization — Replaced the
_initializedflag with@lru_cachefor one-time cached initialization. - SDK import cycle broken —
qwed_sdk.cacheno longer imports fromqwed_sdk.qwed_localfor terminal colors; it usescoloramadirectly with a plain-text fallback. - Integration imports hardened — Optional framework imports (LangChain, CrewAI, LlamaIndex) now default to
Noneexplicitly instead of using bareexcept: pass.
Chores
- Removed unused locals and globals across SDK, core modules, examples, and scripts.
- Replaced module-level
print()calls indatabase.pywithlogger.debug(). - Added
ast.Tupleandast.Listto the safe SymPy node type allow-list.
v4.0.1 — Sentinel guard sync
Released: March 23, 2026 · GitHub Release · PyPIPatch 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}/verifynow acceptssecurity_checks: { exfiltration, mcp_poison }to runExfiltrationGuardandMCPPoisonGuardbefore verification.
Security fixes
- Information Disclosure — Removed raw exception messages from
/verify/ragerror responses; clients receive onlyINTERNAL_VERIFICATION_ERROR. - Symbolic Precision —
RAGVerifyRequest.max_drm_ratechanged fromfloat | str→strwithfield_validatorenforcing Fraction-compatible values. - Response Consistency — RAG error responses now return
"verified": falsematching the success path schema.
SDK changes (@qwed-ai/sdk@4.0.1)
- Added
verifyProcess()method for IRAC/milestone validation. verifyRAG()—maxDrmRatetype changed fromnumbertostring.verifyAgent()— Payload aligned with backend schema, agent IDs URL-encoded.- New types:
Process,RAG,SecurityinVerificationTypeenum.
Tests
test_api_phase17_endpoints.py— covers/verify/process,/verify/ragexception masking, and agent security check blocking.
v4.0.0 — Sentinel edition
Released: March 12, 2026 · GitHub Release · PyPI147 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
.envfirst, 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.
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
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 ReleaseCritical security hardening
- CodeQL Remediation: Resolved 50+ alerts including ReDoS, Clear-text Logging, and Exception Exposure.
- Workflow Permissions: Enforced
permissions: contents: readacross all GitHub Actions to adhere to Least Privilege. - PII Protection: Implemented
redact_piilogic 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_logicandagent_tool_callendpoints.
v2.4.1 — the reasoning engine
Released: January 20, 2026 · GitHub ReleaseNew features
- Optimization Engine (
verify_optimization): AddedLogicVerifiersupport for Z3’sOptimizecontext. - Vacuity Checker (
check_vacuity): Added logical proof to detect “Vacuous Truths”.
Enterprise updates
- Dockerized GitHub Action: The main
qwed-verificationaction now runs in a Docker container.
Fixes and improvements
- Updated
logic_verifier.pywith additive, non-breaking methods. - Replaced shell-based
action_entrypoint.shwith Python handleraction_entrypoint.py.