Skip to content

Commit fc273f1

Browse files
- Added rolling compatibility with the latest AIDEFEND framework, including schema 2.3, canonical guidance IDs, scope boundaries, source-available tools, and dynamic taxonomy changes.
- Upgraded the LanceDB index contract to schema 3.2 with exact source provenance, content digests, manifest verification, and atomic database replacement. - Hardened synchronization to fail closed and preserve or restore the last-known-good database when parsing, validation, indexing, or metadata commits fail. - Updated all 18 MCP tools and 18 REST routes for current framework hierarchy, mappings, threat resolution, planning, statistics, and incident-response outputs. - Bundled the JavaScript parser and pinned Acorn runtime for wheel, sdist, and Docker deployments without requiring npm at runtime. - Improved installation paths, Docker configuration, authentication, CORS, query validation, and cross-platform packaging. - Expanded CI with rolling upstream checks, complete transport smoke tests, negative-path coverage, clean-wheel validation across Python 3.10–3.13, and container runtime checks.
1 parent 6681f51 commit fc273f1

106 files changed

Lines changed: 23560 additions & 2903 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ env/
3535
.gitattributes
3636

3737
# Environment
38-
.env
39-
.env.local
38+
.env*
4039

4140
# Documentation (not needed in container)
4241
README.md
@@ -47,7 +46,23 @@ docs/
4746
tests/
4847
.pytest_cache/
4948
.coverage
49+
coverage.xml
5050
htmlcov/
51+
.tox/
52+
.hypothesis/
53+
test-artifacts/
54+
.release-check-*/
55+
.wheelcheck/
56+
.tmp_*/
57+
58+
# Local dependency and agent/tool state. None of these are runtime inputs, and
59+
# sending them to a remote builder can leak local artifacts or add hundreds of
60+
# megabytes to the build context.
61+
node_modules/
62+
.claude/
63+
.agents/
64+
.codex/
65+
*.whl
5166

5267
# Development dependencies
5368
requirements-dev.txt

.env.example

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ API_PORT=8000
2727
API_WORKERS=1
2828

2929
# Security
30-
# MAX_QUERY_LENGTH set to 1500 to align with bge-small-en-v1.5 model's 512 token limit
31-
# (1500 chars ≈ 375 tokens with 4:1 ratio, leaving buffer for system overhead)
30+
# MAX_QUERY_LENGTH set to 1500 to align with the multilingual-e5-base model's 512 token limit
31+
# (1500 chars ≈ 375 tokens at a 4:1 English char:token ratio, leaving buffer for overhead).
32+
# NOTE: for CJK (Chinese/Japanese/Korean) input the ratio is closer to 1-2 tokens per char,
33+
# so a 1500-char CJK query can exceed 512 tokens and be truncated by the tokenizer; lower
34+
# this value if you query primarily in CJK languages.
3235
MAX_QUERY_LENGTH=1500
3336
MAX_TOP_K=20
3437
DEFAULT_TOP_K=5
@@ -88,6 +91,9 @@ LOG_LEVEL=INFO
8891
ENABLE_FILE_LOGGING=true
8992

9093
# Advanced: Custom paths (leave as default unless needed)
94+
# Source checkouts default to ./data. Installed wheels default to the OS
95+
# per-user application-data directory; Docker fixes DATA_PATH at /app/data.
96+
# Relative wheel overrides remain under that writable per-user directory.
9197
# DATA_PATH=./data
9298
# DB_PATH=./data/aidefend_kb.lancedb
9399
# RAW_PATH=./data/raw_content
@@ -96,7 +102,7 @@ ENABLE_FILE_LOGGING=true
96102

97103
# Advanced: Embedding model (uses FastEmbed with ONNX Runtime)
98104
# Default: Xenova/multilingual-e5-base (Microsoft, multilingual, 768-dim, 512 tokens, 100+ languages)
99-
# - Model size: ~265 MB (int8 quantized), supports cross-language semantic search
105+
# - Model size: ~280 MB (int8 quantized), supports cross-language semantic search
100106
# - Allows users to query in any language (e.g., Chinese, Japanese) and match English content
101107
# Alternative options:
102108
# - intfloat/multilingual-e5-small: Smaller (113 MB), 384-dim, good performance

.github/workflows/ci.yml

Lines changed: 196 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@ name: CI
33
on:
44
push:
55
pull_request:
6+
workflow_dispatch:
7+
inputs:
8+
framework_ref:
9+
description: AIDEFEND framework branch, tag, or commit to stage
10+
required: false
11+
default: main
12+
type: string
13+
run_current_snapshot:
14+
description: Also run the exact 2026-07-24 snapshot assertions
15+
required: false
16+
default: false
17+
type: boolean
18+
repository_dispatch:
19+
types: [aidefend-framework-release]
20+
schedule:
21+
# Exercise the rolling upstream contract every day. Exact snapshot tests
22+
# remain an explicit manual gate and never pin this scheduled check.
23+
- cron: "17 9 * * *"
624

