ACLASCollege commited on
Commit
68b0f89
·
verified ·
1 Parent(s): ff6a59a

Fix audit blacklist handling and demo responsiveness

Browse files
Files changed (2) hide show
  1. agents/logic_auditor.py +132 -128
  2. index.html +546 -487
agents/logic_auditor.py CHANGED
@@ -1,128 +1,132 @@
1
- import json
2
- from pathlib import Path
3
- from typing import Any, Dict, List
4
-
5
- from pydantic import BaseModel
6
-
7
- from core.mcp_protocol import mcp_call
8
-
9
-
10
- class AuditResolution(BaseModel):
11
- verdict: str
12
- risk_score: float
13
- reasoning_steps: List[str]
14
- mcp_trace: str
15
- warning: str = ""
16
-
17
-
18
- class LogicAuditor:
19
- """
20
- Evidence-weighted logic auditor for credential review.
21
-
22
- The auditor treats registry matches as supporting evidence only. It does not
23
- approve a credential solely because an institution exists in ROR or because a
24
- file name contains a trusted-looking keyword.
25
- """
26
-
27
- def __init__(self, blacklist_path: str = "data/fraud_blacklist.json"):
28
- self.blacklist_path = Path(blacklist_path)
29
-
30
- @staticmethod
31
- def _normalize(value: str) -> str:
32
- return " ".join(value.lower().replace("&", "and").split())
33
-
34
- def _load_blacklist_names(self) -> set[str]:
35
- names: set[str] = set()
36
- try:
37
- with self.blacklist_path.open("r", encoding="utf-8") as f:
38
- blacklist_data = json.load(f)
39
- except (OSError, json.JSONDecodeError):
40
- return names
41
-
42
- for entry in blacklist_data.get("blacklist", []):
43
- if entry.get("name"):
44
- names.add(self._normalize(entry["name"]))
45
- for alias in entry.get("aliases", []):
46
- names.add(self._normalize(alias))
47
- return names
48
-
49
- async def audit(self, transcript: Dict[str, Any], profile: Dict[str, Any]) -> AuditResolution:
50
- print("[LOGIC] [Logic-Auditor] Initializing evidence-weighted review...")
51
- call = mcp_call("mcp_logic_audit", {"transcript_id": "...", "context_level": "deep"})
52
-
53
- reasoning_steps: List[str] = []
54
- anomalies: List[tuple[str, float]] = []
55
- warnings: List[str] = []
56
-
57
- inst_name = self._normalize(profile.get("name", ""))
58
- blacklist_names = self._load_blacklist_names()
59
- is_diploma_mill = profile.get("is_diploma_mill", False) or inst_name in blacklist_names
60
- profile_status = profile.get("status", "unknown")
61
-
62
- reasoning_steps.append("Step 0: Checking known diploma-mill and degree-factory indicators.")
63
- if is_diploma_mill or profile_status == "fraudulent":
64
- warning_msg = profile.get("warning") or "DIPLOMA MILL / DEGREE FACTORY DETECTED -- credentials from this institution require hard rejection."
65
- return AuditResolution(
66
- verdict="REJECTED DIPLOMA MILL / DEGREE FACTORY",
67
- risk_score=100.0,
68
- reasoning_steps=[
69
- *reasoning_steps,
70
- f"Result 0: HARD REJECTION. '{profile.get('name', 'Unknown')}' is flagged by the fraud registry.",
71
- "No approval is issued because the issuing entity is disqualified.",
72
- ],
73
- mcp_trace=call.trace_id,
74
- warning=warning_msg,
75
- )
76
- reasoning_steps.append("Result 0: No exact blacklist or alias match found.")
77
-
78
- reasoning_steps.append("Step 1: Mapping graduation window against institutional lifecycle.")
79
- grad_year = int(transcript.get("graduation_year") or 0)
80
- est_year = profile.get("established_year")
81
- if grad_year > 0 and est_year and grad_year < int(est_year):
82
- anomalies.append(("CRITICAL: Graduation predates the institution founding year.", 55.0))
83
- reasoning_steps.append("Result 1: Temporal violation found.")
84
- elif est_year:
85
- reasoning_steps.append("Result 1: Timeline is internally consistent.")
86
- else:
87
- warnings.append("Founding year unavailable; temporal validation is incomplete.")
88
- reasoning_steps.append("Result 1: Founding year unavailable; timeline needs review.")
89
-
90
- reasoning_steps.append("Step 2: Evaluating registry evidence without granting automatic approval.")
91
- has_ror_id = bool(profile.get("ror_id"))
92
- source = profile.get("source", "none")
93
- match_confidence = float(profile.get("match_confidence") or 0.0)
94
- if has_ror_id and profile_status == "active":
95
- reasoning_steps.append("Result 2: Active ROR presence found as supporting institution-existence evidence.")
96
- elif has_ror_id:
97
- anomalies.append((f"WARNING: ROR status is '{profile_status}', not active.", 30.0))
98
- reasoning_steps.append("Result 2: Registry presence found, but status requires review.")
99
- else:
100
- anomalies.append(("WARNING: No verified registry identifier was resolved.", 35.0))
101
- reasoning_steps.append("Result 2: No ROR identifier available.")
102
-
103
- if source in {"ror", "local_index"} and 0 < match_confidence < 0.80:
104
- anomalies.append(("WARNING: Institution match confidence is below the production threshold.", 25.0))
105
- reasoning_steps.append("Result 2b: Match confidence is low and should be manually reviewed.")
106
-
107
- reasoning_steps.append("Step 3: Checking credential-authenticity evidence.")
108
- if not transcript.get("credential_id") and not transcript.get("signature_verified"):
109
- anomalies.append(("WARNING: No credential ID or cryptographic issuer signature was verified.", 35.0))
110
- reasoning_steps.append("Result 3: Credential authenticity remains unproven.")
111
- else:
112
- reasoning_steps.append("Result 3: Credential-level evidence is present.")
113
-
114
- risk_score = min(100.0, sum(weight for _, weight in anomalies))
115
- if risk_score >= 85:
116
- verdict = "REJECTED"
117
- elif risk_score > 0 or warnings:
118
- verdict = "NEEDS_REVIEW"
119
- else:
120
- verdict = "APPROVED"
121
-
122
- return AuditResolution(
123
- verdict=verdict,
124
- risk_score=risk_score,
125
- reasoning_steps=[*reasoning_steps, *[item for item, _ in anomalies], *warnings],
126
- mcp_trace=call.trace_id,
127
- warning="; ".join(warnings),
128
- )
 
 
 
 
 
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Any, Dict, List
4
+
5
+ from pydantic import BaseModel
6
+
7
+ from core.mcp_protocol import mcp_call
8
+
9
+
10
+ class AuditResolution(BaseModel):
11
+ verdict: str
12
+ risk_score: float
13
+ reasoning_steps: List[str]
14
+ mcp_trace: str
15
+ warning: str = ""
16
+
17
+
18
+ class LogicAuditor:
19
+ """
20
+ Evidence-weighted logic auditor for credential review.
21
+
22
+ The auditor treats registry matches as supporting evidence only. It does not
23
+ approve a credential solely because an institution exists in ROR or because a
24
+ file name contains a trusted-looking keyword.
25
+ """
26
+
27
+ def __init__(self, blacklist_path: str = "data/fraud_blacklist.json"):
28
+ self.blacklist_path = Path(blacklist_path)
29
+
30
+ @staticmethod
31
+ def _normalize(value: str) -> str:
32
+ return " ".join(value.lower().replace("&", "and").split())
33
+
34
+ def _load_blacklist_names(self) -> set[str]:
35
+ names: set[str] = set()
36
+ try:
37
+ with self.blacklist_path.open("r", encoding="utf-8") as f:
38
+ blacklist_data = json.load(f)
39
+ except (OSError, json.JSONDecodeError):
40
+ return names
41
+
42
+ for entry in blacklist_data.get("blacklist", []):
43
+ if entry.get("name"):
44
+ names.add(self._normalize(entry["name"]))
45
+ for alias in entry.get("aliases", []):
46
+ names.add(self._normalize(alias))
47
+ return names
48
+
49
+ async def audit(self, transcript: Dict[str, Any], profile: Dict[str, Any]) -> AuditResolution:
50
+ print("[LOGIC] [Logic-Auditor] Initializing evidence-weighted review...")
51
+ call = mcp_call("mcp_logic_audit", {"transcript_id": "...", "context_level": "deep"})
52
+
53
+ reasoning_steps: List[str] = []
54
+ anomalies: List[tuple[str, float]] = []
55
+ warnings: List[str] = []
56
+
57
+ profile_name = self._normalize(profile.get("name", ""))
58
+ transcript_name = self._normalize(transcript.get("institution_name", ""))
59
+ blacklist_names = self._load_blacklist_names()
60
+ profile_blacklisted = profile_name in blacklist_names
61
+ transcript_blacklisted = transcript_name in blacklist_names
62
+ is_diploma_mill = profile.get("is_diploma_mill", False) or profile_blacklisted or transcript_blacklisted
63
+ profile_status = profile.get("status", "unknown")
64
+
65
+ reasoning_steps.append("Step 0: Checking known diploma-mill and degree-factory indicators.")
66
+ if is_diploma_mill or profile_status == "fraudulent":
67
+ warning_msg = profile.get("warning") or "DIPLOMA MILL / DEGREE FACTORY DETECTED -- credentials from this institution require hard rejection."
68
+ rejected_name = transcript.get("institution_name") if transcript_blacklisted else profile.get("name", "Unknown")
69
+ return AuditResolution(
70
+ verdict="REJECTED - DIPLOMA MILL / DEGREE FACTORY",
71
+ risk_score=100.0,
72
+ reasoning_steps=[
73
+ *reasoning_steps,
74
+ f"Result 0: HARD REJECTION. '{rejected_name}' is flagged by the fraud registry.",
75
+ "No approval is issued because the issuing entity is disqualified.",
76
+ ],
77
+ mcp_trace=call.trace_id,
78
+ warning=warning_msg,
79
+ )
80
+ reasoning_steps.append("Result 0: No exact blacklist or alias match found.")
81
+
82
+ reasoning_steps.append("Step 1: Mapping graduation window against institutional lifecycle.")
83
+ grad_year = int(transcript.get("graduation_year") or 0)
84
+ est_year = profile.get("established_year")
85
+ if grad_year > 0 and est_year and grad_year < int(est_year):
86
+ anomalies.append(("CRITICAL: Graduation predates the institution founding year.", 55.0))
87
+ reasoning_steps.append("Result 1: Temporal violation found.")
88
+ elif est_year:
89
+ reasoning_steps.append("Result 1: Timeline is internally consistent.")
90
+ else:
91
+ warnings.append("Founding year unavailable; temporal validation is incomplete.")
92
+ reasoning_steps.append("Result 1: Founding year unavailable; timeline needs review.")
93
+
94
+ reasoning_steps.append("Step 2: Evaluating registry evidence without granting automatic approval.")
95
+ has_ror_id = bool(profile.get("ror_id"))
96
+ source = profile.get("source", "none")
97
+ match_confidence = float(profile.get("match_confidence") or 0.0)
98
+ if has_ror_id and profile_status == "active":
99
+ reasoning_steps.append("Result 2: Active ROR presence found as supporting institution-existence evidence.")
100
+ elif has_ror_id:
101
+ anomalies.append((f"WARNING: ROR status is '{profile_status}', not active.", 30.0))
102
+ reasoning_steps.append("Result 2: Registry presence found, but status requires review.")
103
+ else:
104
+ anomalies.append(("WARNING: No verified registry identifier was resolved.", 35.0))
105
+ reasoning_steps.append("Result 2: No ROR identifier available.")
106
+
107
+ if source in {"ror", "local_index"} and 0 < match_confidence < 0.80:
108
+ anomalies.append(("WARNING: Institution match confidence is below the production threshold.", 25.0))
109
+ reasoning_steps.append("Result 2b: Match confidence is low and should be manually reviewed.")
110
+
111
+ reasoning_steps.append("Step 3: Checking credential-authenticity evidence.")
112
+ if not transcript.get("credential_id") and not transcript.get("signature_verified"):
113
+ anomalies.append(("WARNING: No credential ID or cryptographic issuer signature was verified.", 35.0))
114
+ reasoning_steps.append("Result 3: Credential authenticity remains unproven.")
115
+ else:
116
+ reasoning_steps.append("Result 3: Credential-level evidence is present.")
117
+
118
+ risk_score = min(100.0, sum(weight for _, weight in anomalies))
119
+ if risk_score >= 85:
120
+ verdict = "REJECTED"
121
+ elif risk_score > 0 or warnings:
122
+ verdict = "NEEDS_REVIEW"
123
+ else:
124
+ verdict = "APPROVED"
125
+
126
+ return AuditResolution(
127
+ verdict=verdict,
128
+ risk_score=risk_score,
129
+ reasoning_steps=[*reasoning_steps, *[item for item, _ in anomalies], *warnings],
130
+ mcp_trace=call.trace_id,
131
+ warning="; ".join(warnings),
132
+ )
index.html CHANGED
@@ -1,487 +1,546 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Aegis-Graph | Sovereign Academic Audit Protocol | ACLAS College</title>
7
- <meta name="description" content="Aegis-Graph is the world's first sovereign academic audit network using Agentic GraphRAG and Multi-Agent Intelligence to verify institutional credentials. Developed by the Atlanta College of Liberal Arts and Sciences (ACLAS).">
8
- <meta name="keywords" content="Aegis-Graph, ACLAS College, Atlanta College of Liberal Arts and Sciences, Sovereign Academic Audit, Agentic GraphRAG, AI Fraud Detection, Academic Integrity Protocol, MCP Protocol">
9
- <meta name="author" content="Atlanta College of Liberal Arts and Sciences (ACLAS)">
10
-
11
- <!-- Geo / Local SEO -->
12
- <meta name="geo.region" content="US-GA">
13
- <meta name="geo.placename" content="Atlanta">
14
-
15
- <!-- OpenGraph / Social SEO -->
16
- <meta property="og:title" content="Aegis-Graph: Sovereign Academic Audit Protocol">
17
- <meta property="og:description" content="Securing global academic integrity through decentralized AI reasoning and institutional graph consensus.">
18
- <meta property="og:url" content="https://aclascollege.github.io/aegis-graph/">
19
- <meta property="og:site_name" content="Aegis-Graph Sovereign Network">
20
- <meta property="og:type" content="website">
21
-
22
- <!-- Structured Data for AEO (JSON-LD) -->
23
- <script type="application/ld+json">
24
- {
25
- "@context": "https://schema.org",
26
- "@type": "SoftwareApplication",
27
- "name": "Aegis-Graph",
28
- "operatingSystem": "Web, Python, Docker",
29
- "applicationCategory": "AcademicSecurityApplication",
30
- "description": "Decentralized protocol for sovereign academic auditing and credential verification using Multi-Agent Reasoning Swarms.",
31
- "author": {
32
- "@type": "EducationalOrganization",
33
- "name": "Atlanta College of Liberal Arts and Sciences",
34
- "alternateName": "ACLAS College",
35
- "url": "https://aclas.college/"
36
- },
37
- "publisher": {
38
- "@type": "EducationalOrganization",
39
- "name": "Atlanta College of Liberal Arts and Sciences"
40
- },
41
- "offers": {
42
- "@type": "Offer",
43
- "price": "0",
44
- "priceCurrency": "USD"
45
- }
46
- }
47
- </script>
48
-
49
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
50
- <style>
51
- :root {
52
- --bg: #010204;
53
- --card: rgba(13, 17, 23, 0.7);
54
- --border: rgba(255, 255, 255, 0.08);
55
- --accent: #00ffaa;
56
- --accent-glow: rgba(0, 255, 170, 0.2);
57
- --text: #ffffff;
58
- --dim: #8b949e;
59
- --glass: blur(12px) saturate(180%);
60
- }
61
- * { margin: 0; padding: 0; box-sizing: border-box; -webkit-font-smoothing: antialiased; }
62
- body {
63
- background: var(--bg);
64
- color: var(--text);
65
- font-family: 'Inter', sans-serif;
66
- overflow: hidden;
67
- height: 100vh;
68
- display: flex;
69
- flex-direction: column;
70
- background-image:
71
- radial-gradient(circle at 50% -20%, rgba(0, 255, 170, 0.05), transparent 50%),
72
- radial-gradient(circle at 0% 100%, rgba(0, 100, 255, 0.03), transparent 40%);
73
- }
74
- header {
75
- display: flex;
76
- justify-content: space-between;
77
- align-items: center;
78
- padding: 18px 40px;
79
- border-bottom: 1px solid var(--border);
80
- backdrop-filter: var(--glass);
81
- z-index: 1000;
82
- }
83
- .logo-group { display: flex; align-items: center; gap: 12px; }
84
- .logo-group img { height: 24px; }
85
- .logo-group h1 { font-family: 'Outfit'; font-size: 18px; letter-spacing: 3px; font-weight: 800; text-transform: uppercase; }
86
-
87
- .header-tools { display: flex; align-items: center; gap: 24px; }
88
- .header-icons { display: flex; gap: 20px; border-right: 1px solid var(--border); padding-right: 24px; }
89
- .icon-link { color: var(--text); opacity: 0.6; transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1); }
90
- .icon-link:hover { opacity: 1; color: var(--accent); transform: translateY(-3px); }
91
-
92
- .container {
93
- display: grid;
94
- grid-template-columns: 300px 1fr 340px;
95
- gap: 20px;
96
- padding: 24px;
97
- flex: 1;
98
- overflow: hidden;
99
- }
100
- .bento {
101
- background: var(--card);
102
- border: 1px solid var(--border);
103
- border-radius: 16px;
104
- padding: 24px;
105
- display: flex;
106
- flex-direction: column;
107
- overflow: hidden;
108
- backdrop-filter: var(--glass);
109
- transition: 0.3s;
110
- }
111
- .bento:hover { border-color: rgba(0, 255, 170, 0.3); }
112
-
113
- .label {
114
- font-size: 10px;
115
- color: var(--dim);
116
- text-transform: uppercase;
117
- letter-spacing: 2px;
118
- margin-bottom: 16px;
119
- font-weight: 700;
120
- display: flex;
121
- align-items: center;
122
- gap: 10px;
123
- }
124
- .label::after { content: ''; flex: 1; height: 1px; background: var(--border); }
125
-
126
- .agent-card {
127
- background: rgba(255, 255, 255, 0.03);
128
- border: 1px solid var(--border);
129
- padding: 16px;
130
- border-radius: 12px;
131
- margin-bottom: 12px;
132
- transition: 0.3s;
133
- }
134
- .agent-card:hover { background: rgba(255, 255, 255, 0.05); }
135
-
136
- .energy-bar { height: 3px; background: rgba(255, 255, 255, 0.05); margin-top: 10px; overflow: hidden; border-radius: 2px; }
137
- .energy-fill { height: 100%; background: var(--accent); width: 0%; transition: width 1s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 0 10px var(--accent); }
138
-
139
- .drop-zone {
140
- width: 100%;
141
- height: 100%;
142
- border: 2px dashed var(--border);
143
- border-radius: 20px;
144
- display: flex;
145
- flex-direction: column;
146
- align-items: center;
147
- justify-content: center;
148
- cursor: pointer;
149
- transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
150
- background: rgba(255, 255, 255, 0.01);
151
- }
152
- .drop-zone:hover { border-color: var(--accent); background: rgba(0, 255, 170, 0.03); transform: scale(0.99); }
153
-
154
- .terminal {
155
- background: rgba(0, 0, 0, 0.4);
156
- border-radius: 12px;
157
- padding: 20px;
158
- font-family: 'JetBrains Mono', monospace;
159
- font-size: 11px;
160
- flex: 1;
161
- overflow-y: auto;
162
- line-height: 1.7;
163
- color: var(--dim);
164
- border: 1px solid var(--border);
165
- }
166
- .terminal::-webkit-scrollbar { width: 4px; }
167
- .terminal::-webkit-scrollbar-thumb { background: var(--border); border-radius: 10px; }
168
-
169
- .btn-main {
170
- background: var(--accent);
171
- color: #000;
172
- border: none;
173
- padding: 14px 28px;
174
- border-radius: 8px;
175
- font-weight: 700;
176
- font-size: 13px;
177
- cursor: pointer;
178
- font-family: 'Outfit';
179
- letter-spacing: 1px;
180
- transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
181
- }
182
- .btn-main:hover { transform: translateY(-2px); box-shadow: 0 8px 25px var(--accent-glow); filter: brightness(1.1); }
183
- .btn-main:active { transform: translateY(0); }
184
-
185
- .meta-item { background: rgba(255, 255, 255, 0.02); padding: 14px; border-radius: 10px; border: 1px solid var(--border); }
186
- .meta-val { font-size: 14px; font-weight: 700; color: var(--accent); margin-top: 6px; font-family: 'JetBrains Mono'; }
187
-
188
- .custom-dropdown {
189
- position: relative;
190
- cursor: pointer;
191
- font-size: 13px;
192
- font-weight: 600;
193
- min-width: 130px;
194
- z-index: 2000;
195
- }
196
- .dropdown-selected {
197
- padding: 8px 16px;
198
- background: rgba(255, 255, 255, 0.05);
199
- border: 1px solid var(--border);
200
- border-radius: 8px;
201
- display: flex;
202
- justify-content: space-between;
203
- align-items: center;
204
- transition: 0.3s;
205
- }
206
- .dropdown-selected:hover {
207
- background: rgba(255, 255, 255, 0.08);
208
- border-color: rgba(255, 255, 255, 0.2);
209
- }
210
- .dropdown-options {
211
- position: absolute;
212
- top: calc(100% + 8px);
213
- right: 0;
214
- background: #0d1117;
215
- border: 1px solid var(--border);
216
- border-radius: 12px;
217
- overflow: hidden;
218
- display: none;
219
- flex-direction: column;
220
- min-width: 100%;
221
- box-shadow: 0 10px 30px rgba(0,0,0,0.5);
222
- backdrop-filter: var(--glass);
223
- }
224
- .custom-dropdown.open .dropdown-options {
225
- display: flex;
226
- animation: dropdownFade 0.2s ease-out;
227
- }
228
- @keyframes dropdownFade {
229
- from { opacity: 0; transform: translateY(-10px); }
230
- to { opacity: 1; transform: translateY(0); }
231
- }
232
- .dropdown-option {
233
- padding: 10px 20px;
234
- transition: 0.3s;
235
- white-space: nowrap;
236
- }
237
- .dropdown-option:hover {
238
- background: var(--accent);
239
- color: #000;
240
- }
241
-
242
- .modal {
243
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
244
- background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(20px);
245
- display: none; justify-content: center; align-items: center; z-index: 3000;
246
- }
247
- .report {
248
- background: #ffffff; color: #000; width: 540px; padding: 60px;
249
- border-radius: 24px; box-shadow: 0 30px 60px rgba(0,0,0,0.5);
250
- animation: modalPop 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
251
- }
252
- @keyframes modalPop { from { transform: scale(0.9) translateY(20px); opacity: 0; } to { transform: scale(1) translateY(0); opacity: 1; } }
253
- </style>
254
- </head>
255
- <body>
256
- <header>
257
- <div class="logo-group">
258
- <img src="assets/logo-new.png" alt="Logo">
259
- <h1>AEGIS-GRAPH <span style="font-size: 9px; color:var(--accent); opacity: 0.8;">SOVEREIGN_NODE v2.15</span></h1>
260
- </div>
261
- <div class="header-tools">
262
- <div class="header-icons">
263
- <a href="https://aclas.college" target="_blank" class="icon-link" title="Institution">
264
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18M3 10h18M5 10V7a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v3M4 10v11M20 10v11M10 14v4M14 14v4"/></svg>
265
- </a>
266
- <a href="https://docs.aclas.college/aegis-graph" target="_blank" class="icon-link" title="GitBook Documentation">
267
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20M4 19.5A2.5 2.5 0 0 0 6.5 22H20M4 19.5V3A2.5 2.5 0 0 1 6.5 0.5H20v16.5H6.5a2.5 2.5 0 0 0-2.5 2.5z"/></svg>
268
- </a>
269
- <a href="https://github.com/aclascollege/aegis-graph" target="_blank" class="icon-link" title="GitHub Repository">
270
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>
271
- </a>
272
- </div>
273
- <div class="custom-dropdown" id="lang-dropdown">
274
- <div class="dropdown-selected" id="lang-current">English <span style="font-size: 8px;">▼</span></div>
275
- <div class="dropdown-options">
276
- <div class="dropdown-option" data-value="en">English</div>
277
- <div class="dropdown-option" data-value="cn">简体中文</div>
278
- <div class="dropdown-option" data-value="es">Español</div>
279
- <div class="dropdown-option" data-value="fr">Français</div>
280
- <div class="dropdown-option" data-value="de">Deutsch</div>
281
- <div class="dropdown-option" data-value="jp">日本語</div>
282
- <div class="dropdown-option" data-value="kr">한국어</div>
283
- <div class="dropdown-option" data-value="pt">Português</div>
284
- </div>
285
- </div>
286
- <button class="btn-main" id="audit-btn" data-i18n="btn_audit">START AUDIT</button>
287
- </div>
288
- </header>
289
-
290
- <div class="container">
291
- <div class="bento" style="grid-row: span 2;">
292
- <p class="label" data-i18n="label_agents">System Swarm</p>
293
- <div class="agent-card"><div style="display:flex; justify-content:space-between; font-size:12px; font-weight: 600;"><span data-i18n="agent_v">Vision Forensics</span><span id="v-status" style="color:var(--dim)">IDLE</span></div><div class="energy-bar"><div class="energy-fill" id="v-fill"></div></div></div>
294
- <div class="agent-card"><div style="display:flex; justify-content:space-between; font-size:12px; font-weight: 600;"><span data-i18n="agent_g">Graph Navigator</span><span id="g-status" style="color:var(--dim)">IDLE</span></div><div class="energy-bar"><div class="energy-fill" id="g-fill"></div></div></div>
295
- <div class="agent-card"><div style="display:flex; justify-content:space-between; font-size:12px; font-weight: 600;"><span data-i18n="agent_l">Logic Auditor</span><span id="l-status" style="color:var(--dim)">IDLE</span></div><div class="energy-bar"><div class="energy-fill" id="l-fill"></div></div></div>
296
- <div style="margin-top: auto; padding: 20px; background: rgba(0, 255, 170, 0.03); border-radius: 12px; border: 1px solid var(--border);">
297
- <p class="label" data-i18n="label_node_title" style="margin-bottom: 8px;">Active Node</p>
298
- <p style="font-size: 13px; font-weight: 800; color:var(--accent); font-family: 'Outfit';">AEGIS_SOV_7822</p>
299
- <p style="font-size: 11px; color:var(--dim); line-height: 1.5; margin-top: 4px;">Maintained by the <a href="https://aclas.college" style="color:var(--accent); text-decoration: underline;">Atlanta College of Liberal Arts and Sciences</a>. Global sovereign research partner.</p>
300
- <a href="https://github.com/aclascollege/aegis-graph" target="_blank" style="font-size: 10px; color: var(--accent); text-decoration: none; margin-top: 12px; display: inline-block; font-weight: 700; letter-spacing: 0.5px;">PROTOCOL SOURCE ↗</a>
301
- </div>
302
- </div>
303
- <div class="bento"><p class="label" data-i18n="label_hub">Evidence Processing</p><div class="center-hub" style="height: 100%;"><div class="drop-zone" id="drop-zone"><div style="font-size: 32px; color: var(--accent); margin-bottom: 12px;">✧</div><p style="font-size: 15px; font-weight: 700; font-family: 'Outfit'; letter-spacing: 1px;" data-i18n="drop_text">INGEST CREDENTIAL</p><p style="font-size: 10px; color: var(--dim); margin-top: 4px;">PDF, JPG, PNG SUPPORTED</p><p style="font-size: 9px; color: #ffcc66; margin-top: 8px; max-width: 260px; line-height: 1.4;">DEMO MODE: local preview only. Final credential verification requires a server-signed audit.</p><input type="file" id="f-input" accept=".pdf,image/png,image/jpeg" style="display: none"></div></div></div>
304
- <div class="bento" style="grid-row: span 2;"><p class="label" data-i18n="label_telemetry">Live Telemetry</p><div class="terminal" id="console"><div>[SYSTEM] Sovereign Node Active. Waiting for ingestion...</div></div></div>
305
- <div class="bento">
306
- <p class="label" data-i18n="label_metadata">Sovereign Analytics</p>
307
- <div class="meta-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
308
- <div class="meta-item"><span class="label" style="margin-bottom:0; font-size: 8px;" data-i18n="meta_issuer">Issuer</span><div class="meta-val" id="m-issuer">--</div></div>
309
- <div class="meta-item"><span class="label" style="margin-bottom:0; font-size: 8px;" data-i18n="meta_status">Status</span><div class="meta-val" id="m-status">--</div></div>
310
- </div>
311
- </div>
312
- </div>
313
- <div class="modal" id="modal"><div class="report" id="rep-body"></div></div>
314
-
315
- <script>
316
- const i18n = {
317
- en: { btn_audit: "START AUDIT", label_agents: "System Agents", agent_v: "Vision Forensics", agent_g: "Graph Navigator", agent_l: "Logic Auditor", label_node_title: "Institutional Node", label_hub: "Evidence Processing", drop_text: "Ingest Credential", label_telemetry: "Live Telemetry", label_metadata: "Metadata Analytics", meta_issuer: "Issuer", meta_status: "Status", verdict_ok: "SERVER AUDIT REQUIRED", verdict_no: "AUDIT REJECTED" },
318
- cn: { btn_audit: "开始审计", label_agents: "系统代理", agent_v: "视觉法证", agent_g: "图谱导航", agent_l: "逻辑审计", label_node_title: "主权节点", label_hub: "证据处理中心", drop_text: "载入凭证", label_telemetry: "实时遥测", label_metadata: "元数据分析", meta_issuer: "签发机构", meta_status: "状态", verdict_ok: "需要服务端审计", verdict_no: "审计拒绝" },
319
- es: { btn_audit: "INICIAR AUDITORÍA", label_agents: "Agentes de Sistema", agent_v: "Forense Visual", agent_g: "Navegador de Grafos", agent_l: "Auditor Lógico", label_node_title: "Nodo Institucional", label_hub: "Procesamiento", drop_text: "Ingresar Credencial", label_telemetry: "Telemetría", label_metadata: "Metadatos", meta_issuer: "Emisor", meta_status: "Estado", verdict_ok: "AUDITORÍA REQUERIDA", verdict_no: "RECHAZADO" },
320
- fr: { btn_audit: "LANCER L'AUDIT", label_agents: "Agents Système", agent_v: "Forensique Visuelle", agent_g: "Navigateur", agent_l: "Auditeur Logique", label_node_title: "Nœud Institutionnel", label_hub: "Traitement", drop_text: "Ingérer", label_telemetry: "Télémétrie", label_metadata: "Métadonnées", meta_issuer: "Émetteur", meta_status: "Statut", verdict_ok: "AUDIT REQUIS", verdict_no: "REJETÉ" },
321
- de: { btn_audit: "AUDIT STARTEN", label_agents: "System-Agenten", agent_v: "Visuelle Forensik", agent_g: "Graph-Navigator", agent_l: "Logik-Auditor", label_node_title: "Institutioneller Knoten", label_hub: "Verarbeitung", drop_text: "Nachweis Einreichen", label_telemetry: "Telemetrie", label_metadata: "Metadaten", meta_issuer: "Aussteller", meta_status: "Status", verdict_ok: "SERVER-AUDIT ERFORDERLICH", verdict_no: "ABGELEHNT" },
322
- jp: { btn_audit: "監査開始", label_agents: "システムエージェント", agent_v: "視覚法医学", agent_g: "グラフナビ", agent_l: "ロジック監査", label_node_title: "機関ノード", label_hub: "証拠処理", drop_text: "資格情報を入れる", label_telemetry: "テレメトリ", label_metadata: "メタデータ", meta_issuer: "発行者", meta_status: "ステータス", verdict_ok: "サーバー監査が必要", verdict_no: "拒否" },
323
- kr: { btn_audit: "감사 시작", label_agents: "시스템 에이전트", agent_v: "시각적 법의학", agent_g: "그래프 탐색", agent_l: "논리 감사", label_node_title: "기관 노드", label_hub: "증거 처리", drop_text: "자격 증명 제출", label_telemetry: "원격 측정", label_metadata: "메타데이터", meta_issuer: "발행자", meta_status: "상태", verdict_ok: "서버 감사 필요", verdict_no: "거부" },
324
- pt: { btn_audit: "INICIAR AUDITORIA", label_agents: "Agentes de Sistema", agent_v: "Forense Visual", agent_g: "Navegador", agent_l: "Auditor Lógico", label_node_title: "Nó Institucional", label_hub: "Processamento", drop_text: "Inserir Credencial", label_telemetry: "Telemetria", label_metadata: "Metadados", meta_issuer: "Emissor", meta_status: "Estado", verdict_ok: "AUDITORIA REQUERIDA", verdict_no: "REJEITADO" }
325
- };
326
-
327
- const dropdown = document.getElementById('lang-dropdown');
328
- const langCurrent = document.getElementById('lang-current');
329
- let currentLang = 'en';
330
-
331
- dropdown.onclick = (e) => {
332
- dropdown.classList.toggle('open');
333
- e.stopPropagation();
334
- };
335
-
336
- // Close dropdown when clicking outside
337
- window.addEventListener('click', (e) => {
338
- if (!dropdown.contains(e.target)) {
339
- dropdown.classList.remove('open');
340
- }
341
- });
342
-
343
- document.querySelectorAll('.dropdown-option').forEach(opt => {
344
- opt.onclick = (e) => {
345
- currentLang = opt.getAttribute('data-value');
346
- langCurrent.replaceChildren();
347
- langCurrent.appendChild(document.createTextNode(`${opt.innerText} `));
348
- const arrow = document.createElement('span');
349
- arrow.style.fontSize = '8px';
350
- arrow.innerText = '▼';
351
- langCurrent.appendChild(arrow);
352
- document.querySelectorAll('[data-i18n]').forEach(el => {
353
- const key = el.getAttribute('data-i18n');
354
- if(i18n[currentLang][key]) el.innerText = i18n[currentLang][key];
355
- });
356
- e.stopPropagation();
357
- dropdown.classList.remove('open');
358
- };
359
- });
360
-
361
- const btn = document.getElementById('audit-btn');
362
- const cons = document.getElementById('console');
363
- let file = null;
364
-
365
- function log(msg) {
366
- const d = document.createElement('div');
367
- const prompt = document.createElement('span');
368
- prompt.style.color = 'var(--accent)';
369
- prompt.innerText = '> ';
370
- d.appendChild(prompt);
371
- d.appendChild(document.createTextNode(msg));
372
- cons.appendChild(d); cons.scrollTop = cons.scrollHeight;
373
- }
374
-
375
- document.getElementById('f-input').onchange = (e) => {
376
- if(e.target.files.length) {
377
- file = e.target.files[0];
378
- const dropZone = document.getElementById('drop-zone');
379
- dropZone.replaceChildren();
380
-
381
- const check = document.createElement('div');
382
- check.style.color = 'var(--accent)';
383
- check.innerText = '';
384
- dropZone.appendChild(check);
385
-
386
- const name = document.createElement('p');
387
- name.style.fontSize = '10px';
388
- name.innerText = file.name;
389
- dropZone.appendChild(name);
390
-
391
- const note = document.createElement('p');
392
- note.style.fontSize = '9px';
393
- note.style.color = '#ffcc66';
394
- note.style.marginTop = '8px';
395
- note.innerText = 'Demo intake only; no browser-side approval will be issued.';
396
- dropZone.appendChild(note);
397
-
398
- log(`INGESTED: ${file.name}`);
399
- }
400
- };
401
- document.getElementById('drop-zone').onclick = () => document.getElementById('f-input').click();
402
-
403
- btn.onclick = async () => {
404
- if(!file) return;
405
- btn.disabled = true;
406
- log("INIT: Preparing local demo intake...");
407
-
408
- // Vision Agent
409
- document.getElementById('v-fill').style.width = "100%";
410
- log("VISION: Demo animation only; document bytes are not authenticated in-browser...");
411
- await new Promise(r => setTimeout(r, 800));
412
-
413
- // Graph Agent
414
- document.getElementById('g-fill').style.width = "100%";
415
- log("GRAPH: Skipping browser-side registry approval; server audit required...");
416
- await new Promise(r => setTimeout(r, 1500)); // Simulating a deeper search
417
-
418
- // Logic Agent
419
- document.getElementById('l-fill').style.width = "100%";
420
- log("LOGIC: Refusing automatic approval without signed server evidence...");
421
- await new Promise(r => setTimeout(r, 800));
422
-
423
- await finish();
424
- };
425
-
426
- async function finish() {
427
- let verdict = 'NEEDS_REVIEW';
428
- let reason = 'DEMO MODE: This browser-only page does not read or authenticate document contents. A professional credential decision requires OCR/forensics, issuer registry checks, revocation checks, and a server-signed audit certificate.';
429
- let status_code = 'DEMO_ONLY';
430
- let issuer = 'UNVERIFIED';
431
- let ror_id = '--';
432
-
433
- const allowedTypes = ['application/pdf', 'image/png', 'image/jpeg'];
434
- const fileTypeKnown = allowedTypes.includes(file.type);
435
- if (!fileTypeKnown) {
436
- verdict = 'REJECTED';
437
- status_code = 'UNSUPPORTED_TYPE';
438
- reason = 'Unsupported file type. Upload a PDF, PNG, or JPEG credential for a production server-side audit.';
439
- log('SYSTEM: REJECTED - Unsupported file type for audit intake.');
440
- } else {
441
- log('SYSTEM: DEMO ONLY - File content was not sent to a trusted audit service.');
442
- log('SYSTEM: NEEDS REVIEW - No browser-side approval is issued.');
443
- }
444
-
445
- const modal = document.getElementById('modal');
446
- modal.style.display = 'flex';
447
- const t = i18n[currentLang];
448
-
449
- document.getElementById('m-issuer').innerText = issuer;
450
- document.getElementById('m-status').innerText = status_code;
451
-
452
- const verdictTitle = verdict === 'REJECTED' ? t.verdict_no : 'SERVER AUDIT REQUIRED';
453
- const verdictColor = verdict === 'REJECTED' ? '#ff3366' : '#ffcc66';
454
-
455
- const report = document.getElementById('rep-body');
456
- report.replaceChildren();
457
-
458
- const title = document.createElement('h2');
459
- title.style.color = verdictColor;
460
- title.innerText = verdictTitle;
461
- report.appendChild(title);
462
-
463
- const summary = document.createElement('p');
464
- summary.style.margin = '20px 0';
465
- summary.style.lineHeight = '1.6';
466
- summary.innerText = reason;
467
- report.appendChild(summary);
468
-
469
- const details = document.createElement('div');
470
- details.style.borderTop = '1px solid #eee';
471
- details.style.paddingTop = '15px';
472
- details.style.fontSize = '11px';
473
- details.style.color = '#666';
474
- details.style.marginBottom = '20px';
475
- details.innerText = `ROR ID: ${ror_id}\nAudit Trace: LOCAL-DEMO-NOT-SIGNED`;
476
- report.appendChild(details);
477
-
478
- const done = document.createElement('button');
479
- done.className = 'btn-main';
480
- done.style.width = '100%';
481
- done.innerText = 'DONE';
482
- done.onclick = () => location.reload();
483
- report.appendChild(done);
484
- }
485
- </script>
486
- </body>
487
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Aegis-Graph | Sovereign Academic Audit Protocol | ACLAS College</title>
7
+ <meta name="description" content="Aegis-Graph is the world's first sovereign academic audit network using Agentic GraphRAG and Multi-Agent Intelligence to verify institutional credentials. Developed by the Atlanta College of Liberal Arts and Sciences (ACLAS).">
8
+ <meta name="keywords" content="Aegis-Graph, ACLAS College, Atlanta College of Liberal Arts and Sciences, Sovereign Academic Audit, Agentic GraphRAG, AI Fraud Detection, Academic Integrity Protocol, MCP Protocol">
9
+ <meta name="author" content="Atlanta College of Liberal Arts and Sciences (ACLAS)">
10
+
11
+ <!-- Geo / Local SEO -->
12
+ <meta name="geo.region" content="US-GA">
13
+ <meta name="geo.placename" content="Atlanta">
14
+
15
+ <!-- OpenGraph / Social SEO -->
16
+ <meta property="og:title" content="Aegis-Graph: Sovereign Academic Audit Protocol">
17
+ <meta property="og:description" content="Securing global academic integrity through decentralized AI reasoning and institutional graph consensus.">
18
+ <meta property="og:url" content="https://aclascollege.github.io/aegis-graph/">
19
+ <meta property="og:site_name" content="Aegis-Graph Sovereign Network">
20
+ <meta property="og:type" content="website">
21
+ <link rel="icon" href="assets/favicon.ico">
22
+
23
+ <!-- Structured Data for AEO (JSON-LD) -->
24
+ <script type="application/ld+json">
25
+ {
26
+ "@context": "https://schema.org",
27
+ "@type": "SoftwareApplication",
28
+ "name": "Aegis-Graph",
29
+ "operatingSystem": "Web, Python, Docker",
30
+ "applicationCategory": "AcademicSecurityApplication",
31
+ "description": "Decentralized protocol for sovereign academic auditing and credential verification using Multi-Agent Reasoning Swarms.",
32
+ "author": {
33
+ "@type": "EducationalOrganization",
34
+ "name": "Atlanta College of Liberal Arts and Sciences",
35
+ "alternateName": "ACLAS College",
36
+ "url": "https://aclas.college/"
37
+ },
38
+ "publisher": {
39
+ "@type": "EducationalOrganization",
40
+ "name": "Atlanta College of Liberal Arts and Sciences"
41
+ },
42
+ "offers": {
43
+ "@type": "Offer",
44
+ "price": "0",
45
+ "priceCurrency": "USD"
46
+ }
47
+ }
48
+ </script>
49
+
50
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
51
+ <style>
52
+ :root {
53
+ --bg: #010204;
54
+ --card: rgba(13, 17, 23, 0.7);
55
+ --border: rgba(255, 255, 255, 0.08);
56
+ --accent: #00ffaa;
57
+ --accent-glow: rgba(0, 255, 170, 0.2);
58
+ --text: #ffffff;
59
+ --dim: #8b949e;
60
+ --glass: blur(12px) saturate(180%);
61
+ }
62
+ * { margin: 0; padding: 0; box-sizing: border-box; -webkit-font-smoothing: antialiased; }
63
+ body {
64
+ background: var(--bg);
65
+ color: var(--text);
66
+ font-family: 'Inter', sans-serif;
67
+ overflow: hidden;
68
+ height: 100vh;
69
+ display: flex;
70
+ flex-direction: column;
71
+ background-image:
72
+ radial-gradient(circle at 50% -20%, rgba(0, 255, 170, 0.05), transparent 50%),
73
+ radial-gradient(circle at 0% 100%, rgba(0, 100, 255, 0.03), transparent 40%);
74
+ }
75
+ header {
76
+ display: flex;
77
+ justify-content: space-between;
78
+ align-items: center;
79
+ padding: 18px 40px;
80
+ border-bottom: 1px solid var(--border);
81
+ backdrop-filter: var(--glass);
82
+ z-index: 1000;
83
+ }
84
+ .logo-group { display: flex; align-items: center; gap: 12px; }
85
+ .logo-group img { height: 24px; }
86
+ .logo-group h1 { font-family: 'Outfit'; font-size: 18px; letter-spacing: 3px; font-weight: 800; text-transform: uppercase; }
87
+
88
+ .header-tools { display: flex; align-items: center; gap: 24px; }
89
+ .header-icons { display: flex; gap: 20px; border-right: 1px solid var(--border); padding-right: 24px; }
90
+ .icon-link { color: var(--text); opacity: 0.6; transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1); }
91
+ .icon-link:hover { opacity: 1; color: var(--accent); transform: translateY(-3px); }
92
+
93
+ .container {
94
+ display: grid;
95
+ grid-template-columns: 300px 1fr 340px;
96
+ gap: 20px;
97
+ padding: 24px;
98
+ flex: 1;
99
+ overflow: hidden;
100
+ }
101
+ .bento {
102
+ background: var(--card);
103
+ border: 1px solid var(--border);
104
+ border-radius: 16px;
105
+ padding: 24px;
106
+ display: flex;
107
+ flex-direction: column;
108
+ overflow: hidden;
109
+ backdrop-filter: var(--glass);
110
+ transition: 0.3s;
111
+ }
112
+ .bento:hover { border-color: rgba(0, 255, 170, 0.3); }
113
+
114
+ .label {
115
+ font-size: 10px;
116
+ color: var(--dim);
117
+ text-transform: uppercase;
118
+ letter-spacing: 2px;
119
+ margin-bottom: 16px;
120
+ font-weight: 700;
121
+ display: flex;
122
+ align-items: center;
123
+ gap: 10px;
124
+ }
125
+ .label::after { content: ''; flex: 1; height: 1px; background: var(--border); }
126
+
127
+ .agent-card {
128
+ background: rgba(255, 255, 255, 0.03);
129
+ border: 1px solid var(--border);
130
+ padding: 16px;
131
+ border-radius: 12px;
132
+ margin-bottom: 12px;
133
+ transition: 0.3s;
134
+ }
135
+ .agent-card:hover { background: rgba(255, 255, 255, 0.05); }
136
+
137
+ .energy-bar { height: 3px; background: rgba(255, 255, 255, 0.05); margin-top: 10px; overflow: hidden; border-radius: 2px; }
138
+ .energy-fill { height: 100%; background: var(--accent); width: 0%; transition: width 1s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 0 10px var(--accent); }
139
+
140
+ .drop-zone {
141
+ width: 100%;
142
+ height: 100%;
143
+ border: 2px dashed var(--border);
144
+ border-radius: 20px;
145
+ display: flex;
146
+ flex-direction: column;
147
+ align-items: center;
148
+ justify-content: center;
149
+ cursor: pointer;
150
+ transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
151
+ background: rgba(255, 255, 255, 0.01);
152
+ }
153
+ .drop-zone:hover { border-color: var(--accent); background: rgba(0, 255, 170, 0.03); transform: scale(0.99); }
154
+
155
+ .terminal {
156
+ background: rgba(0, 0, 0, 0.4);
157
+ border-radius: 12px;
158
+ padding: 20px;
159
+ font-family: 'JetBrains Mono', monospace;
160
+ font-size: 11px;
161
+ flex: 1;
162
+ overflow-y: auto;
163
+ line-height: 1.7;
164
+ color: var(--dim);
165
+ border: 1px solid var(--border);
166
+ }
167
+ .terminal::-webkit-scrollbar { width: 4px; }
168
+ .terminal::-webkit-scrollbar-thumb { background: var(--border); border-radius: 10px; }
169
+
170
+ .btn-main {
171
+ background: var(--accent);
172
+ color: #000;
173
+ border: none;
174
+ padding: 14px 28px;
175
+ border-radius: 8px;
176
+ font-weight: 700;
177
+ font-size: 13px;
178
+ cursor: pointer;
179
+ font-family: 'Outfit';
180
+ letter-spacing: 1px;
181
+ transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
182
+ }
183
+ .btn-main:hover { transform: translateY(-2px); box-shadow: 0 8px 25px var(--accent-glow); filter: brightness(1.1); }
184
+ .btn-main:active { transform: translateY(0); }
185
+
186
+ .meta-item { background: rgba(255, 255, 255, 0.02); padding: 14px; border-radius: 10px; border: 1px solid var(--border); }
187
+ .meta-val { font-size: 14px; font-weight: 700; color: var(--accent); margin-top: 6px; font-family: 'JetBrains Mono'; }
188
+
189
+ .custom-dropdown {
190
+ position: relative;
191
+ cursor: pointer;
192
+ font-size: 13px;
193
+ font-weight: 600;
194
+ min-width: 130px;
195
+ z-index: 2000;
196
+ }
197
+ .dropdown-selected {
198
+ padding: 8px 16px;
199
+ background: rgba(255, 255, 255, 0.05);
200
+ border: 1px solid var(--border);
201
+ border-radius: 8px;
202
+ display: flex;
203
+ justify-content: space-between;
204
+ align-items: center;
205
+ transition: 0.3s;
206
+ }
207
+ .dropdown-selected:hover {
208
+ background: rgba(255, 255, 255, 0.08);
209
+ border-color: rgba(255, 255, 255, 0.2);
210
+ }
211
+ .dropdown-options {
212
+ position: absolute;
213
+ top: calc(100% + 8px);
214
+ right: 0;
215
+ background: #0d1117;
216
+ border: 1px solid var(--border);
217
+ border-radius: 12px;
218
+ overflow: hidden;
219
+ display: none;
220
+ flex-direction: column;
221
+ min-width: 100%;
222
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
223
+ backdrop-filter: var(--glass);
224
+ }
225
+ .custom-dropdown.open .dropdown-options {
226
+ display: flex;
227
+ animation: dropdownFade 0.2s ease-out;
228
+ }
229
+ @keyframes dropdownFade {
230
+ from { opacity: 0; transform: translateY(-10px); }
231
+ to { opacity: 1; transform: translateY(0); }
232
+ }
233
+ .dropdown-option {
234
+ padding: 10px 20px;
235
+ transition: 0.3s;
236
+ white-space: nowrap;
237
+ }
238
+ .dropdown-option:hover {
239
+ background: var(--accent);
240
+ color: #000;
241
+ }
242
+
243
+ .modal {
244
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
245
+ background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(20px);
246
+ display: none; justify-content: center; align-items: center; z-index: 3000;
247
+ }
248
+ .report {
249
+ background: #ffffff; color: #000; width: 540px; padding: 60px;
250
+ border-radius: 24px; box-shadow: 0 30px 60px rgba(0,0,0,0.5);
251
+ animation: modalPop 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
252
+ }
253
+ @keyframes modalPop { from { transform: scale(0.9) translateY(20px); opacity: 0; } to { transform: scale(1) translateY(0); opacity: 1; } }
254
+
255
+ @media (max-width: 900px) {
256
+ body {
257
+ overflow-y: auto;
258
+ height: auto;
259
+ min-height: 100vh;
260
+ }
261
+ header {
262
+ flex-wrap: wrap;
263
+ gap: 14px;
264
+ padding: 16px;
265
+ }
266
+ .logo-group h1 {
267
+ font-size: 15px;
268
+ letter-spacing: 1px;
269
+ }
270
+ .header-tools {
271
+ width: 100%;
272
+ justify-content: space-between;
273
+ gap: 12px;
274
+ }
275
+ .header-icons {
276
+ gap: 14px;
277
+ padding-right: 12px;
278
+ }
279
+ .custom-dropdown {
280
+ min-width: 112px;
281
+ }
282
+ .btn-main {
283
+ padding: 12px 14px;
284
+ font-size: 12px;
285
+ white-space: nowrap;
286
+ }
287
+ .container {
288
+ display: flex;
289
+ flex-direction: column;
290
+ padding: 16px;
291
+ overflow: visible;
292
+ }
293
+ .bento {
294
+ min-height: 220px;
295
+ }
296
+ .terminal {
297
+ min-height: 220px;
298
+ }
299
+ .modal {
300
+ padding: 16px;
301
+ }
302
+ .report {
303
+ width: min(100%, 540px);
304
+ padding: 32px 24px;
305
+ border-radius: 16px;
306
+ }
307
+ }
308
+ </style>
309
+ </head>
310
+ <body>
311
+ <header>
312
+ <div class="logo-group">
313
+ <img src="assets/logo-new.png" alt="Logo">
314
+ <h1>AEGIS-GRAPH <span style="font-size: 9px; color:var(--accent); opacity: 0.8;">SOVEREIGN_NODE v2.15</span></h1>
315
+ </div>
316
+ <div class="header-tools">
317
+ <div class="header-icons">
318
+ <a href="https://aclas.college" target="_blank" class="icon-link" title="Institution">
319
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18M3 10h18M5 10V7a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v3M4 10v11M20 10v11M10 14v4M14 14v4"/></svg>
320
+ </a>
321
+ <a href="https://docs.aclas.college/aegis-graph" target="_blank" class="icon-link" title="GitBook Documentation">
322
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20M4 19.5A2.5 2.5 0 0 0 6.5 22H20M4 19.5V3A2.5 2.5 0 0 1 6.5 0.5H20v16.5H6.5a2.5 2.5 0 0 0-2.5 2.5z"/></svg>
323
+ </a>
324
+ <a href="https://github.com/aclascollege/aegis-graph" target="_blank" class="icon-link" title="GitHub Repository">
325
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>
326
+ </a>
327
+ </div>
328
+ <div class="custom-dropdown" id="lang-dropdown">
329
+ <div class="dropdown-selected" id="lang-current">English <span style="font-size: 8px;">▼</span></div>
330
+ <div class="dropdown-options">
331
+ <div class="dropdown-option" data-value="en">English</div>
332
+ <div class="dropdown-option" data-value="cn">简体中文</div>
333
+ <div class="dropdown-option" data-value="es">Español</div>
334
+ <div class="dropdown-option" data-value="fr">Français</div>
335
+ <div class="dropdown-option" data-value="de">Deutsch</div>
336
+ <div class="dropdown-option" data-value="jp">日本語</div>
337
+ <div class="dropdown-option" data-value="kr">한국어</div>
338
+ <div class="dropdown-option" data-value="pt">Português</div>
339
+ </div>
340
+ </div>
341
+ <button class="btn-main" id="audit-btn" data-i18n="btn_audit">START AUDIT</button>
342
+ </div>
343
+ </header>
344
+
345
+ <div class="container">
346
+ <div class="bento" style="grid-row: span 2;">
347
+ <p class="label" data-i18n="label_agents">System Swarm</p>
348
+ <div class="agent-card"><div style="display:flex; justify-content:space-between; font-size:12px; font-weight: 600;"><span data-i18n="agent_v">Vision Forensics</span><span id="v-status" style="color:var(--dim)">IDLE</span></div><div class="energy-bar"><div class="energy-fill" id="v-fill"></div></div></div>
349
+ <div class="agent-card"><div style="display:flex; justify-content:space-between; font-size:12px; font-weight: 600;"><span data-i18n="agent_g">Graph Navigator</span><span id="g-status" style="color:var(--dim)">IDLE</span></div><div class="energy-bar"><div class="energy-fill" id="g-fill"></div></div></div>
350
+ <div class="agent-card"><div style="display:flex; justify-content:space-between; font-size:12px; font-weight: 600;"><span data-i18n="agent_l">Logic Auditor</span><span id="l-status" style="color:var(--dim)">IDLE</span></div><div class="energy-bar"><div class="energy-fill" id="l-fill"></div></div></div>
351
+ <div style="margin-top: auto; padding: 20px; background: rgba(0, 255, 170, 0.03); border-radius: 12px; border: 1px solid var(--border);">
352
+ <p class="label" data-i18n="label_node_title" style="margin-bottom: 8px;">Active Node</p>
353
+ <p style="font-size: 13px; font-weight: 800; color:var(--accent); font-family: 'Outfit';">AEGIS_SOV_7822</p>
354
+ <p style="font-size: 11px; color:var(--dim); line-height: 1.5; margin-top: 4px;">Maintained by the <a href="https://aclas.college" style="color:var(--accent); text-decoration: underline;">Atlanta College of Liberal Arts and Sciences</a>. Global sovereign research partner.</p>
355
+ <a href="https://github.com/aclascollege/aegis-graph" target="_blank" style="font-size: 10px; color: var(--accent); text-decoration: none; margin-top: 12px; display: inline-block; font-weight: 700; letter-spacing: 0.5px;">PROTOCOL SOURCE ↗</a>
356
+ </div>
357
+ </div>
358
+ <div class="bento"><p class="label" data-i18n="label_hub">Evidence Processing</p><div class="center-hub" style="height: 100%;"><div class="drop-zone" id="drop-zone"><div style="font-size: 32px; color: var(--accent); margin-bottom: 12px;">✧</div><p style="font-size: 15px; font-weight: 700; font-family: 'Outfit'; letter-spacing: 1px;" data-i18n="drop_text">INGEST CREDENTIAL</p><p style="font-size: 10px; color: var(--dim); margin-top: 4px;">PDF, JPG, PNG SUPPORTED</p><p style="font-size: 9px; color: #ffcc66; margin-top: 8px; max-width: 260px; line-height: 1.4;">DEMO MODE: local preview only. Final credential verification requires a server-signed audit.</p><input type="file" id="f-input" accept=".pdf,image/png,image/jpeg" style="display: none"></div></div></div>
359
+ <div class="bento" style="grid-row: span 2;"><p class="label" data-i18n="label_telemetry">Live Telemetry</p><div class="terminal" id="console"><div>[SYSTEM] Sovereign Node Active. Waiting for ingestion...</div></div></div>
360
+ <div class="bento">
361
+ <p class="label" data-i18n="label_metadata">Sovereign Analytics</p>
362
+ <div class="meta-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
363
+ <div class="meta-item"><span class="label" style="margin-bottom:0; font-size: 8px;" data-i18n="meta_issuer">Issuer</span><div class="meta-val" id="m-issuer">--</div></div>
364
+ <div class="meta-item"><span class="label" style="margin-bottom:0; font-size: 8px;" data-i18n="meta_status">Status</span><div class="meta-val" id="m-status">--</div></div>
365
+ </div>
366
+ </div>
367
+ </div>
368
+ <div class="modal" id="modal"><div class="report" id="rep-body"></div></div>
369
+
370
+ <script>
371
+ const i18n = {
372
+ en: { btn_audit: "START AUDIT", label_agents: "System Agents", agent_v: "Vision Forensics", agent_g: "Graph Navigator", agent_l: "Logic Auditor", label_node_title: "Institutional Node", label_hub: "Evidence Processing", drop_text: "Ingest Credential", label_telemetry: "Live Telemetry", label_metadata: "Metadata Analytics", meta_issuer: "Issuer", meta_status: "Status", verdict_ok: "SERVER AUDIT REQUIRED", verdict_no: "AUDIT REJECTED" },
373
+ cn: { btn_audit: "开始审计", label_agents: "系统代理", agent_v: "视觉法证", agent_g: "图谱导航", agent_l: "逻辑审计", label_node_title: "主权节点", label_hub: "证据处理中心", drop_text: "载入凭证", label_telemetry: "实时遥测", label_metadata: "元数据分析", meta_issuer: "签发机构", meta_status: "状态", verdict_ok: "需要服务端审计", verdict_no: "审计拒绝" },
374
+ es: { btn_audit: "INICIAR AUDITORÍA", label_agents: "Agentes de Sistema", agent_v: "Forense Visual", agent_g: "Navegador de Grafos", agent_l: "Auditor Lógico", label_node_title: "Nodo Institucional", label_hub: "Procesamiento", drop_text: "Ingresar Credencial", label_telemetry: "Telemetría", label_metadata: "Metadatos", meta_issuer: "Emisor", meta_status: "Estado", verdict_ok: "AUDITORÍA REQUERIDA", verdict_no: "RECHAZADO" },
375
+ fr: { btn_audit: "LANCER L'AUDIT", label_agents: "Agents Système", agent_v: "Forensique Visuelle", agent_g: "Navigateur", agent_l: "Auditeur Logique", label_node_title: "Nœud Institutionnel", label_hub: "Traitement", drop_text: "Ingérer", label_telemetry: "Télémétrie", label_metadata: "Métadonnées", meta_issuer: "Émetteur", meta_status: "Statut", verdict_ok: "AUDIT REQUIS", verdict_no: "REJETÉ" },
376
+ de: { btn_audit: "AUDIT STARTEN", label_agents: "System-Agenten", agent_v: "Visuelle Forensik", agent_g: "Graph-Navigator", agent_l: "Logik-Auditor", label_node_title: "Institutioneller Knoten", label_hub: "Verarbeitung", drop_text: "Nachweis Einreichen", label_telemetry: "Telemetrie", label_metadata: "Metadaten", meta_issuer: "Aussteller", meta_status: "Status", verdict_ok: "SERVER-AUDIT ERFORDERLICH", verdict_no: "ABGELEHNT" },
377
+ jp: { btn_audit: "監査開始", label_agents: "システムエージェント", agent_v: "視覚法医学", agent_g: "グラフナビ", agent_l: "ロジック監査", label_node_title: "機関ノード", label_hub: "証拠処理", drop_text: "資格情報を入れる", label_telemetry: "テレメトリ", label_metadata: "メタデータ", meta_issuer: "発行者", meta_status: "ステータス", verdict_ok: "サーバー監査が必要", verdict_no: "拒否" },
378
+ kr: { btn_audit: "감사 시작", label_agents: "시스템 에이전트", agent_v: "시각적 법의학", agent_g: "그래프 탐색", agent_l: "논리 감사", label_node_title: "기관 노드", label_hub: "증거 처리", drop_text: "자격 증명 제출", label_telemetry: "원격 측정", label_metadata: "메타데이터", meta_issuer: "발행자", meta_status: "상태", verdict_ok: "서버 감사 필요", verdict_no: "거부" },
379
+ pt: { btn_audit: "INICIAR AUDITORIA", label_agents: "Agentes de Sistema", agent_v: "Forense Visual", agent_g: "Navegador", agent_l: "Auditor Lógico", label_node_title: "Nó Institucional", label_hub: "Processamento", drop_text: "Inserir Credencial", label_telemetry: "Telemetria", label_metadata: "Metadados", meta_issuer: "Emissor", meta_status: "Estado", verdict_ok: "AUDITORIA REQUERIDA", verdict_no: "REJEITADO" }
380
+ };
381
+
382
+ const dropdown = document.getElementById('lang-dropdown');
383
+ const langCurrent = document.getElementById('lang-current');
384
+ let currentLang = 'en';
385
+
386
+ dropdown.onclick = (e) => {
387
+ dropdown.classList.toggle('open');
388
+ e.stopPropagation();
389
+ };
390
+
391
+ // Close dropdown when clicking outside
392
+ window.addEventListener('click', (e) => {
393
+ if (!dropdown.contains(e.target)) {
394
+ dropdown.classList.remove('open');
395
+ }
396
+ });
397
+
398
+ document.querySelectorAll('.dropdown-option').forEach(opt => {
399
+ opt.onclick = (e) => {
400
+ currentLang = opt.getAttribute('data-value');
401
+ langCurrent.replaceChildren();
402
+ langCurrent.appendChild(document.createTextNode(`${opt.innerText} `));
403
+ const arrow = document.createElement('span');
404
+ arrow.style.fontSize = '8px';
405
+ arrow.innerText = '▼';
406
+ langCurrent.appendChild(arrow);
407
+ document.querySelectorAll('[data-i18n]').forEach(el => {
408
+ const key = el.getAttribute('data-i18n');
409
+ if(i18n[currentLang][key]) el.innerText = i18n[currentLang][key];
410
+ });
411
+ e.stopPropagation();
412
+ dropdown.classList.remove('open');
413
+ };
414
+ });
415
+
416
+ const btn = document.getElementById('audit-btn');
417
+ const cons = document.getElementById('console');
418
+ let file = null;
419
+
420
+ function log(msg) {
421
+ const d = document.createElement('div');
422
+ const prompt = document.createElement('span');
423
+ prompt.style.color = 'var(--accent)';
424
+ prompt.innerText = '> ';
425
+ d.appendChild(prompt);
426
+ d.appendChild(document.createTextNode(msg));
427
+ cons.appendChild(d); cons.scrollTop = cons.scrollHeight;
428
+ }
429
+
430
+ document.getElementById('f-input').onchange = (e) => {
431
+ if(e.target.files.length) {
432
+ file = e.target.files[0];
433
+ const dropZone = document.getElementById('drop-zone');
434
+ dropZone.replaceChildren();
435
+
436
+ const check = document.createElement('div');
437
+ check.style.color = 'var(--accent)';
438
+ check.innerText = '';
439
+ dropZone.appendChild(check);
440
+
441
+ const name = document.createElement('p');
442
+ name.style.fontSize = '10px';
443
+ name.innerText = file.name;
444
+ dropZone.appendChild(name);
445
+
446
+ const note = document.createElement('p');
447
+ note.style.fontSize = '9px';
448
+ note.style.color = '#ffcc66';
449
+ note.style.marginTop = '8px';
450
+ note.innerText = 'Demo intake only; no browser-side approval will be issued.';
451
+ dropZone.appendChild(note);
452
+
453
+ log(`INGESTED: ${file.name}`);
454
+ }
455
+ };
456
+ document.getElementById('drop-zone').onclick = () => document.getElementById('f-input').click();
457
+
458
+ btn.onclick = async () => {
459
+ if(!file) {
460
+ log("SYSTEM: Select a PDF, PNG, or JPEG credential before starting the demo audit.");
461
+ document.getElementById('drop-zone').style.borderColor = '#ffcc66';
462
+ return;
463
+ }
464
+ btn.disabled = true;
465
+ log("INIT: Preparing local demo intake...");
466
+
467
+ // Vision Agent
468
+ document.getElementById('v-fill').style.width = "100%";
469
+ log("VISION: Demo animation only; document bytes are not authenticated in-browser...");
470
+ await new Promise(r => setTimeout(r, 800));
471
+
472
+ // Graph Agent
473
+ document.getElementById('g-fill').style.width = "100%";
474
+ log("GRAPH: Skipping browser-side registry approval; server audit required...");
475
+ await new Promise(r => setTimeout(r, 1500)); // Simulating a deeper search
476
+
477
+ // Logic Agent
478
+ document.getElementById('l-fill').style.width = "100%";
479
+ log("LOGIC: Refusing automatic approval without signed server evidence...");
480
+ await new Promise(r => setTimeout(r, 800));
481
+
482
+ await finish();
483
+ };
484
+
485
+ async function finish() {
486
+ let verdict = 'NEEDS_REVIEW';
487
+ let reason = 'DEMO MODE: This browser-only page does not read or authenticate document contents. A professional credential decision requires OCR/forensics, issuer registry checks, revocation checks, and a server-signed audit certificate.';
488
+ let status_code = 'DEMO_ONLY';
489
+ let issuer = 'UNVERIFIED';
490
+ let ror_id = '--';
491
+
492
+ const allowedTypes = ['application/pdf', 'image/png', 'image/jpeg'];
493
+ const fileTypeKnown = allowedTypes.includes(file.type);
494
+ if (!fileTypeKnown) {
495
+ verdict = 'REJECTED';
496
+ status_code = 'UNSUPPORTED_TYPE';
497
+ reason = 'Unsupported file type. Upload a PDF, PNG, or JPEG credential for a production server-side audit.';
498
+ log('SYSTEM: REJECTED - Unsupported file type for audit intake.');
499
+ } else {
500
+ log('SYSTEM: DEMO ONLY - File content was not sent to a trusted audit service.');
501
+ log('SYSTEM: NEEDS REVIEW - No browser-side approval is issued.');
502
+ }
503
+
504
+ const modal = document.getElementById('modal');
505
+ modal.style.display = 'flex';
506
+ const t = i18n[currentLang];
507
+
508
+ document.getElementById('m-issuer').innerText = issuer;
509
+ document.getElementById('m-status').innerText = status_code;
510
+
511
+ const verdictTitle = verdict === 'REJECTED' ? t.verdict_no : 'SERVER AUDIT REQUIRED';
512
+ const verdictColor = verdict === 'REJECTED' ? '#ff3366' : '#ffcc66';
513
+
514
+ const report = document.getElementById('rep-body');
515
+ report.replaceChildren();
516
+
517
+ const title = document.createElement('h2');
518
+ title.style.color = verdictColor;
519
+ title.innerText = verdictTitle;
520
+ report.appendChild(title);
521
+
522
+ const summary = document.createElement('p');
523
+ summary.style.margin = '20px 0';
524
+ summary.style.lineHeight = '1.6';
525
+ summary.innerText = reason;
526
+ report.appendChild(summary);
527
+
528
+ const details = document.createElement('div');
529
+ details.style.borderTop = '1px solid #eee';
530
+ details.style.paddingTop = '15px';
531
+ details.style.fontSize = '11px';
532
+ details.style.color = '#666';
533
+ details.style.marginBottom = '20px';
534
+ details.innerText = `ROR ID: ${ror_id}\nAudit Trace: LOCAL-DEMO-NOT-SIGNED`;
535
+ report.appendChild(details);
536
+
537
+ const done = document.createElement('button');
538
+ done.className = 'btn-main';
539
+ done.style.width = '100%';
540
+ done.innerText = 'DONE';
541
+ done.onclick = () => location.reload();
542
+ report.appendChild(done);
543
+ }
544
+ </script>
545
+ </body>
546
+ </html>