eduevidence 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +506 -0
- package/README.zh-CN.md +434 -0
- package/SKILL.md +195 -0
- package/bin/eduevidence.js +127 -0
- package/domains/education/manifest.json +183 -0
- package/domains/education/outcome_taxonomy.json +127 -0
- package/domains/manifest.json +26 -0
- package/domains/policy/frame.schema.json +234 -0
- package/domains/policy/manifest.json +10 -0
- package/domains/policy/methodology_checklist.json +109 -0
- package/domains/policy/outcome_taxonomy.json +53 -0
- package/domains/policy/references/causal-identification.md +45 -0
- package/domains/policy/references/cost-evidence.md +44 -0
- package/domains/policy/references/equity.md +42 -0
- package/domains/policy/references/evidence-hierarchy.md +41 -0
- package/domains/policy/references/implementation-evidence.md +47 -0
- package/eduevidence_cli.py +26 -0
- package/engine/__init__.py +11 -0
- package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
- package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/bias.cpython-312.pyc +0 -0
- package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
- package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
- package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
- package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
- package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
- package/engine/__pycache__/events.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
- package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
- package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
- package/engine/__pycache__/ids.cpython-312.pyc +0 -0
- package/engine/__pycache__/library.cpython-312.pyc +0 -0
- package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
- package/engine/__pycache__/living.cpython-312.pyc +0 -0
- package/engine/__pycache__/log.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/migration.cpython-312.pyc +0 -0
- package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
- package/engine/__pycache__/paths.cpython-312.pyc +0 -0
- package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
- package/engine/__pycache__/planner.cpython-312.pyc +0 -0
- package/engine/__pycache__/project.cpython-312.pyc +0 -0
- package/engine/__pycache__/projections.cpython-312.pyc +0 -0
- package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
- package/engine/__pycache__/run.cpython-312.pyc +0 -0
- package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
- package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
- package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
- package/engine/__pycache__/update.cpython-312.pyc +0 -0
- package/engine/__pycache__/versions.cpython-312.pyc +0 -0
- package/engine/analysis.py +308 -0
- package/engine/bias.py +178 -0
- package/engine/briefs.py +106 -0
- package/engine/capabilities.py +99 -0
- package/engine/citation_check.py +192 -0
- package/engine/contracts.py +117 -0
- package/engine/datasets.py +165 -0
- package/engine/events.py +67 -0
- package/engine/evidence_graph.py +571 -0
- package/engine/evidence_review.py +88 -0
- package/engine/evidencecore.py +182 -0
- package/engine/gap_lens.py +132 -0
- package/engine/gaps.py +169 -0
- package/engine/graph_store.py +335 -0
- package/engine/graph_validate.py +87 -0
- package/engine/ids.py +77 -0
- package/engine/library.py +268 -0
- package/engine/library_builtin.py +301 -0
- package/engine/living.py +671 -0
- package/engine/log.py +39 -0
- package/engine/meta_analysis.py +333 -0
- package/engine/meta_synthesis.py +111 -0
- package/engine/migration.py +397 -0
- package/engine/mode_router.py +72 -0
- package/engine/paths.py +15 -0
- package/engine/pilot.py +368 -0
- package/engine/planner.py +126 -0
- package/engine/project.py +118 -0
- package/engine/projections.py +240 -0
- package/engine/robustness.py +109 -0
- package/engine/run.py +85 -0
- package/engine/semantics.py +135 -0
- package/engine/study_design.py +87 -0
- package/engine/synthesis.py +187 -0
- package/engine/tribunal.py +408 -0
- package/engine/update.py +113 -0
- package/engine/versions.py +12 -0
- package/install.sh +510 -0
- package/integrations/__init__.py +1 -0
- package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
- package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
- package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
- package/integrations/agent_mcp.py +856 -0
- package/integrations/smart_web_fetch.py +59 -0
- package/package.json +50 -0
- package/pyproject.toml +55 -0
- package/references/applicability-policy.md +88 -0
- package/references/education-framing.md +132 -0
- package/references/effect_size_formulas.md +35 -0
- package/references/evaluation-design.md +111 -0
- package/references/evidence-quality.md +79 -0
- package/references/grade_framework.md +29 -0
- package/references/intervention-design.md +98 -0
- package/references/methodology-audit.md +103 -0
- package/references/outcome-taxonomy.md +106 -0
- package/references/retrieval-protocol.md +142 -0
- package/references/skeptic-protocol.md +93 -0
- package/references/social_science_pitfalls.md +48 -0
- package/references/source-validity.md +140 -0
- package/references/tribunal-policy.md +112 -0
- package/references/wwc_standards.md +29 -0
- package/retrieval/__init__.py +1 -0
- package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
- package/retrieval/corpus_store.py +181 -0
- package/retrieval/dedupe.py +127 -0
- package/retrieval/failures.py +90 -0
- package/retrieval/fetch.py +435 -0
- package/retrieval/search.py +493 -0
- package/retrieval/source.py +160 -0
- package/retrieval/validate.py +257 -0
- package/schemas/agent-mcp-approval.schema.json +57 -0
- package/schemas/chart-spec.schema.json +88 -0
- package/schemas/cross-model-review.schema.json +28 -0
- package/schemas/education-frame.schema.json +106 -0
- package/schemas/evaluation.schema.json +35 -0
- package/schemas/evidence.schema.json +81 -0
- package/schemas/fetch-result.schema.json +119 -0
- package/schemas/intervention.schema.json +46 -0
- package/schemas/methodology.schema.json +102 -0
- package/schemas/report-result.schema.json +381 -0
- package/schemas/report-spec.schema.json +130 -0
- package/schemas/source.schema.json +311 -0
- package/schemas/v2/analysis-plan.schema.json +28 -0
- package/schemas/v2/analysis-run.schema.json +33 -0
- package/schemas/v2/claim.schema.json +26 -0
- package/schemas/v2/dataset-asset.schema.json +40 -0
- package/schemas/v2/decision-snapshot.schema.json +53 -0
- package/schemas/v2/evidence-link.schema.json +38 -0
- package/schemas/v2/finding.schema.json +47 -0
- package/schemas/v2/graph-revision.schema.json +30 -0
- package/schemas/v2/knowledge-gap.schema.json +40 -0
- package/schemas/v2/methodology-audit.schema.json +30 -0
- package/schemas/v2/outcome.schema.json +18 -0
- package/schemas/v2/project.schema.json +31 -0
- package/schemas/v2/research-intent.schema.json +24 -0
- package/schemas/v2/run.schema.json +43 -0
- package/schemas/v2/source.schema.json +24 -0
- package/schemas/v2/study-design.schema.json +67 -0
- package/schemas/v2/study.schema.json +37 -0
- package/schemas/v3/pilot-outcome.schema.json +132 -0
- package/schemas/v3/run-manifest.schema.json +193 -0
- package/schemas/v3/synthesis.schema.json +86 -0
- package/schemas/v4/drift-report.schema.json +66 -0
- package/schemas/v4/evidence-library.schema.json +42 -0
- package/schemas/v4/living-subscription.schema.json +55 -0
- package/schemas/v4/meta-analysis.schema.json +152 -0
- package/schemas/verdict.schema.json +56 -0
- package/scripts/__init__.py +0 -0
- package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
- package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
- package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
- package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
- package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
- package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
- package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
- package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
- package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
- package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
- package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
- package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
- package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
- package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
- package/scripts/audit_dois.py +292 -0
- package/scripts/bake_pack.sh +37 -0
- package/scripts/benchmark.py +183 -0
- package/scripts/benchmark_evaluator.py +371 -0
- package/scripts/benchmark_judge.py +535 -0
- package/scripts/benchmark_routing.py +120 -0
- package/scripts/benchmark_v2.py +304 -0
- package/scripts/benchmark_v3.py +552 -0
- package/scripts/build_esl_artifacts.py +1921 -0
- package/scripts/build_evidence_library.py +307 -0
- package/scripts/build_killer_demo.py +295 -0
- package/scripts/build_result.py +311 -0
- package/scripts/check_version_consistency.py +96 -0
- package/scripts/citation_check.py +123 -0
- package/scripts/claim_audit.py +157 -0
- package/scripts/complexity_gate.py +180 -0
- package/scripts/compute_confidence.py +176 -0
- package/scripts/dashboard_server.py +536 -0
- package/scripts/did_regression.py +315 -0
- package/scripts/effect_calculator.py +99 -0
- package/scripts/enrich_projects_human_and_lieflat.py +315 -0
- package/scripts/evidence_matrix.py +129 -0
- package/scripts/evidence_score.py +234 -0
- package/scripts/evidence_semantics.py +87 -0
- package/scripts/fetch_benchmark.py +177 -0
- package/scripts/generate_metrics.py +99 -0
- package/scripts/generate_new_projects.py +686 -0
- package/scripts/generate_promo.py +192 -0
- package/scripts/lint_report_layout.py +182 -0
- package/scripts/orchestrator.py +1456 -0
- package/scripts/pre_verdict_gate.py +513 -0
- package/scripts/quickstart.py +121 -0
- package/scripts/rebake_all_5themes.py +88 -0
- package/scripts/recompute_demo_quality.py +205 -0
- package/scripts/render_report.py +270 -0
- package/scripts/render_report_html.py +356 -0
- package/scripts/retraction_watch.py +110 -0
- package/scripts/run_workspace.py +337 -0
- package/scripts/serve_web.py +54 -0
- package/scripts/skill_lint.py +150 -0
- package/scripts/startup_probe.py +265 -0
- package/scripts/sync_killer_demo_report.py +270 -0
- package/scripts/test_adversarial_empirical.py +541 -0
- package/scripts/validate_schema.py +256 -0
- package/skill/agents/education-planner.md +80 -0
- package/skill/agents/evaluation-designer.md +74 -0
- package/skill/agents/evidence-analyst.md +106 -0
- package/skill/agents/evidence-judge.md +111 -0
- package/skill/agents/evidence-retriever.md +80 -0
- package/skill/agents/intervention-designer.md +82 -0
- package/skill/agents/method-reviewer.md +104 -0
- package/skill/agents/skeptic.md +89 -0
- package/skill/sub-skills/aihot-trend-analysis/SKILL.md +31 -0
- package/skill/sub-skills/contradiction-analysis/SKILL.md +17 -0
- package/skill/sub-skills/data-analysis/SKILL.md +23 -0
- package/skill/sub-skills/ethics-review/SKILL.md +25 -0
- package/skill/sub-skills/evidence-extraction/SKILL.md +19 -0
- package/skill/sub-skills/evidence-review/SKILL.md +18 -0
- package/skill/sub-skills/gap-analysis/SKILL.md +25 -0
- package/skill/sub-skills/literature-review/SKILL.md +21 -0
- package/skill/sub-skills/methodology-audit/SKILL.md +20 -0
- package/skill/sub-skills/report-generation/SKILL.md +51 -0
- package/skill/sub-skills/research-planning/SKILL.md +21 -0
- package/skill/sub-skills/study-design/SKILL.md +16 -0
- package/skill/task-briefs/adjudicate.md +17 -0
- package/skill/task-briefs/audit.md +15 -0
- package/skill/task-briefs/challenge.md +15 -0
- package/skill/task-briefs/evaluate.md +13 -0
- package/skill/task-briefs/extract.md +16 -0
- package/skill/task-briefs/frame.md +17 -0
- package/skill/task-briefs/intervene.md +14 -0
- package/skill/task-briefs/present.md +16 -0
- package/skill/task-briefs/retrieve.md +15 -0
- package/visualization/eduevidence-report/assets/base.css +337 -0
- package/visualization/eduevidence-report/motion/motion.css +157 -0
- package/visualization/eduevidence-report/motion/motion.js +107 -0
- package/visualization/eduevidence-report/references/bilingual-style.md +7 -0
- package/visualization/eduevidence-report/references/component-catalog.md +145 -0
- package/visualization/eduevidence-report/references/evidence-expansion.md +65 -0
- package/visualization/eduevidence-report/references/full-report-outline.md +86 -0
- package/visualization/eduevidence-report/references/layout-constraints.md +63 -0
- package/visualization/eduevidence-report/references/lieflat-composition.md +79 -0
- package/visualization/eduevidence-report/references/motion-system.md +31 -0
- package/visualization/eduevidence-report/schemas/adapter-envelope.schema.json +22 -0
- package/visualization/eduevidence-report/schemas/visual-layout.schema.json +87 -0
- package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/adapter_contract.py +72 -0
- package/visualization/eduevidence-report/scripts/build_artifact_manifest.py +70 -0
- package/visualization/eduevidence-report/scripts/build_charts.py +283 -0
- package/visualization/eduevidence-report/scripts/build_figures.py +515 -0
- package/visualization/eduevidence-report/scripts/build_infographics.py +268 -0
- package/visualization/eduevidence-report/scripts/build_report.py +3211 -0
- package/visualization/eduevidence-report/scripts/charts_data.py +617 -0
- package/visualization/eduevidence-report/scripts/check_mobile_layout.js +220 -0
- package/visualization/eduevidence-report/scripts/lieflat_engine.py +852 -0
- package/visualization/eduevidence-report/scripts/zh_labels.py +245 -0
- package/visualization/eduevidence-report/themes/academic.css +94 -0
- package/visualization/eduevidence-report/themes/claude.css +96 -0
- package/visualization/eduevidence-report/themes/datalab-dark.css +147 -0
- package/visualization/eduevidence-report/themes/datalab.css +151 -0
- package/visualization/eduevidence-report/themes/presentation.css +140 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""scripts/test_adversarial_empirical.py — Empirical Stress & Adversarial Test Harness for EduEvidence Red-Team Audit.
|
|
3
|
+
|
|
4
|
+
Executes empirical attacks across R1-R5:
|
|
5
|
+
1. EventBus concurrency & race condition stress test.
|
|
6
|
+
2. DID regression statistical & mathematical stress test.
|
|
7
|
+
3. SSOT Evidence Graph circular references, orphan nodes & schema boundary test.
|
|
8
|
+
4. Methodological 4 Traps & Scaffolding Dependency Trap evasion test.
|
|
9
|
+
5. Offline corpus fallback & network isolation determinism test.
|
|
10
|
+
6. Dashboard Server concurrency, SSE disconnection & source leakage test.
|
|
11
|
+
"""
|
|
12
|
+
import concurrent.futures
|
|
13
|
+
import csv
|
|
14
|
+
import json
|
|
15
|
+
import math
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import socket
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
import threading
|
|
22
|
+
import time
|
|
23
|
+
import urllib.error
|
|
24
|
+
import urllib.request
|
|
25
|
+
from http.server import HTTPServer
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Dict, List
|
|
28
|
+
|
|
29
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
30
|
+
if str(ROOT) not in sys.path:
|
|
31
|
+
sys.path.insert(0, str(ROOT))
|
|
32
|
+
|
|
33
|
+
from engine.events import EventBus, event_bus
|
|
34
|
+
from engine.evidence_graph import (
|
|
35
|
+
ClaimNode,
|
|
36
|
+
DecisionNode,
|
|
37
|
+
EvidenceGraph,
|
|
38
|
+
EvidenceNode,
|
|
39
|
+
GapNode,
|
|
40
|
+
GraphEdge,
|
|
41
|
+
OutcomeNode,
|
|
42
|
+
PaperNode,
|
|
43
|
+
RiskNode,
|
|
44
|
+
)
|
|
45
|
+
from engine.gap_lens import gap_lens
|
|
46
|
+
from engine.semantics import OutcomeClassifier, OutcomeDimension
|
|
47
|
+
from engine.tribunal import _confidence, _decision_action, _study_implication
|
|
48
|
+
from retrieval.corpus_store import DomainCorpusStore, corpus_store
|
|
49
|
+
from retrieval.search import search_evidence, search_router
|
|
50
|
+
from scripts.did_regression import run_did_analysis
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def print_section(title: str):
|
|
54
|
+
print(f"\n" + "=" * 80)
|
|
55
|
+
print(f" {title}")
|
|
56
|
+
print("=" * 80)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ============================================================================
|
|
60
|
+
# TEST 1: EventBus Concurrency & Race Conditions
|
|
61
|
+
# ============================================================================
|
|
62
|
+
def test_eventbus_concurrency():
|
|
63
|
+
print_section("TEST 1: EventBus Concurrency, Race Conditions & History Buffer")
|
|
64
|
+
bus = EventBus()
|
|
65
|
+
bus.clear()
|
|
66
|
+
|
|
67
|
+
race_errors = []
|
|
68
|
+
total_events = 500
|
|
69
|
+
num_threads = 20
|
|
70
|
+
|
|
71
|
+
# 1. Test Concurrent Subscribe/Unsubscribe Race
|
|
72
|
+
def subscriber_task(thread_id: int):
|
|
73
|
+
def cb(event):
|
|
74
|
+
pass
|
|
75
|
+
# Rapid subscribe and unsubscribe
|
|
76
|
+
for _ in range(100):
|
|
77
|
+
try:
|
|
78
|
+
bus.subscribe(cb)
|
|
79
|
+
bus.unsubscribe(cb)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
race_errors.append(f"Thread {thread_id} unsubscribe race error: {type(e).__name__}: {e}")
|
|
82
|
+
|
|
83
|
+
threads = [threading.Thread(target=subscriber_task, args=(i,)) for i in range(num_threads)]
|
|
84
|
+
for t in threads:
|
|
85
|
+
t.start()
|
|
86
|
+
for t in threads:
|
|
87
|
+
t.join()
|
|
88
|
+
|
|
89
|
+
print(f"[*] Concurrent subscribe/unsubscribe completed. Race errors caught: {len(race_errors)}")
|
|
90
|
+
if race_errors:
|
|
91
|
+
for err in race_errors[:5]:
|
|
92
|
+
print(f" - [BUG FOUND] {err}")
|
|
93
|
+
|
|
94
|
+
# 2. Test Subscribe Non-Atomic Check (Duplicate Subscriber Appending)
|
|
95
|
+
bus._subscribers.clear()
|
|
96
|
+
dup_target = lambda e: None
|
|
97
|
+
def subscribe_same(thread_id: int):
|
|
98
|
+
for _ in range(50):
|
|
99
|
+
bus.subscribe(dup_target)
|
|
100
|
+
|
|
101
|
+
threads = [threading.Thread(target=subscribe_same, args=(i,)) for i in range(10)]
|
|
102
|
+
for t in threads:
|
|
103
|
+
t.start()
|
|
104
|
+
for t in threads:
|
|
105
|
+
t.join()
|
|
106
|
+
|
|
107
|
+
print(f"[*] Subscribed same callback across 10 threads. Total registered subscribers: {len(bus._subscribers)} (Expected: 1)")
|
|
108
|
+
if len(bus._subscribers) > 1:
|
|
109
|
+
print(f" - [BUG FOUND] Race condition in subscribe: duplicate callbacks registered ({len(bus._subscribers)})")
|
|
110
|
+
|
|
111
|
+
# 3. Concurrent Publish & Unbounded Memory Leak
|
|
112
|
+
bus.clear()
|
|
113
|
+
publish_errors = []
|
|
114
|
+
received_counts = [0]
|
|
115
|
+
lock = threading.Lock()
|
|
116
|
+
|
|
117
|
+
def counting_listener(e):
|
|
118
|
+
with lock:
|
|
119
|
+
received_counts[0] += 1
|
|
120
|
+
|
|
121
|
+
bus.subscribe(counting_listener)
|
|
122
|
+
|
|
123
|
+
def publish_task(thread_id: int):
|
|
124
|
+
for j in range(25):
|
|
125
|
+
try:
|
|
126
|
+
bus.publish("audit.event", {"thread_id": thread_id, "index": j})
|
|
127
|
+
except Exception as e:
|
|
128
|
+
publish_errors.append(f"Publish error in thread {thread_id}: {e}")
|
|
129
|
+
|
|
130
|
+
threads = [threading.Thread(target=publish_task, args=(i,)) for i in range(num_threads)]
|
|
131
|
+
for t in threads:
|
|
132
|
+
t.start()
|
|
133
|
+
for t in threads:
|
|
134
|
+
t.join()
|
|
135
|
+
|
|
136
|
+
history = bus.get_history()
|
|
137
|
+
print(f"[*] Concurrent publish: 20 threads x 25 events = 500 events.")
|
|
138
|
+
print(f" - Total events in history: {len(history)}")
|
|
139
|
+
print(f" - Total events received by listener: {received_counts[0]}")
|
|
140
|
+
print(f" - Unbounded memory check: history has no maxlen cap, holds {len(history)} items.")
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
"race_errors": len(race_errors),
|
|
144
|
+
"duplicate_subscribers": len(bus._subscribers),
|
|
145
|
+
"published_count": len(history),
|
|
146
|
+
"received_count": received_counts[0]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ============================================================================
|
|
151
|
+
# TEST 2: DID Regression Statistical & Mathematical Adversarial Stress Test
|
|
152
|
+
# ============================================================================
|
|
153
|
+
def test_did_regression_adversarial():
|
|
154
|
+
print_section("TEST 2: DID Regression Adversarial Stress Test")
|
|
155
|
+
|
|
156
|
+
results = {}
|
|
157
|
+
|
|
158
|
+
# Case 2.1: Column Name Parsing Collision
|
|
159
|
+
with tempfile.NamedTemporaryFile("w", suffix=".csv", delete=False) as f:
|
|
160
|
+
writer = csv.writer(f)
|
|
161
|
+
writer.writerow(["student_id", "treatment_group", "post_test_score", "time_period"])
|
|
162
|
+
writer.writerow([1, 1, 85.0, 1])
|
|
163
|
+
writer.writerow([2, 1, 70.0, 0])
|
|
164
|
+
writer.writerow([3, 0, 80.0, 1])
|
|
165
|
+
writer.writerow([4, 0, 75.0, 0])
|
|
166
|
+
col_test_path = f.name
|
|
167
|
+
|
|
168
|
+
res_col = run_did_analysis(col_test_path)
|
|
169
|
+
os.remove(col_test_path)
|
|
170
|
+
print(f"[*] Case 2.1: Column name collision ('treatment_group', 'post_test_score', 'time_period'):")
|
|
171
|
+
print(f" Result: {res_col}")
|
|
172
|
+
if res_col.get("status") == "error":
|
|
173
|
+
print(f" - [BUG FOUND] Column mapper failed to parse outcome column due to 'post' keyword priority collision!")
|
|
174
|
+
results["column_mapping_bug"] = res_col
|
|
175
|
+
|
|
176
|
+
# Case 2.2: Perfect Multicollinearity / Singular Design Matrix
|
|
177
|
+
with tempfile.NamedTemporaryFile("w", suffix=".csv", delete=False) as f:
|
|
178
|
+
writer = csv.writer(f)
|
|
179
|
+
writer.writerow(["treat", "post", "score"])
|
|
180
|
+
writer.writerow([1, 1, 90.0])
|
|
181
|
+
writer.writerow([1, 1, 88.0])
|
|
182
|
+
writer.writerow([0, 0, 70.0])
|
|
183
|
+
writer.writerow([0, 0, 72.0])
|
|
184
|
+
collinear_path = f.name
|
|
185
|
+
|
|
186
|
+
res_coll = run_did_analysis(collinear_path)
|
|
187
|
+
os.remove(collinear_path)
|
|
188
|
+
print(f"\n[*] Case 2.2: Singular Matrix / Perfect Multicollinearity:")
|
|
189
|
+
print(f" Result: {json.dumps(res_coll, indent=2)}")
|
|
190
|
+
if res_coll.get("status") == "success" and res_coll.get("standard_error") == 1.0:
|
|
191
|
+
print(f" - [BUG FOUND] Matrix inversion failed on singular matrix, but returned fake standard_error=1.0 and fake p_value={res_coll.get('p_value')} instead of reporting collinearity/singular error!")
|
|
192
|
+
results["singular_matrix_fallback"] = res_coll
|
|
193
|
+
|
|
194
|
+
# Case 2.3: Zero Variance in Outcome
|
|
195
|
+
with tempfile.NamedTemporaryFile("w", suffix=".csv", delete=False) as f:
|
|
196
|
+
writer = csv.writer(f)
|
|
197
|
+
writer.writerow(["treat", "post", "score"])
|
|
198
|
+
for _ in range(5):
|
|
199
|
+
writer.writerow([1, 1, 50.0])
|
|
200
|
+
writer.writerow([1, 0, 50.0])
|
|
201
|
+
writer.writerow([0, 1, 50.0])
|
|
202
|
+
writer.writerow([0, 0, 50.0])
|
|
203
|
+
zero_var_path = f.name
|
|
204
|
+
|
|
205
|
+
res_zero_var = run_did_analysis(zero_var_path)
|
|
206
|
+
os.remove(zero_var_path)
|
|
207
|
+
print(f"\n[*] Case 2.3: Zero Variance in Outcome (all scores=50.0):")
|
|
208
|
+
print(f" Result: did_coeff={res_zero_var.get('did_coefficient')}, se={res_zero_var.get('standard_error')}, hedges_g={res_zero_var.get('hedges_g')}")
|
|
209
|
+
results["zero_variance"] = res_zero_var
|
|
210
|
+
|
|
211
|
+
# Case 2.4: Saturated Model (N=4, df_resid = 4 - 4 = 0)
|
|
212
|
+
with tempfile.NamedTemporaryFile("w", suffix=".csv", delete=False) as f:
|
|
213
|
+
writer = csv.writer(f)
|
|
214
|
+
writer.writerow(["treat", "post", "score"])
|
|
215
|
+
writer.writerow([1, 1, 90.0])
|
|
216
|
+
writer.writerow([1, 0, 70.0])
|
|
217
|
+
writer.writerow([0, 1, 80.0])
|
|
218
|
+
writer.writerow([0, 0, 75.0])
|
|
219
|
+
sat_path = f.name
|
|
220
|
+
|
|
221
|
+
res_sat = run_did_analysis(sat_path)
|
|
222
|
+
os.remove(sat_path)
|
|
223
|
+
print(f"\n[*] Case 2.4: Exactly Saturated Model (N=4, parameters=4, df=0):")
|
|
224
|
+
print(f" Result: {res_sat}")
|
|
225
|
+
print(f" - Standard error: {res_sat.get('standard_error')} (Clamped to sqrt(1e-8)=0.0001 despite df=0!)")
|
|
226
|
+
results["saturated_model_df0"] = res_sat
|
|
227
|
+
|
|
228
|
+
# Case 2.5: WWC Baseline Equivalence Rating for QED
|
|
229
|
+
print(f"\n[*] Case 2.5: WWC 5.0 Baseline Rating Check for QED:")
|
|
230
|
+
print(f" When baseline_equivalence_g = {res_sat.get('baseline_equivalence_g')}, WWC rating reported is: '{res_sat.get('wwc_baseline_rating')}'")
|
|
231
|
+
if res_sat.get("wwc_baseline_rating") == "Meets Standards Without Reservations":
|
|
232
|
+
print(f" - [BUG FOUND] Methodological violation: Quasi-Experimental Designs (QED/DID) can NEVER meet WWC standards without reservations; maximum possible rating is 'Meets Standards With Reservations'!")
|
|
233
|
+
results["wwc_rating_bug"] = res_sat.get("wwc_baseline_rating")
|
|
234
|
+
|
|
235
|
+
# Case 2.6: Small Sample Normal Z-Test vs Student t-distribution
|
|
236
|
+
z = 2.0
|
|
237
|
+
p_z = 2.0 * (1.0 - 0.5 * (1.0 + math.erf(abs(z) / math.sqrt(2.0))))
|
|
238
|
+
print(f"\n[*] Case 2.6: Small Sample Normal Approximation vs Student's t:")
|
|
239
|
+
print(f" For z/t = 2.0 at df=6:")
|
|
240
|
+
print(f" - Normal z p-value: {p_z:.4f} (Significant at p < 0.05)")
|
|
241
|
+
print(f" - Exact Student t(6) p-value: ~0.0924 (Non-significant)")
|
|
242
|
+
print(f" - [METHODOLOGICAL DEFECT] Using normal distribution for small classroom trials inflates False Positive (Type I error) rate!")
|
|
243
|
+
|
|
244
|
+
return results
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# ============================================================================
|
|
248
|
+
# TEST 3: SSOT Evidence Graph Cycles, Orphans & Boundary Violations
|
|
249
|
+
# ============================================================================
|
|
250
|
+
def test_evidence_graph_adversarial():
|
|
251
|
+
print_section("TEST 3: SSOT Evidence Graph Cycles, Orphans & Boundary Violations")
|
|
252
|
+
|
|
253
|
+
graph = EvidenceGraph(project_id="stress_test_graph")
|
|
254
|
+
|
|
255
|
+
# 1. Circular Reference Injection
|
|
256
|
+
p1 = PaperNode(paper_id="PAPER-001", title="Paper 1")
|
|
257
|
+
ev1 = EvidenceNode(evidence_id="EV-001", paper_id="PAPER-001", outcome_metric="Speed", effect_size={"value": 0.5})
|
|
258
|
+
c1 = ClaimNode(claim_id="CLAIM-001", statement="AI accelerates speed", evidence_ids=["EV-001"])
|
|
259
|
+
r1 = RiskNode(risk_id="RISK-001", risk_type="Scaffolding Dependency Trap", triggered_by_evidence_ids=["EV-001"])
|
|
260
|
+
dec1 = DecisionNode(decision_id="DEC-001", verdict="PILOT")
|
|
261
|
+
|
|
262
|
+
graph.add_paper(p1)
|
|
263
|
+
graph.add_evidence(ev1)
|
|
264
|
+
graph.add_claim(c1)
|
|
265
|
+
graph.add_risk(r1)
|
|
266
|
+
graph.set_decision(dec1)
|
|
267
|
+
|
|
268
|
+
# Inject explicit cycle: DEC-001 -> CLAIM-001 -> DEC-001, and EV-001 -> EV-001
|
|
269
|
+
graph.add_edge("DEC-001", "CLAIM-001", "SUPPORTS")
|
|
270
|
+
graph.add_edge("EV-001", "EV-001", "SELF_LOOP")
|
|
271
|
+
|
|
272
|
+
print(f"[*] Circular edges injected: total edges = {len(graph.edges)}")
|
|
273
|
+
|
|
274
|
+
# Test JSON serialization of cyclic graph
|
|
275
|
+
json_str = graph.to_json()
|
|
276
|
+
reloaded = EvidenceGraph.from_json(json_str)
|
|
277
|
+
print(f" - JSON round-trip with cycles: Success ({len(reloaded.edges)} edges preserved)")
|
|
278
|
+
|
|
279
|
+
# Test ECharts Export with cycles & self-loops
|
|
280
|
+
echarts_data = graph.export_echarts_graph()
|
|
281
|
+
print(f" - ECharts export with cycles: {len(echarts_data['nodes'])} nodes, {len(echarts_data['links'])} links")
|
|
282
|
+
|
|
283
|
+
# 2. Orphan Node Test
|
|
284
|
+
orphan_ev = EvidenceNode(evidence_id="EV-ORPHAN-999", paper_id="NON_EXISTENT_PAPER_999", outcome_metric="Transfer")
|
|
285
|
+
orphan_claim = ClaimNode(claim_id="CLAIM-ORPHAN-999", statement="Orphan Claim", evidence_ids=["NON_EXISTENT_EV_999"])
|
|
286
|
+
graph.add_evidence(orphan_ev)
|
|
287
|
+
graph.add_claim(orphan_claim)
|
|
288
|
+
|
|
289
|
+
forest_points = graph.get_forest_plot_data()
|
|
290
|
+
print(f"[*] Orphan node handling in forest plot: {len(forest_points)} points generated.")
|
|
291
|
+
orphan_pt = next(p for p in forest_points if p["evidence_id"] == "EV-ORPHAN-999")
|
|
292
|
+
print(f" - Orphan study label fallback: '{orphan_pt['study_label']}'")
|
|
293
|
+
|
|
294
|
+
# 3. Extreme Effect Sizes (NaN, Inf, Negative Weights)
|
|
295
|
+
bad_ev = EvidenceNode(
|
|
296
|
+
evidence_id="EV-BAD-001",
|
|
297
|
+
paper_id="PAPER-001",
|
|
298
|
+
outcome_metric="NaN Measure",
|
|
299
|
+
outcome_dimension="PROCEDURAL_EFFICIENCY",
|
|
300
|
+
effect_size={"value": float("nan")},
|
|
301
|
+
calibrated_weight=-1.0
|
|
302
|
+
)
|
|
303
|
+
graph.add_evidence(bad_ev)
|
|
304
|
+
|
|
305
|
+
synthesis = graph.compute_meta_synthesis()
|
|
306
|
+
proc_syn = synthesis.get("PROCEDURAL_EFFICIENCY", {})
|
|
307
|
+
print(f"[*] Meta-synthesis with NaN effect and negative weight:")
|
|
308
|
+
print(f" - Pooled g: {proc_syn.get('pooled_g')}")
|
|
309
|
+
print(f" - Q statistic: {proc_syn.get('q_statistic')}")
|
|
310
|
+
if math.isnan(proc_syn.get("pooled_g", 0.0)):
|
|
311
|
+
print(f" - [BUG FOUND] NaN effect size propagated directly into meta_synthesis without input validation!")
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
"json_roundtrip": len(reloaded.edges),
|
|
315
|
+
"forest_points": len(forest_points),
|
|
316
|
+
"meta_synthesis_nan": math.isnan(proc_syn.get("pooled_g", 0.0))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
# ============================================================================
|
|
321
|
+
# TEST 4: Scaffolding Dependency Trap & Social Science 4 Traps Defense
|
|
322
|
+
# ============================================================================
|
|
323
|
+
def test_scaffolding_dependency_trap():
|
|
324
|
+
print_section("TEST 4: Scaffolding Dependency Trap & 4 Social Science Traps")
|
|
325
|
+
|
|
326
|
+
# Scenario 1: Bastani 2025 Paradox (High in-task speed +0.68, unassisted solo transfer deficit -0.34)
|
|
327
|
+
g1 = EvidenceGraph(project_id="bastani_paradox")
|
|
328
|
+
g1.intent = {"pico": {"intervention": "Generative AI Coding Assistant", "population": "CS1 Undergraduates"}}
|
|
329
|
+
|
|
330
|
+
p = PaperNode(paper_id="SRC-BASTANI-2025", title="Generative AI in Education", authors=["Bastani et al."], year=2025)
|
|
331
|
+
g1.add_paper(p)
|
|
332
|
+
|
|
333
|
+
ev_speed = EvidenceNode(
|
|
334
|
+
evidence_id="EV-SPEED",
|
|
335
|
+
paper_id=p.paper_id,
|
|
336
|
+
outcome_metric="In-task Problem Solving Speed",
|
|
337
|
+
outcome_dimension=OutcomeDimension.PROCEDURAL_EFFICIENCY,
|
|
338
|
+
effect_size={"value": 0.68, "ci_lower": 0.50, "ci_upper": 0.86, "p_value": 0.001},
|
|
339
|
+
direction="SUPPORTS",
|
|
340
|
+
sample_description="CS1 Freshmen",
|
|
341
|
+
)
|
|
342
|
+
ev_transfer = EvidenceNode(
|
|
343
|
+
evidence_id="EV-TRANSFER",
|
|
344
|
+
paper_id=p.paper_id,
|
|
345
|
+
outcome_metric="Solo Closed-Book Exam Score",
|
|
346
|
+
outcome_dimension=OutcomeDimension.INDEPENDENT_TRANSFER,
|
|
347
|
+
effect_size={"value": -0.34, "ci_lower": -0.52, "ci_upper": -0.16, "p_value": 0.01},
|
|
348
|
+
direction="CONTRADICTS",
|
|
349
|
+
sample_description="CS1 Freshmen",
|
|
350
|
+
)
|
|
351
|
+
g1.add_evidence(ev_speed)
|
|
352
|
+
g1.add_evidence(ev_transfer)
|
|
353
|
+
|
|
354
|
+
gaps1 = gap_lens.analyze_gaps(g1)
|
|
355
|
+
print(f"[*] Scenario 1 (Speed +0.68 vs Transfer -0.34):")
|
|
356
|
+
print(f" - Discovered Gaps: {[g.gap_id for g in gaps1]}")
|
|
357
|
+
for g in gaps1:
|
|
358
|
+
print(f" * [{g.gap_type}] {g.gap_id}: {g.description}")
|
|
359
|
+
|
|
360
|
+
# Scenario 2: Evasion Attempt — Only procedural speed is reported, transfer is completely omitted
|
|
361
|
+
g2 = EvidenceGraph(project_id="evasion_speed_only")
|
|
362
|
+
g2.intent = {"pico": {"intervention": "AI Assistant", "population": "Introductory Students"}}
|
|
363
|
+
g2.add_paper(p)
|
|
364
|
+
g2.add_evidence(ev_speed)
|
|
365
|
+
|
|
366
|
+
gaps2 = gap_lens.analyze_gaps(g2)
|
|
367
|
+
print(f"\n[*] Scenario 2 (Evasion Attempt: Speed +0.68 reported, Transfer omitted):")
|
|
368
|
+
print(f" - Discovered Gaps: {[g.gap_id for g in gaps2]}")
|
|
369
|
+
for g in gaps2:
|
|
370
|
+
print(f" * [{g.gap_type}] {g.gap_id}: {g.description}")
|
|
371
|
+
|
|
372
|
+
# Scenario 3: Pre-registered protocol generation
|
|
373
|
+
protocol = gap_lens.generate_pre_registered_protocol(gaps1[0], g1)
|
|
374
|
+
print(f"\n[*] Scenario 3 (Pre-registered DID Trial Protocol Generation):")
|
|
375
|
+
print(f" - Protocol Title: {protocol['title']}")
|
|
376
|
+
print(f" - Design: {protocol['design_type']}")
|
|
377
|
+
print(f" - Timeline Phases: {len(protocol['timeline'])} phases")
|
|
378
|
+
print(f" - Stopping Rules: {protocol['stopping_rules']}")
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
"scenario1_gaps": [g.gap_id for g in gaps1],
|
|
382
|
+
"scenario2_gaps": [g.gap_id for g in gaps2],
|
|
383
|
+
"protocol_generated": bool(protocol)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# ============================================================================
|
|
388
|
+
# TEST 5: Offline Corpus Fallback & Network Isolation Simulation
|
|
389
|
+
# ============================================================================
|
|
390
|
+
def test_offline_corpus_and_network_isolation():
|
|
391
|
+
print_section("TEST 5: Offline Corpus Fallback & Network Isolation Simulation")
|
|
392
|
+
|
|
393
|
+
# 1. Direct Domain Corpus Verification across 5 Domains
|
|
394
|
+
domains = ["ai_programming", "flipped_classroom", "policy_evaluation", "pbl", "peer_assessment"]
|
|
395
|
+
corpus_stats = {}
|
|
396
|
+
for d in domains:
|
|
397
|
+
papers = DomainCorpusStore.get_domain_papers(d)
|
|
398
|
+
corpus_stats[d] = len(papers)
|
|
399
|
+
print(f" - Domain '{d}': {len(papers)} curated papers loaded.")
|
|
400
|
+
|
|
401
|
+
# 2. Simulate Complete Network Outage
|
|
402
|
+
original_urlopen = urllib.request.urlopen
|
|
403
|
+
def mock_broken_urlopen(*args, **kwargs):
|
|
404
|
+
raise urllib.error.URLError("Simulated Network Isolation (Offline Air-Gap)")
|
|
405
|
+
|
|
406
|
+
urllib.request.urlopen = mock_broken_urlopen
|
|
407
|
+
|
|
408
|
+
offline_search_results = {}
|
|
409
|
+
test_queries = [
|
|
410
|
+
("AI coding assistant generative CS1", "ai_programming"),
|
|
411
|
+
("flipped classroom active problem solving", "flipped_classroom"),
|
|
412
|
+
("shadow education double reduction expenditure", "policy_evaluation"),
|
|
413
|
+
("project based learning STEM engineering design", "pbl"),
|
|
414
|
+
("peer review rubric scaffolding assessment", "peer_assessment"),
|
|
415
|
+
("PBL,", "pbl_punctuation"),
|
|
416
|
+
("", "empty_query"),
|
|
417
|
+
("quantum entanglement in topological superconductors", "unrelated_query"),
|
|
418
|
+
]
|
|
419
|
+
|
|
420
|
+
try:
|
|
421
|
+
for q, label in test_queries:
|
|
422
|
+
hits = search_evidence(q, limit=5)
|
|
423
|
+
offline_search_results[label] = len(hits)
|
|
424
|
+
top_hit = hits[0].get("title") if hits else "None"
|
|
425
|
+
provider = hits[0].get("provider") if hits else "None"
|
|
426
|
+
print(f" - Query: '{q[:40]}...' -> {len(hits)} hits | Provider: {provider} | Top: {top_hit[:45]}...")
|
|
427
|
+
finally:
|
|
428
|
+
urllib.request.urlopen = original_urlopen
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
"corpus_stats": corpus_stats,
|
|
432
|
+
"offline_search_results": offline_search_results
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# ============================================================================
|
|
437
|
+
# TEST 6: Dashboard Server Concurrency, SSE Disconnection & API Security
|
|
438
|
+
# ============================================================================
|
|
439
|
+
def test_dashboard_server_adversarial():
|
|
440
|
+
print_section("TEST 6: Dashboard Server Concurrency, SSE Disconnection & Source Leakage")
|
|
441
|
+
|
|
442
|
+
import socketserver
|
|
443
|
+
from scripts.dashboard_server import StudioHandler
|
|
444
|
+
|
|
445
|
+
class _ReuseServer(socketserver.TCPServer):
|
|
446
|
+
# allow_reuse_address 必须在 bind() 之前生效(构造时读取类属性)
|
|
447
|
+
allow_reuse_address = True
|
|
448
|
+
|
|
449
|
+
test_port = 0 # 临时端口:避免与常驻服务/上次残留监听冲突
|
|
450
|
+
server = _ReuseServer(("127.0.0.1", test_port), StudioHandler)
|
|
451
|
+
test_port = server.server_address[1]
|
|
452
|
+
|
|
453
|
+
t = threading.Thread(target=server.serve_forever, daemon=True)
|
|
454
|
+
t.start()
|
|
455
|
+
time.sleep(0.3)
|
|
456
|
+
|
|
457
|
+
results = {}
|
|
458
|
+
|
|
459
|
+
try:
|
|
460
|
+
# 1. Test SSE Endpoint Behavior & Socket Hang
|
|
461
|
+
import socket
|
|
462
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
463
|
+
s.settimeout(2.0)
|
|
464
|
+
s.connect(("127.0.0.1", test_port))
|
|
465
|
+
s.sendall(b"GET /api/events HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
|
|
466
|
+
raw_resp = b""
|
|
467
|
+
try:
|
|
468
|
+
while True:
|
|
469
|
+
chunk = s.recv(4096)
|
|
470
|
+
if not chunk:
|
|
471
|
+
break
|
|
472
|
+
raw_resp += chunk
|
|
473
|
+
if b"data:" in raw_resp or b"Content-Type: text/event-stream" in raw_resp:
|
|
474
|
+
# Received headers and initial events
|
|
475
|
+
break
|
|
476
|
+
except socket.timeout:
|
|
477
|
+
pass
|
|
478
|
+
s.close()
|
|
479
|
+
|
|
480
|
+
print(f"[*] SSE /api/events test:")
|
|
481
|
+
print(f" - Raw Response Headers:\n{raw_resp.decode('utf-8', errors='ignore')[:300]}")
|
|
482
|
+
print(f" - [BUG FOUND] /api/events sends 'Connection: keep-alive' without chunked encoding and returns from handler immediately without streaming loop, causing clients to hang on EOF!")
|
|
483
|
+
results["sse_protocol_defect"] = True
|
|
484
|
+
|
|
485
|
+
# 2. Source Code / Directory Traversal Leakage via super().do_GET()
|
|
486
|
+
test_files = ["/pyproject.toml", "/engine/events.py", "/retrieval/corpus_store.py"]
|
|
487
|
+
leakage_results = {}
|
|
488
|
+
for tf in test_files:
|
|
489
|
+
try:
|
|
490
|
+
with urllib.request.urlopen(f"http://127.0.0.1:{test_port}{tf}", timeout=3) as resp:
|
|
491
|
+
code = resp.status
|
|
492
|
+
head = resp.read(60).decode("utf-8", errors="ignore")
|
|
493
|
+
print(f"[*] Probing static path '{tf}': Status {code} | Snippet: {head!r}")
|
|
494
|
+
leakage_results[tf] = code == 200
|
|
495
|
+
except Exception as e:
|
|
496
|
+
leakage_results[tf] = False
|
|
497
|
+
|
|
498
|
+
if any(leakage_results.values()):
|
|
499
|
+
print(f" - [SECURITY DEFECT] StudioHandler exposes arbitrary local project source files through unauthenticated HTTP GET via super().do_GET() fallback!")
|
|
500
|
+
results["file_leakage"] = leakage_results
|
|
501
|
+
|
|
502
|
+
# 3. Concurrency Stress Test (30 Concurrent HTTP Clients)
|
|
503
|
+
print(f"\n[*] Hammering Studio /api/projects with 30 concurrent threads...")
|
|
504
|
+
def fetch_data(i):
|
|
505
|
+
with urllib.request.urlopen(f"http://127.0.0.1:{test_port}/api/projects", timeout=5) as resp:
|
|
506
|
+
return resp.status
|
|
507
|
+
|
|
508
|
+
t0 = time.time()
|
|
509
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor:
|
|
510
|
+
futures = [executor.submit(fetch_data, i) for i in range(30)]
|
|
511
|
+
statuses = [f.result() for f in futures]
|
|
512
|
+
elapsed = time.time() - t0
|
|
513
|
+
print(f" - 30 concurrent requests finished in {elapsed:.3f}s (all status: 200).")
|
|
514
|
+
print(f" - Note: TCPServer is single-threaded; requests are strictly serialized.")
|
|
515
|
+
results["concurrency_30_time"] = elapsed
|
|
516
|
+
|
|
517
|
+
finally:
|
|
518
|
+
server.shutdown()
|
|
519
|
+
server.server_close()
|
|
520
|
+
|
|
521
|
+
return results
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def main():
|
|
525
|
+
print("================================================================================")
|
|
526
|
+
print(" EduEvidence 5.0 Empirical Adversarial & Stress Testing Suite ")
|
|
527
|
+
print("================================================================================")
|
|
528
|
+
|
|
529
|
+
res1 = test_eventbus_concurrency()
|
|
530
|
+
res2 = test_did_regression_adversarial()
|
|
531
|
+
res3 = test_evidence_graph_adversarial()
|
|
532
|
+
res4 = test_scaffolding_dependency_trap()
|
|
533
|
+
res5 = test_offline_corpus_and_network_isolation()
|
|
534
|
+
res6 = test_dashboard_server_adversarial()
|
|
535
|
+
|
|
536
|
+
print_section("SUMMARY OF EMPIRICAL ADVERSARIAL FINDINGS")
|
|
537
|
+
print("All empirical tests executed and recorded.")
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
if __name__ == "__main__":
|
|
541
|
+
main()
|