725
permissions:
826
contents: read
@@ -11,37 +29,201 @@ jobs:
1129
test:
1230
name: Test Suite
1331
runs-on: ubuntu-latest
14-
timeout-minutes: 30
32+
timeout-minutes: 60
33+
env:
34+
GITHUB_BRANCH: ${{ github.event.client_payload.framework_ref || inputs.framework_ref || 'main' }}
1535

1636
steps:
1737
- name: Checkout code
18-
uses: actions/checkout@v4
38+
uses: actions/checkout@v5
1939

2040
- name: Set up Python
21-
uses: actions/setup-python@v5
41+
uses: actions/setup-python@v6
2242
with:
2343
python-version: "3.11"
2444
cache: "pip"
2545

2646
- name: Set up Node.js
27-
uses: actions/setup-node@v4
47+
uses: actions/setup-node@v5
2848
with:
29-
node-version: "20"
30-
cache: "npm"
49+
node-version: "22.7.0"
3150

3251
- name: Install Python dependencies
3352
run: |
3453
python -m pip install --upgrade pip
3554
python -m pip install -r requirements-dev.txt
3655
37-
- name: Install Node dependencies
38-
run: npm ci
39-
4056
- name: Build local knowledge base
4157
run: python __main__.py --resync
4258

43-
- name: Run pytest
44-
run: python -m pytest -q
59+
- name: Verify exact framework-to-index manifest
60+
run: python scripts/verify_index_manifest.py
61+
62+
- name: Run rolling compatibility pytest without silent skips
63+
run: |
64+
mkdir -p test-artifacts
65+
python -m pytest -q -m "not current_snapshot" --junitxml=test-artifacts/pytest.xml
66+
python - <<'PY'
67+
import xml.etree.ElementTree as ET
68+
from pathlib import Path
69+
70+
report = Path("test-artifacts/pytest.xml")
71+
root = ET.parse(report).getroot()
72+
skipped = sum(
73+
int(suite.attrib.get("skipped", 0))
74+
for suite in root.iter("testsuite")
75+
)
76+
if skipped:
77+
raise SystemExit(f"Release test suite skipped {skipped} test(s)")
78+
PY
79+
80+
- name: Run exact 2026-07-24 framework snapshot gate
81+
if: ${{ github.event_name == 'workflow_dispatch' && inputs.run_current_snapshot }}
82+
run: >-
83+
python -m pytest -q -m current_snapshot
84+
tests/test_current_framework_contract.py
85+
86+
- name: Exercise every MCP and REST tool
87+
run: python scripts/smoke_all_tools.py --data-path data --transport both --timeout 180
88+
89+
- name: Build and smoke-test clean release artifacts
90+
run: |
91+
python -m pip install build
92+
python scripts/build_release_artifacts.py --outdir dist
93+
python scripts/verify_distribution_inventory.py dist
94+
python -m pip install --force-reinstall --no-deps dist/*.whl
95+
mkdir -p /tmp/aidefend-wheel-smoke/scripts
96+
cp scripts/verify_index_manifest.py /tmp/aidefend-wheel-smoke/scripts/verify_index_manifest.py
97+
cp scripts/smoke_all_tools.py /tmp/aidefend-wheel-smoke/scripts/smoke_all_tools.py
98+
export PYTHONPATH=""
99+
export DATA_PATH="$GITHUB_WORKSPACE/data"
100+
cd /tmp/aidefend-wheel-smoke
101+
python - <<'PY'
102+
import json
103+
import os
104+
from pathlib import Path
105+
import app
106+
import app.tools
107+
import mcp_server
108+
from app.utils import NODE_PARSER_SCRIPT, parse_js_file_with_node
109+
110+
assert NODE_PARSER_SCRIPT.exists(), NODE_PARSER_SCRIPT
111+
workspace = Path(os.environ["GITHUB_WORKSPACE"]).resolve()
112+
assert not Path(app.__file__).resolve().is_relative_to(workspace)
113+
assert not Path(app.tools.__file__).resolve().is_relative_to(workspace)
114+
assert not Path(mcp_server.__file__).resolve().is_relative_to(workspace)
115+
data_path = workspace / "data"
116+
version = json.loads((data_path / "local_version.json").read_text(encoding="utf-8"))
117+
source_files = version.get("source_files")
118+
assert isinstance(source_files, list) and source_files
119+
tactic_files = [name for name in source_files if name != "aidefend-intro.js"]
120+
assert tactic_files
121+
assert all(
122+
isinstance(name, str)
123+
and Path(name).name == name
124+
and name.endswith(".js")
125+
for name in tactic_files
126+
)
127+
parsed = parse_js_file_with_node(data_path / "raw_content" / tactic_files[0])
128+
assert isinstance(parsed.get("name"), str) and parsed["name"].strip()
129+
assert isinstance(parsed.get("techniques"), list) and parsed["techniques"]
130+
PY
131+
python scripts/verify_index_manifest.py
132+
python scripts/smoke_all_tools.py \
133+
--data-path "$DATA_PATH" \
134+
--transport both \
135+
--timeout 180
136+
137+
clean-install:
138+
name: Clean wheel - ${{ matrix.os }} / Python ${{ matrix.python }}
139+
runs-on: ${{ matrix.os }}
140+
timeout-minutes: 35
141+
strategy:
142+
fail-fast: false
143+
matrix:
144+
os: [ubuntu-latest, windows-latest, macos-latest]
145+
python: ["3.10", "3.11", "3.12", "3.13"]
146+
env:
147+
PYTHONPATH: ""
148+
149+
steps:
150+
- name: Checkout code
151+
uses: actions/checkout@v5
152+
153+
- name: Set up Python
154+
uses: actions/setup-python@v6
155+
with:
156+
python-version: ${{ matrix.python }}
157+
158+
- name: Set up Node.js
159+
uses: actions/setup-node@v5
160+
with:
161+
node-version: "22.7.0"
162+
163+
- name: Build wheel from a clean sdist extraction
164+
run: |
165+
python -m pip install --upgrade pip build
166+
python scripts/build_release_artifacts.py --outdir dist
167+
python scripts/verify_distribution_inventory.py dist
168+
169+
- name: Install wheel with all declared dependencies
170+
run: >-
171+
python -c "from pathlib import Path; import subprocess, sys;
172+
wheels=list(Path('dist').glob('*.whl'));
173+
assert len(wheels) == 1, wheels;
174+
subprocess.check_call([sys.executable, '-m', 'pip', 'install',
175+
'--force-reinstall', str(wheels[0])])"
176+
177+
- name: Verify dependency and installed-wheel contracts outside the repo
178+
working-directory: ${{ runner.temp }}
179+
run: |
180+
python -m pip check
181+
python "${{ github.workspace }}/scripts/verify_clean_install.py"
182+
183+
container:
184+
name: Container Contract
185+
runs-on: ubuntu-latest
186+
timeout-minutes: 30
187+
env:
188+
AIDEFEND_API_KEY: ci-container-contract-only
189+
190+
steps:
191+
- name: Checkout code
192+
uses: actions/checkout@v5
193+
194+
- name: Validate Dockerfile and Compose
195+
run: |
196+
docker compose config --quiet
197+
LOCAL_FRAMEWORK_PATH=/native/host/framework docker compose config | grep -q 'LOCAL_FRAMEWORK_PATH:'
198+
if LOCAL_FRAMEWORK_PATH=/native/host/framework docker compose config | grep -q /native/host/framework; then
199+
echo Native LOCAL_FRAMEWORK_PATH leaked into the Linux container configuration
200+
exit 1
201+
fi
202+
docker build --check .
203+
204+
- name: Build image
205+
run: docker build --tag aidefend-mcp:ci .
206+
207+
- name: Smoke-test the final runtime
208+
run: |
209+
test $(docker run --rm --entrypoint id aidefend-mcp:ci -u) -ne 0
210+
docker run --rm \
211+
--env AUTH_MODE=api_key \
212+
--env AIDEFEND_API_KEY=$AIDEFEND_API_KEY \
213+
--entrypoint python \
214+
aidefend-mcp:ci -c 'import app.main, mcp_server'
215+
docker run --rm \
216+
--mount type=bind,source=${{ github.workspace }}/tests/fixtures,target=/fixtures,readonly \
217+
--entrypoint node \
218+
aidefend-mcp:ci parse_js_module.mjs /fixtures/test_example.js \
219+
| grep -q 'Test Tactic'
220+
221+
- name: Verify persisted data volume is writable by the service user
222+
run: |
223+
docker run --rm \
224+
--mount type=volume,target=/app/data \
225+
--entrypoint touch \
226+
aidefend-mcp:ci /app/data/container-write-check
45227
46228
bandit:
47229
name: Bandit
@@ -50,10 +232,10 @@ jobs:
50232

51233
steps:
52234
- name: Checkout code
53-
uses: actions/checkout@v4
235+
uses: actions/checkout@v5
54236

55237
- name: Set up Python
56-
uses: actions/setup-python@v5
238+
uses: actions/setup-python@v6
57239
with:
58240
python-version: "3.11"
59241
cache: "pip"
@@ -64,4 +246,4 @@ jobs:
64246
python -m pip install bandit[toml]
65247
66248
- name: Run Bandit
67-
run: python -m bandit -q -r app
249+
run: python -m bandit -q -r app mcp_server.py __main__.py

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,15 @@ temp/
8989
tmp/
9090
.release-check-*/
9191
.wheelcheck/
92+
test-artifacts/
93+
.tmp_pytest*/
94+
.pytest-*/
95+
.tmp_*/
9296

9397
# VSCode Claude extension cache
9498
.claude/
99+
.agents/
100+
.codex/
95101

96102
# Claude temporary output files
97103
CLAUDE*.md

0 commit comments

Comments
 (0)