This file is for Claude Code. It documents the architecture, decisions made, and instructions for continuing work in future sessions.
Aevoxis Warden Engine: Spec-Drift Chronometer Top 300 finalist in the AWS 10,000 AIdeas Competition 2025. Built by Vinita Silaparasetty, AI Governance Engineer, Aevoxis Solutions (aevoxis.de).
The product demonstrates EU AI Act Articles 12, 13, 14 & 50 compliance engineering: real-time drift detection between human-authored architectural specs and AI-generated code, with a Human-in-the-Loop Justification Gate that blocks execution until a human approves or rejects the drift.
Live demo: https://spec-drift-chronometer.aevoxis.de
spec-drift_chronometer/
├── backend/
│ ├── main.py # FastAPI Warden Engine — single file, all logic here
│ ├── requirements.txt # fastapi, mangum, pydantic, uvicorn, google-generativeai, requests
│ ├── Procfile # web: uvicorn main:app --host 0.0.0.0 --port 8080
│ ├── Dockerfile # Lambda container image (public.ecr.aws/lambda/python:3.12)
│ └── provision_ledger.sh # DynamoDB Intent Ledger setup script
├── frontend/
│ ├── src/app/
│ │ ├── page.tsx # Root page — polls /drift every 3s, passes state down
│ │ └── layout.tsx # App shell — fonts, metadata, viewport
│ ├── src/components/
│ │ ├── DriftDashboard.tsx # Main UI — chart, stat cards, logs, spec vault
│ │ ├── JustificationGate.tsx # Article 14 modal — submit justification, show result
│ │ └── GovernanceActions.tsx # Run Audit / Download Audit buttons
│ ├── next.config.ts # output: 'export' + reactCompiler: true
│ ├── package.json # Next.js 16, React 19, Tailwind 4
│ └── public/
│ ├── icon.png
│ └── manifest.json
├── test_research/
│ ├── drift_calculator.py # Standalone drift scorer (gitpython, no backend needed)
│ ├── run_tests.py # 3-phase test runner: drift / justification / audit
│ ├── run_failure_modes.py # 8 EU AI Act failure mode tests (FM1,FM3-FM6,FM10-FM12)
│ ├── requirements.txt # requests, gitpython, pandas, tabulate
│ ├── README.md # How to run, relationship to IEEE paper
│ └── results/ # All test run outputs (committed for paper reproducibility)
│ ├── failure_modes_summary.md # IEEE paper data table
│ ├── failure_modes_raw.txt # Full test run output
│ ├── raw_results_mistral.txt # 9-justification Mistral test results
│ └── audit_trail/ # Gate audit files from each test run
├── .kiro/
│ ├── steering/ # Human-authored spec vault (governance.md, tech.md, etc.)
│ └── audit/last_sync.audit # Written by backend on every audit/gate event
├── screenshots/
│ ├── 01-dashboard.png # Sovereign Dashboard (normal state)
│ ├── 02-justification-gate-approved.png # Gate APPROVED with reasoning trace
│ └── README.md
├── dev.sh # One-command launcher for local dev
├── pyproject.toml # uv/hatch project definition
├── uv.lock # uv lock file
└── .env.example # Documents all environment variables
Single FastAPI app. No database required for DEMO_MODE.
In-memory state machine (_demo_state):
gate_status:CLEAR → TRIGGERED → PENDING → RESOLVED- Resets to
CLEARon backend restart (intentional — each demo session is fresh)
Demo scenario loop (_demo_scenarios):
- 23 pre-scripted
(drift_value, status_label)pairs cycling through: SOVEREIGN → MONITORING → CRITICAL_DRIFT (gate triggers) → GATE_PENDING → RESOLVING → SOVEREIGN - Advances on every
/driftpoll, pauses when gate is TRIGGERED or PENDING
Approval logic (DEMO_MODE):
- Justification > 20 chars → APPROVED, Intent Alignment Score 91/100
- Justification ≤ 20 chars → REJECTED, Intent Alignment Score 29/100
Production path (DEMO_MODE=false):
- Calls
amazon.nova-pro-v1:0via boto3 on AWS Bedrock, eu-central-1 - Falls back gracefully if credentials missing
- Production drift scorer:
_score_diff_locally_production()— linear mapping0.001 + raw * 0.013. Gate triggers at raw token divergence ≥ ~52%. Do NOT use_map_drift_score()for production diffs — that function is calibrated for the 23 pre-crafted demo sample diffs only.
WARDEN_LLM override (WARDEN_LLM env var):
When set, /gate/submit routes justification evaluation to a real LLM instead
of _bedrock_analyze(). Handled by _warden_llm_analyze(). Supported values:
gemini— usesGEMINI_API_KEY, modelgemini-1.5-flashvia google-generativeaihuggingface— usesHF_API_KEY, modelmeta-llama/Llama-3.1-8B-Instruct:autoviarouter.huggingface.co(requires HuggingFace Pro account)mistral— usesMISTRAL_API_KEY, modelmistral-small-latestviaapi.mistral.ai/v1/chat/completions(OpenAI-compatible)
All three backends use the same prompt template and return the same response shape.
If the LLM call fails with a network/auth error, the exception is caught and the
error is embedded in reasoning_trace — the endpoint still returns HTTP 200
REJECTED. This is a known Article 17 silent failure (see Known Issues).
API routes:
GET /drift — advance demo, return drift + gate state
GET /gate/status — current gate state + last decision
POST /gate/submit — submit justification, invoke Warden, append to audit file
POST /audit — write full ASCII audit report to .kiro/audit/last_sync.audit
GET /download-audit — serve audit file as download
GET /specs — return .kiro/steering/ file contents
Audit file path: .kiro/audit/last_sync.audit (relative to project root, resolved
from backend/../.kiro/audit/).
Fully client-side. output: 'export' in next.config.ts — compatible with
Cloudflare Pages static deployment. No getServerSideProps, no server actions,
no API routes.
Data flow:
page.tsxpollsGET /driftevery 3 seconds viauseEffect- Passes
driftData,currentStatus,gateStatus,currentDrift,demoModedown toDriftDashboard DriftDashboardauto-showsJustificationGatemodal whengateStatus === "TRIGGERED"JustificationGatePOSTs to/gate/submit, shows APPROVED/REJECTED result inlineGovernanceActionsPOSTs to/auditthen opens/download-auditin new tab
All components are "use client" — no hydration concerns.
API URL: process.env.NEXT_PUBLIC_API_URL (defaults to http://localhost:8000).
Written to frontend/.env.local by dev.sh. Must be set at build time for
static export since it's a NEXT_PUBLIC_ variable baked into the bundle.
| Target | How |
|---|---|
| Local dev | DEMO_MODE=true ./dev.sh |
| Backend on any PaaS | Procfile: web: uvicorn main:app --host 0.0.0.0 --port 8080 |
| Backend on AWS Lambda | Dockerfile (ECR Lambda Python 3.12 base), handler: main.handler via Mangum |
| Frontend on Cloudflare Pages | npm run build → deploy out/ directory |
Note: Mangum (from mangum import Mangum; handler = Mangum(app)) is imported in
production to wrap FastAPI for Lambda. It is listed in requirements.txt but the
handler export must be wired at the bottom of main.py if Lambda deployment is
activated.
These files are the human-authored architectural intent that the Warden cross-references:
| File | Purpose |
|---|---|
governance.md |
Warden persona, negotiation protocol |
tech.md |
Technology constraints — region, models, runtimes |
product.md |
Vision and strategic pillars |
human-intent-specs.md |
INTENT-001 through INTENT-006 declarations |
spec.json |
Machine-readable thresholds and model config |
structure.md |
Repository structure intent |
boilerplate-standards.md |
Code standards |
The backend loads these via _load_spec_intent() and they are available at GET /specs.
# DEMO_MODE — no AWS needed
DEMO_MODE=true ./dev.sh
# Production — requires AWS credentials in .env
cp .env.example .env
# fill in AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION=eu-central-1
DEMO_MODE=false ./dev.shdev.sh does:
- Creates
venv/, installsbackend/requirements.txt - Starts uvicorn on port 8000, logs to
backend.log - Writes
frontend/.env.local - Installs
node_modulesif needed, starts Next.js on port 3000, logs tofrontend.log
To reset the demo gate cycle: restart only the backend process. The frontend does not need to restart.
kill $(lsof -ti:8000)
source venv/bin/activate && DEMO_MODE=true python -m uvicorn backend.main:app \
--host 0.0.0.0 --port 8000 --reload &To take a screenshot of the Justification Gate Approved state:
A Playwright script exists at /Users/apple/tmp_playwright/gate_screenshot.js.
It requires the backend to be freshly started (gate in CLEAR state) and the
frontend on port 3000. Run node gate_screenshot.js — it waits for drift to
rise (~30s), submits a justification, and saves the screenshot.
- Moved screenshots from
docs/screenshots/toscreenshots/(root) - Deleted
docs/folder entirely - Deleted
drif.webpplaceholder image - Captured
02-justification-gate-approved.pnglive from the running app using Playwright (chromiumvia Google Chrome headless), not a static mock
- Added Live Demo section near the top
- Renamed "Main Dashboard" → "Sovereign Dashboard"
- Added "Justification Gate Approved" section with live screenshot
- Added "Sample Audit Trail Output" section with real ASCII audit content
- Removed redundant
## Screenshotssection (screenshots are inline) - Removed
## Aboutsection (duplicated by GitHub sidebar) - Removed
Mode: DEMO_MODE (simulated)from the audit code block in README; added a one-line prose note below instead
- Changed from a restrictive evaluation-only license to AGPL-3.0
- Added commercial licensing contact line under the License section in README
- Added
output: "export"tofrontend/next.config.tsfor Cloudflare Pages - No code refactoring needed — the entire frontend was already client-side
- Added
*.bakto.gitignore - Removed
frontend/README.md(boilerplate, superseded by root README) - Removed
frontend/src/app/page.tsx.bak(committed backup — should never have been tracked) - Deleted local untracked junk:
recovery_temp/, rootsteering/duplicate,backend/main.py.bak,backend/test_results.log, and all Python packages that had been installed directly intobackend/(Lambda build artifacts frompip install -r requirements.txt -t backend/run in the wrong directory) - Created
backend/Procfilefor PaaS deployment
During a git pull --rebase, the remote had independently moved screenshot paths
and updated the license. Conflict in README.md was resolved by keeping the
"Sovereign Dashboard" rename over the remote's "Main Dashboard" label.
Added WARDEN_LLM env var that routes /gate/submit to a real LLM. Implemented
_warden_llm_analyze() in main.py with three backends: gemini, huggingface,
mistral. The DEMO_MODE path and _bedrock_analyze() are completely untouched —
WARDEN_LLM only activates when the env var is explicitly set.
HuggingFace notes: api-inference.huggingface.co is DNS-blocked in some sandboxed
environments. Use router.huggingface.co instead. The router requires a Pro
account — free-tier keys return {"error": "Invalid username or password."}.
Model name needs the :auto suffix: meta-llama/Llama-3.1-8B-Instruct:auto.
Mistral works reliably from all environments tested: api.mistral.ai,
mistral-small-latest, OpenAI-compatible response format.
The original production path used _map_drift_score() which is calibrated for
demo sample diffs. For real git diffs, tokens like ledger, immutable, and
verification appear in both the spec vault (357 tokens) and diff output, giving
raw divergence of ~0.70 which maps to 0.0072 — just below the 0.0075 threshold.
Fix: _score_diff_locally_production() with linear formula 0.001 + raw * 0.013.
Gate triggers when raw ≥ ~0.52. Committed in 7a92016.
Created test_research/ with three components:
drift_calculator.py— standalone drift scorer using gitpython, no backend neededrun_tests.py— 3-phase test runner (drift across 5 commits, 9 justification quality levels WEAK/MEDIUM/STRONG, audit trail generation)run_failure_modes.py— 8 EU AI Act failure mode tests
Ran all 9 justification tests with Mistral. Results: WEAK justifications scored
0–5/100 (REJECTED), MEDIUM scored 10/100 (REJECTED), STRONG scored 91–95/100
(APPROVED). Results saved in test_research/results/raw_results_mistral.txt.
Ran 8 failure mode tests against Articles 12, 13, 14, 17 with Mistral key. Results: 6 CONFIRMED, 1 MITIGATED (FM10), 1 PARTIAL (FM12).
Key findings:
- FM3 CONFIRMED — 10-line spec vault injection reduced drift 61% (0.0113→0.0044), dropping below the 0.0075 threshold. Detection for entire vocabulary domain silenced.
- FM5 CONFIRMED — commit scoring 0.0064 (85% of threshold) produces zero audit entry
- FM6 CONFIRMED — 3-commit sequence, zero commit SHAs in audit trail
- FM11 CONFIRMED — invalid Mistral key returns HTTP 200 REJECTED with
{"detail":"Unauthorized"}buried inreasoning_trace. Two silent failure paths: (1) invalid LLM key caught byexcept Exceptionatmain.py:545, (2) boto3 unavailable also returns HTTP 200 REJECTED silently. - FM1 CONFIRMED — score 40/100 REJECTED; gate evaluated text quality only, no identity/role check.
- FM10 MITIGATED — Mistral caught the MD5/bcrypt factual error (score 20/100). But this is a conditional mitigation: the gate prompt asks for "justification adequacy" not "technical accuracy". Mitigation depends on LLM training coverage.
- FM4 CONFIRMED — drift score identical before (0.0126) and after (0.0126) two gate submissions. Spec vault never updated by gate decisions.
All test commits were reverted after each test. Results committed in b69474a.
Note: FM1 score was 5/100 with mistral-small-2412 and 40/100 with mistral-small-2506.
FM10 score was 10/100 with 2412 and 20/100 with 2506. Verdicts (CONFIRMED / MITIGATED)
are identical across both models. mistral-small-2412 was deprecated 2026-06; the
pinned model was updated to mistral-small-2506 in commit b467c89.
Removed results/ from test_research/.gitignore (commented it out with note)
so test results can be committed for IEEE paper reproducibility. The gitignore
still blocks *.env, secrets.py, keys.py, and *_key.txt.
Added WARDEN_LLM and three API key rows to the Environment Variables table.
Added ## Research section documenting test_research/, both test runners,
and the FM3 headline stat. Committed in 2790126.
Pinned the Mistral model from the floating "mistral-small-latest" tag to the
specific versioned alias "mistral-small-2506" and added "temperature": 0 to
the payload in _warden_llm_analyze(). Both changes are in main.py lines 509–551.
Why this matters: mistral-small-latest silently changes when Mistral releases a
new version. temperature=0 removes sampling randomness so the same model produces
identical outputs across runs. Together these make 7 of the 8 failure mode findings
fully deterministic for anyone reproducing the IEEE paper results.
FM10 (MITIGATED) remains conditionally reproducible — it holds for mistral-small-2506
specifically because that model's training data covers the MD5/bcrypt vulnerability.
A different model version may not catch it. The README documents this caveat.
Committed in 81c2af2. test_research/README.md updated to reflect pinned model
and to replace the "±5–10 points variation" note with the determinism guarantee.
Ran both test suites (run_failure_modes.py and run_tests.py) from a completely
fresh git clone following the README instructions verbatim, as an external
reviewer would. Found and fixed the following gaps:
Gap 1 — mistral-small-2412 was deprecated. The Mistral API now returns
HTTP 400 "Invalid model: mistral-small-2412" for that alias. Updated the pinned
model to mistral-small-2506 (confirmed available via Mistral models API) across
backend/main.py, test_research/README.md, failure_modes_summary.md, and
CLAUDE.md. Committed in b467c89.
Gap 2 — FM1 and FM10 scores changed with the new model. Verdicts unchanged,
scores shifted: FM1 5/100 → 40/100, FM10 10/100 → 20/100. Paper table updated
in failure_modes_summary.md. Committed in 414b1f3.
Gap 3 — git user config not listed as prerequisite. The failure mode tests
make real git commits. Without git config user.name and git config user.email,
commits fail silently with no error message, producing wrong or zero drift scores.
Added to test_research/README.md prerequisites. Committed in 5237c39.
Gap 4 — stray temp_drift_test.py file. Deleted leftover FM3 vocabulary
artifact from test_research/. Committed in 5237c39.
run_tests.py Phase 2 was broken: after the first justification submission
(REJECTED), the gate went to RESOLVED and never re-triggered for W2–S3. Root
cause: the backend's gate trigger condition was gate == "CLEAR" only, and the
test script polled /gate/status (read-only) rather than /drift (which advances
the state machine).
Two fixes committed together in 9da1e67:
-
backend/main.py— in_compute_drift(), added production-mode transition: whengate == "RESOLVED"and the new drift score drops at or below threshold, resetgate_statusto"CLEAR"so the gate can re-arm for future high-drift commits. DEMO_MODE behaviour is explicitly unchanged (not DEMO_MODEguard). -
test_research/run_tests.py— Phase 2 inner loop now polls/drifttwice before making the HIGH_DRIFT commit (lets RESOLVED → CLEAR fire), then polls/driftthree more times after the commit (lets CLEAR → TRIGGERED fire), before checking/gate/status. Also addedsys.stdin.isatty()guard so the Phase 2 interactive prompt auto-skips in non-interactive/scripted runs.
All 9 justifications now score correctly. Key findings vs. prior run (2412):
| ID | Justification | Score (2412) | Score (2506) | Decision |
|---|---|---|---|---|
| W1 | "ok" | 5/100 | 20/100 | REJECTED |
| W2 | "approved" | 0/100 | 85/100 | APPROVED |
| W3 | "I updated the code" | 0/100 | 30/100 | REJECTED |
| M1–M3 | Vague sentences | 10/100 | 30/100 | REJECTED |
| S1–S3 | Specific, traceable | 91–95/100 | 85/100 | APPROVED |
W2 is a new finding: mistral-small-2506 interprets the single word
"approved" as a compliance signal, scoring it identically to a STRONG
justification (85/100 APPROVED). mistral-small-2412 correctly rejected it
(0/100 REJECTED). This is a social engineering vulnerability — an
approval-signalling keyword alone can bypass the justification quality gate.
Behaviour is fully deterministic (temperature=0). Documented in
raw_results_mistral.txt and test_research/README.md.
Completed the definitive fresh-clone test: git clone → pip install → backend start
→ run_failure_modes.py → run_tests.py, following README verbatim. Both test suites
pass end-to-end with mistral-small-2506 (temperature=0).
New tests added to run_failure_modes.py for paper findings:
Finding 3 — New File Governance Blindspot (fm_new_file_governance_blindspot()):
- Commits a new file (blockchain/DeFi vocabulary) and computes the diff both
with and without
--diff-filter=M - With filter: 0 tokens visible → score ~0.001 (baseline, gate not triggered)
- Without filter (current): 48 tokens visible → score 0.0132 (gate triggered)
- Proves the historical vulnerability and the current fix empirically
- Committed in
830a431
Gap 7 — Warden Engine Unavailability (gap7_warden_engine_unavailability()):
- Kills backend process, makes HIGH_DRIFT commit, confirms no governance record
- Restarts backend for subsequent tests
- Finding: CONFIRMED — governance failure is itself unlogged (Article 9(2c))
- Committed in
830a431
FM10 / Gap 10 — Updated justification with professional formatting:
- Original justification: simple "MD5 is stronger than bcrypt" claim
- New Gap 10 justification: adds ticket SEC-444, named reviewers (Security team + Architecture board), approval dates, specific performance metric (60% overhead reduction), deployment context (AWS Lambda eu-central-1)
mistral-small-2506still detects the cryptographic error: score 30/100 REJECTED- Professional formatting raised score 20→30 but did not change the MITIGATED verdict
- Committed in
830a431
Finding 1 / Gap 11 — Silent failure across all 9 quality levels (fm_silent_nine_justifications()):
- Isolated backend on port 8002 with
MISTRAL_API_KEY=INVALID_KEY_SILENT_NINE_FM_TEST - Submits all 9 justifications (WEAK/MEDIUM/STRONG) with LLM credentials invalid
- Each iteration: LOW_DRIFT pre-commit (spec-aligned vocab) → RESOLVED→CLEAR fires → HIGH_DRIFT commit → CLEAR→TRIGGERED → submit → score 0 REJECTED → revert
- Result: 9/9 score 0/100 REJECTED, 9 unique verification hashes, audit trail indistinguishable from 9 legitimate low-quality rejections
- Committed in
830a431, fix for RESOLVED→CLEAR in8397c35
Root cause of RESOLVED→CLEAR fix (commit 8397c35):
The Finding 1 test failed initially because the baseline commit (commit 830a431 itself,
which modified run_failure_modes.py) scored above threshold — the test file content
contains blockchain vocabulary strings. After W1's submission (gate→RESOLVED), the
hard-reset to baseline_sha left the backend reading the baseline commit's diff
(>threshold), preventing RESOLVED→CLEAR. Fix: add a LOW_DRIFT pre-commit (spec-aligned
vocabulary: Warden, Bedrock, DynamoDB, Uvicorn) before each HIGH_DRIFT commit. The
spec-aligned diff reliably drops below threshold, firing RESOLVED→CLEAR.
Finding 2 — Drift bifurcation already produced by run_tests.py Phase 1: Phase 1 output from fresh clone run:
[LOW_DRIFT] local=0.500000 backend=0.0026 agree=NO — delta: 0.4974
[HIGH_DRIFT] local=0.789474 backend=0.0103 agree=NO — delta: 0.7792
[SPEC_VIOLATION] local=0.842105 backend=0.0140 agree=NO — delta: 0.8281
[NEUTRAL] local=0.833333 backend=0.0012 agree=NO — delta: 0.8321
0/4 agreement. Local scorer (token-overlap, 0.0–1.0 scale) and backend scorer
(production linear formula, 0.001–0.014 scale) are systematically different. This
is Finding 2 for the paper. Output in results/drift_results_mistral.md.
FM1 score note: FM1 scored 40–45/100 across runs with the same temperature=0 key. Variance arises from gate context (prior submissions in the test session shift Mistral's calibration). The verdict (CONFIRMED) and structural finding (no identity check) are invariant. Both values are documented; paper uses 40/100 as the lower bound.
Added Finding 2 (drift scoring bifurcation) as an explicit row in
test_research/results/failure_modes_summary.md. It was documented in prose in the
fresh-clone section above but was absent from the summary table — external reviewers
using the table as a reference would have missed it.
Also fixed FM10/Gap 10 score in test_research/README.md expected-results table from
~20/100 to ~30/100 to match the SEC-444 sophisticated justification run, and added a
Finding 2 expected-results block to the README (4-commit bifurcation table).
Final paper table state (12 findings, 9 CONFIRMED / 1 MITIGATED / 1 PARTIAL):
| ID | Title | Verdict | Article |
|---|---|---|---|
| FM1 | Authorisation Mismatch | CONFIRMED | Art. 14(4) |
| FM3 | Specification Gaming | CONFIRMED | Art. 13(3b) |
| FM4 | Vocabulary Expansion Desensitisation | CONFIRMED | Art. 14(1) |
| FM5 | Near-Miss Logging Gap | CONFIRMED | Art. 12(1) |
| FM6 | Rollback Target Ambiguity | CONFIRMED | Art. 14(4) |
| FM10/Gap10 | Competence Verification Gap | MITIGATED | Art. 14(4) |
| FM11 | Article 17 Silent Failure (single) | CONFIRMED | Art. 17(1g) |
| FM12/Gap12 | Article 50 Disclosure Gap | PARTIAL | Art. 50(1) |
| Finding 2 | Drift Scoring Bifurcation | CONFIRMED | Art. 9(2c) |
| Finding 3 | New File Governance Blindspot | CONFIRMED | Art. 9(2c) |
| Gap 7 | Warden Engine Unavailability | CONFIRMED | Art. 9(2c) |
| Finding 1/Gap11 | QMS Silent Failure — All 9 Justifications | CONFIRMED | Art. 17(1g) |
Created manuscript_draft.md in the repo root. Full IEEE-format manuscript for
Professor Eldh covering all 12 findings across EU AI Act Articles 9, 12, 13, 14, 17,
and 50.
File: manuscript_draft.md — removed from public repo at commit 45a2300.
Full draft recoverable from git history at commit 62fd746.
Manuscript ID: AEVOXIS-WE-MS-2026-001
Watermark: Visible copyright header (HTML comment + rendered text) + footer.
Unauthorized reproduction notice included. Do not remove without author instruction.
Structure:
- Abstract (~300 words), Index Terms
- I. Introduction — gap, 4 numbered contributions
- II. Background — EU AI Act articles, spec drift, HITL, LLM-as-judge, reproducibility
- III. System Under Test — architecture, spec vault, drift formula, gate state machine
- IV. Methodology — 4 RQs (RQ1–RQ4), Mistral selection rationale, reproducibility measures
- V. Results — all 12 findings, Tables I–IV with all empirical numbers
- VI. Discussion — 3 systemic patterns, W2 keyword bypass, multi-LLM comparison future work
- VII. Threats to Validity — internal, external, construct, conclusion
- VIII. Conclusion
- Acknowledgements — AI drafting disclosure (research is original; Claude assisted drafting)
- References — 15 IEEE-format numbered citations [1]–[15]
Mistral rationale (as written in §IV-C): European provenance (French company, aligns
with EU data sovereignty), mid-size model realistic for production compliance systems,
versioned alias mistral-small-2506 (not floating latest), temperature=0 for
determinism, OpenAI-compatible REST (no proprietary SDK), accessible free trial tier.
Multi-LLM comparison noted in §VI-D as planned future work.
AI disclosure in Acknowledgements: manuscript drafting by Claude (Anthropic); all research design, test execution, data, findings, interpretations, and conclusions are the original work of Vinita Silaparasetty.
Key citation: Reference [13] (Wang & Yu, EUROCRYPT 2005, MD5 collision breaks) is cited to support the FM10/Gap 10 finding on the MD5/bcrypt error.
Do not commit API keys to this file. The manuscript references the Mistral API but contains no credentials.
Pronoun convention (commit 62fd746): The manuscript uses first-person singular
throughout ("I designed", "I measured", "I confirmed", etc.). IEEE sole-author
convention — do not revert to "we/our". Passive voice is used where it reads more
naturally for methodology prose ("All findings are reported at...", "The nine
verification hashes..."). No "we" or "our" remains in the body text; the string
"WE" appears only in the watermark ID (AEVOXIS-WE-MS-2026-001), which is correct.
Author line format: "Vinita Silaparasetty, AI Governance Engineer, Aevoxis Solutions, Germany" — title is part of the author block, not a separate affiliation line.
-
Gate state is in-memory only. Restarting the backend resets
gate_statustoCLEAR. This is fine for demo but means the gate cycle always restarts from scratch. If you need persistence across restarts, wire_demo_stateto a file or DynamoDB. -
NEXT_PUBLIC_API_URLis baked at build time. For Cloudflare Pages, this env var must be set in the Cloudflare Pages build settings beforenpm run buildruns. The static export cannot read runtime environment variables. -
Mangum handler not currently exported.
main.pyimports Mangum inrequirements.txtbut thehandler = Mangum(app)line for Lambda is not in the currentmain.py. Add it at the bottom if deploying to Lambda. -
backend.logandfrontend.logare written to the project root bydev.sh. They are gitignored (*.log) but will appear ingit statusif you generate them. This is expected. -
frontend/.env.localis auto-generated bydev.shand gitignored byfrontend/.gitignore. Never commit it. -
uv.lockandpyproject.tomlare tracked. They define the project for theuvpackage manager.dev.shuses plainvenv/pipinstead. Both can coexist. -
FM11 silent failure is not fixed. When
WARDEN_LLMis set and the LLM returns a 4xx error,_warden_llm_analyze()catches it inexcept Exception(line 545 ofmain.py) and returns a REJECTED response with the error buried inreasoning_trace./gate/submitcannot distinguish this from a legitimate REJECTED and returns HTTP 200. The frontend seesdecision: REJECTEDin both cases. Fix: checkresp.status_codebeforeraise_for_status()and return{"http_error": f"Mistral API {resp.status_code}"}for 4xx — this triggers the HTTP 500 path that already exists ingate_submit(). -
FM3 spec vault has no tamper-evidence. Anyone with write access to
.kiro/steering/can add vocabulary to suppress drift detection for any code domain. No integrity check or access control exists on the spec vault files. -
Near-miss events (FM5) leave no audit trail. The audit file is only written by
/gate/submitandPOST /audit. Commits that score below the threshold produce no log entry, making escalating near-miss patterns invisible retrospectively. -
Gate has no authentication (FM1).
/gate/submitaccepts requests from any caller with network access. There is no middleware to verify the submitter's identity, role, or authority. Anyone who writes a spec-aligned justification will receive the same APPROVED decision as a senior architect. -
Rollback target not in audit (FM6). The audit file contains drift value, justification, decision, model, and timestamp — but no commit SHA, branch, or file list. When multiple commits trigger the gate cumulatively, operators cannot identify which commit to roll back without manual
git bisect.
| Variable | Default | Notes |
|---|---|---|
DEMO_MODE |
true |
false requires AWS credentials |
DRIFT_THRESHOLD |
0.0075 |
Gate triggers above this value |
NEXT_PUBLIC_API_URL |
http://localhost:8000 |
Baked at build time |
AWS_ACCESS_KEY_ID |
— | Production only |
AWS_SECRET_ACCESS_KEY |
— | Production only |
AWS_REGION |
eu-central-1 |
Must be Frankfurt for data sovereignty |
WARDEN_LLM |
(unset) | Route gate evaluation to real LLM: gemini, huggingface, mistral |
GEMINI_API_KEY |
— | Required when WARDEN_LLM=gemini |
HF_API_KEY |
— | Required when WARDEN_LLM=huggingface (Pro account needed) |
MISTRAL_API_KEY |
— | Required when WARDEN_LLM=mistral |
See .env.example for the full list.