@sanctuary-framework/mcp-server 0.5.4 → 0.5.6
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/dist/cli.cjs +1936 -1399
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1936 -1399
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1937 -1398
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -2
- package/dist/index.d.ts +97 -2
- package/dist/index.js +1935 -1399
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -48,7 +48,7 @@ function defaultConfig() {
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
disclosure: {
|
|
51
|
-
proof_system: "
|
|
51
|
+
proof_system: "schnorr-pedersen",
|
|
52
52
|
default_policy: "minimum-necessary"
|
|
53
53
|
},
|
|
54
54
|
reputation: {
|
|
@@ -162,7 +162,7 @@ function validateConfig(config) {
|
|
|
162
162
|
`Unimplemented config value: execution.environment = "${config.execution.environment}". Only ${[...implementedEnvironment].map((v) => `"${v}"`).join(", ")} are currently implemented. Using an unimplemented environment would silently degrade security.`
|
|
163
163
|
);
|
|
164
164
|
}
|
|
165
|
-
const implementedProofSystem = /* @__PURE__ */ new Set(["commitment-only"]);
|
|
165
|
+
const implementedProofSystem = /* @__PURE__ */ new Set(["schnorr-pedersen", "commitment-only"]);
|
|
166
166
|
if (!implementedProofSystem.has(config.disclosure.proof_system)) {
|
|
167
167
|
errors.push(
|
|
168
168
|
`Unimplemented config value: disclosure.proof_system = "${config.disclosure.proof_system}". Only ${[...implementedProofSystem].map((v) => `"${v}"`).join(", ")} is currently implemented. Using an unimplemented proof system would silently degrade security.`
|
|
@@ -867,6 +867,8 @@ tier3_always_allow:
|
|
|
867
867
|
- handshake_respond
|
|
868
868
|
- handshake_complete
|
|
869
869
|
- handshake_status
|
|
870
|
+
- handshake_exchange
|
|
871
|
+
- handshake_verify_attestation
|
|
870
872
|
- reputation_query_weighted
|
|
871
873
|
- federation_peers
|
|
872
874
|
- federation_trust_evaluate
|
|
@@ -968,6 +970,8 @@ var init_loader = __esm({
|
|
|
968
970
|
"handshake_respond",
|
|
969
971
|
"handshake_complete",
|
|
970
972
|
"handshake_status",
|
|
973
|
+
"handshake_exchange",
|
|
974
|
+
"handshake_verify_attestation",
|
|
971
975
|
"reputation_query_weighted",
|
|
972
976
|
"federation_peers",
|
|
973
977
|
"federation_trust_evaluate",
|
|
@@ -1162,174 +1166,255 @@ function generateLoginHTML(options) {
|
|
|
1162
1166
|
return `<!DOCTYPE html>
|
|
1163
1167
|
<html lang="en">
|
|
1164
1168
|
<head>
|
|
1165
|
-
<meta charset="
|
|
1166
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1167
|
-
<title>Sanctuary
|
|
1168
|
-
<
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1169
|
+
<meta charset="UTF-8">
|
|
1170
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1171
|
+
<title>Sanctuary Dashboard</title>
|
|
1172
|
+
<style>
|
|
1173
|
+
:root {
|
|
1174
|
+
--bg: #0d1117;
|
|
1175
|
+
--surface: #161b22;
|
|
1176
|
+
--border: #30363d;
|
|
1177
|
+
--text-primary: #e6edf3;
|
|
1178
|
+
--text-secondary: #8b949e;
|
|
1179
|
+
--green: #3fb950;
|
|
1180
|
+
--amber: #d29922;
|
|
1181
|
+
--red: #f85149;
|
|
1182
|
+
--blue: #58a6ff;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
* {
|
|
1186
|
+
margin: 0;
|
|
1187
|
+
padding: 0;
|
|
1188
|
+
box-sizing: border-box;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
body {
|
|
1192
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
1193
|
+
background-color: var(--bg);
|
|
1194
|
+
color: var(--text-primary);
|
|
1195
|
+
min-height: 100vh;
|
|
1196
|
+
display: flex;
|
|
1197
|
+
align-items: center;
|
|
1198
|
+
justify-content: center;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
.login-container {
|
|
1202
|
+
width: 100%;
|
|
1203
|
+
max-width: 400px;
|
|
1204
|
+
padding: 20px;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
.login-card {
|
|
1208
|
+
background-color: var(--surface);
|
|
1209
|
+
border: 1px solid var(--border);
|
|
1210
|
+
border-radius: 8px;
|
|
1211
|
+
padding: 40px 32px;
|
|
1212
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
.login-header {
|
|
1216
|
+
display: flex;
|
|
1217
|
+
align-items: center;
|
|
1218
|
+
gap: 12px;
|
|
1219
|
+
margin-bottom: 32px;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
.logo {
|
|
1223
|
+
font-size: 24px;
|
|
1224
|
+
font-weight: 700;
|
|
1225
|
+
color: var(--blue);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
.logo-text {
|
|
1229
|
+
display: flex;
|
|
1230
|
+
flex-direction: column;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
.logo-text .title {
|
|
1234
|
+
font-size: 18px;
|
|
1235
|
+
font-weight: 600;
|
|
1236
|
+
letter-spacing: -0.5px;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
.logo-text .version {
|
|
1240
|
+
font-size: 12px;
|
|
1241
|
+
color: var(--text-secondary);
|
|
1242
|
+
margin-top: 2px;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
.form-group {
|
|
1246
|
+
margin-bottom: 24px;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
label {
|
|
1250
|
+
display: block;
|
|
1251
|
+
font-size: 14px;
|
|
1252
|
+
font-weight: 500;
|
|
1253
|
+
margin-bottom: 8px;
|
|
1254
|
+
color: var(--text-primary);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
input[type="text"],
|
|
1258
|
+
input[type="password"] {
|
|
1259
|
+
width: 100%;
|
|
1260
|
+
padding: 10px 12px;
|
|
1261
|
+
background-color: var(--bg);
|
|
1262
|
+
border: 1px solid var(--border);
|
|
1263
|
+
border-radius: 6px;
|
|
1264
|
+
color: var(--text-primary);
|
|
1265
|
+
font-size: 14px;
|
|
1266
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1267
|
+
transition: border-color 0.2s;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
input[type="text"]:focus,
|
|
1271
|
+
input[type="password"]:focus {
|
|
1272
|
+
outline: none;
|
|
1273
|
+
border-color: var(--blue);
|
|
1274
|
+
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.1);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.error-message {
|
|
1278
|
+
display: none;
|
|
1279
|
+
background-color: rgba(248, 81, 73, 0.1);
|
|
1280
|
+
border: 1px solid var(--red);
|
|
1281
|
+
color: #ff9999;
|
|
1282
|
+
padding: 12px;
|
|
1283
|
+
border-radius: 6px;
|
|
1284
|
+
font-size: 13px;
|
|
1285
|
+
margin-bottom: 20px;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
.error-message.show {
|
|
1289
|
+
display: block;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
button {
|
|
1293
|
+
width: 100%;
|
|
1294
|
+
padding: 10px 16px;
|
|
1295
|
+
background-color: var(--blue);
|
|
1296
|
+
color: var(--bg);
|
|
1297
|
+
border: none;
|
|
1298
|
+
border-radius: 6px;
|
|
1299
|
+
font-size: 14px;
|
|
1300
|
+
font-weight: 600;
|
|
1301
|
+
cursor: pointer;
|
|
1302
|
+
transition: background-color 0.2s;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
button:hover {
|
|
1306
|
+
background-color: #79c0ff;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
button:active {
|
|
1310
|
+
background-color: #4184e4;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
button:disabled {
|
|
1314
|
+
background-color: var(--text-secondary);
|
|
1315
|
+
cursor: not-allowed;
|
|
1316
|
+
opacity: 0.5;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
.info-text {
|
|
1320
|
+
font-size: 12px;
|
|
1321
|
+
color: var(--text-secondary);
|
|
1322
|
+
margin-top: 16px;
|
|
1323
|
+
text-align: center;
|
|
1324
|
+
}
|
|
1325
|
+
</style>
|
|
1279
1326
|
</head>
|
|
1280
1327
|
<body>
|
|
1281
|
-
<div class="login-container">
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1328
|
+
<div class="login-container">
|
|
1329
|
+
<div class="login-card">
|
|
1330
|
+
<div class="login-header">
|
|
1331
|
+
<div class="logo">\u25C6</div>
|
|
1332
|
+
<div class="logo-text">
|
|
1333
|
+
<div class="title">SANCTUARY</div>
|
|
1334
|
+
<div class="version">v${options.serverVersion}</div>
|
|
1335
|
+
</div>
|
|
1336
|
+
</div>
|
|
1337
|
+
|
|
1338
|
+
<div id="error-message" class="error-message"></div>
|
|
1339
|
+
|
|
1340
|
+
<form id="login-form">
|
|
1341
|
+
<div class="form-group">
|
|
1342
|
+
<label for="auth-token">Bearer Token</label>
|
|
1343
|
+
<input
|
|
1344
|
+
type="text"
|
|
1345
|
+
id="auth-token"
|
|
1346
|
+
name="token"
|
|
1347
|
+
placeholder="Paste your session token..."
|
|
1348
|
+
autocomplete="off"
|
|
1349
|
+
spellcheck="false"
|
|
1350
|
+
required
|
|
1351
|
+
/>
|
|
1352
|
+
</div>
|
|
1353
|
+
|
|
1354
|
+
<button type="submit" id="login-button">Open Dashboard</button>
|
|
1355
|
+
</form>
|
|
1356
|
+
|
|
1357
|
+
<div class="info-text">
|
|
1358
|
+
Session tokens expire after 1 hour of inactivity
|
|
1359
|
+
</div>
|
|
1360
|
+
</div>
|
|
1294
1361
|
</div>
|
|
1295
|
-
|
|
1296
|
-
<script>
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1362
|
+
|
|
1363
|
+
<script>
|
|
1364
|
+
const loginForm = document.getElementById('login-form');
|
|
1365
|
+
const authTokenInput = document.getElementById('auth-token');
|
|
1366
|
+
const errorMessage = document.getElementById('error-message');
|
|
1367
|
+
const loginButton = document.getElementById('login-button');
|
|
1368
|
+
|
|
1369
|
+
loginForm.addEventListener('submit', async (e) => {
|
|
1370
|
+
e.preventDefault();
|
|
1371
|
+
const token = authTokenInput.value.trim();
|
|
1372
|
+
|
|
1373
|
+
if (!token) {
|
|
1374
|
+
showError('Token is required');
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
loginButton.disabled = true;
|
|
1379
|
+
loginButton.textContent = 'Verifying...';
|
|
1380
|
+
errorMessage.classList.remove('show');
|
|
1381
|
+
|
|
1382
|
+
try {
|
|
1383
|
+
const response = await fetch('/auth/session', {
|
|
1384
|
+
method: 'POST',
|
|
1385
|
+
headers: {
|
|
1386
|
+
'Content-Type': 'application/json',
|
|
1387
|
+
'Authorization': 'Bearer ' + token,
|
|
1388
|
+
},
|
|
1389
|
+
body: JSON.stringify({ token }),
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
if (response.ok) {
|
|
1393
|
+
const data = await response.json();
|
|
1394
|
+
sessionStorage.setItem('authToken', token);
|
|
1395
|
+
window.location.href = '/dashboard';
|
|
1396
|
+
} else if (response.status === 401) {
|
|
1397
|
+
showError('Invalid token. Please check and try again.');
|
|
1398
|
+
} else {
|
|
1399
|
+
showError('Authentication failed. Please try again.');
|
|
1400
|
+
}
|
|
1401
|
+
} catch (err) {
|
|
1402
|
+
showError('Connection error. Please check your network.');
|
|
1403
|
+
} finally {
|
|
1404
|
+
loginButton.disabled = false;
|
|
1405
|
+
loginButton.textContent = 'Open Dashboard';
|
|
1406
|
+
}
|
|
1310
1407
|
});
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
'; path=/; SameSite=Strict; max-age=' + maxAge;
|
|
1322
|
-
// Reload to enter the dashboard
|
|
1323
|
-
window.location.reload();
|
|
1324
|
-
} catch (err) {
|
|
1325
|
-
errEl.textContent = err.message || 'Authentication failed. Check your token.';
|
|
1326
|
-
errEl.style.display = 'block';
|
|
1327
|
-
btn.disabled = false;
|
|
1328
|
-
btn.textContent = 'Open Dashboard';
|
|
1329
|
-
}
|
|
1330
|
-
return false;
|
|
1331
|
-
}
|
|
1332
|
-
</script>
|
|
1408
|
+
|
|
1409
|
+
function showError(message) {
|
|
1410
|
+
errorMessage.textContent = message;
|
|
1411
|
+
errorMessage.classList.add('show');
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
authTokenInput.addEventListener('input', () => {
|
|
1415
|
+
errorMessage.classList.remove('show');
|
|
1416
|
+
});
|
|
1417
|
+
</script>
|
|
1333
1418
|
</body>
|
|
1334
1419
|
</html>`;
|
|
1335
1420
|
}
|
|
@@ -1337,1412 +1422,1648 @@ function generateDashboardHTML(options) {
|
|
|
1337
1422
|
return `<!DOCTYPE html>
|
|
1338
1423
|
<html lang="en">
|
|
1339
1424
|
<head>
|
|
1340
|
-
<meta charset="
|
|
1341
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1342
|
-
<title>Sanctuary
|
|
1343
|
-
<
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1425
|
+
<meta charset="UTF-8">
|
|
1426
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1427
|
+
<title>Sanctuary Dashboard</title>
|
|
1428
|
+
<style>
|
|
1429
|
+
:root {
|
|
1430
|
+
--bg: #0d1117;
|
|
1431
|
+
--surface: #161b22;
|
|
1432
|
+
--border: #30363d;
|
|
1433
|
+
--text-primary: #e6edf3;
|
|
1434
|
+
--text-secondary: #8b949e;
|
|
1435
|
+
--green: #3fb950;
|
|
1436
|
+
--amber: #d29922;
|
|
1437
|
+
--red: #f85149;
|
|
1438
|
+
--blue: #58a6ff;
|
|
1439
|
+
--success: #3fb950;
|
|
1440
|
+
--warning: #d29922;
|
|
1441
|
+
--error: #f85149;
|
|
1442
|
+
--muted: #21262d;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
* {
|
|
1446
|
+
margin: 0;
|
|
1447
|
+
padding: 0;
|
|
1448
|
+
box-sizing: border-box;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
html, body {
|
|
1452
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
1453
|
+
background-color: var(--bg);
|
|
1454
|
+
color: var(--text-primary);
|
|
1455
|
+
height: 100%;
|
|
1456
|
+
overflow: hidden;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
body {
|
|
1460
|
+
display: flex;
|
|
1461
|
+
flex-direction: column;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/* Status Bar */
|
|
1465
|
+
.status-bar {
|
|
1466
|
+
position: fixed;
|
|
1467
|
+
top: 0;
|
|
1468
|
+
left: 0;
|
|
1469
|
+
right: 0;
|
|
1470
|
+
height: 56px;
|
|
1471
|
+
background-color: var(--surface);
|
|
1472
|
+
border-bottom: 1px solid var(--border);
|
|
1473
|
+
display: flex;
|
|
1474
|
+
align-items: center;
|
|
1475
|
+
padding: 0 24px;
|
|
1476
|
+
gap: 24px;
|
|
1477
|
+
z-index: 100;
|
|
1478
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
.status-bar-left {
|
|
1482
|
+
display: flex;
|
|
1483
|
+
align-items: center;
|
|
1484
|
+
gap: 12px;
|
|
1485
|
+
flex: 0 0 auto;
|
|
1486
|
+
}
|
|
1365
1487
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
body {
|
|
1373
|
-
font-family: var(--sans);
|
|
1374
|
-
background: var(--bg);
|
|
1375
|
-
color: var(--text-primary);
|
|
1376
|
-
display: flex;
|
|
1377
|
-
flex-direction: column;
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
/* \u2500\u2500 Top Status Bar (fixed) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
1488
|
+
.logo-icon {
|
|
1489
|
+
font-size: 20px;
|
|
1490
|
+
color: var(--blue);
|
|
1491
|
+
font-weight: 700;
|
|
1492
|
+
}
|
|
1381
1493
|
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
right: 0;
|
|
1387
|
-
height: 56px;
|
|
1388
|
-
background: var(--surface);
|
|
1389
|
-
border-bottom: 1px solid var(--border);
|
|
1390
|
-
display: flex;
|
|
1391
|
-
align-items: center;
|
|
1392
|
-
padding: 0 20px;
|
|
1393
|
-
gap: 24px;
|
|
1394
|
-
z-index: 1000;
|
|
1395
|
-
}
|
|
1494
|
+
.logo-info {
|
|
1495
|
+
display: flex;
|
|
1496
|
+
flex-direction: column;
|
|
1497
|
+
}
|
|
1396
1498
|
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1499
|
+
.logo-title {
|
|
1500
|
+
font-size: 13px;
|
|
1501
|
+
font-weight: 600;
|
|
1502
|
+
line-height: 1;
|
|
1503
|
+
color: var(--text-primary);
|
|
1504
|
+
}
|
|
1403
1505
|
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
}
|
|
1506
|
+
.logo-version {
|
|
1507
|
+
font-size: 11px;
|
|
1508
|
+
color: var(--text-secondary);
|
|
1509
|
+
margin-top: 2px;
|
|
1510
|
+
}
|
|
1410
1511
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1512
|
+
.status-bar-center {
|
|
1513
|
+
flex: 1;
|
|
1514
|
+
display: flex;
|
|
1515
|
+
justify-content: center;
|
|
1516
|
+
}
|
|
1414
1517
|
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1518
|
+
.sovereignty-badge {
|
|
1519
|
+
display: flex;
|
|
1520
|
+
align-items: center;
|
|
1521
|
+
gap: 8px;
|
|
1522
|
+
padding: 8px 16px;
|
|
1523
|
+
background-color: rgba(63, 185, 80, 0.1);
|
|
1524
|
+
border: 1px solid rgba(63, 185, 80, 0.3);
|
|
1525
|
+
border-radius: 6px;
|
|
1526
|
+
font-size: 13px;
|
|
1527
|
+
font-weight: 500;
|
|
1528
|
+
}
|
|
1420
1529
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
justify-content: center;
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
.sovereignty-badge {
|
|
1429
|
-
display: flex;
|
|
1430
|
-
align-items: center;
|
|
1431
|
-
gap: 8px;
|
|
1432
|
-
padding: 6px 12px;
|
|
1433
|
-
background: rgba(88, 166, 255, 0.1);
|
|
1434
|
-
border: 1px solid var(--blue);
|
|
1435
|
-
border-radius: 20px;
|
|
1436
|
-
font-size: 13px;
|
|
1437
|
-
font-weight: 600;
|
|
1438
|
-
}
|
|
1530
|
+
.sovereignty-badge.degraded {
|
|
1531
|
+
background-color: rgba(210, 153, 34, 0.1);
|
|
1532
|
+
border-color: rgba(210, 153, 34, 0.3);
|
|
1533
|
+
}
|
|
1439
1534
|
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
width: 28px;
|
|
1445
|
-
height: 28px;
|
|
1446
|
-
border-radius: 50%;
|
|
1447
|
-
font-family: var(--mono);
|
|
1448
|
-
font-weight: 700;
|
|
1449
|
-
font-size: 12px;
|
|
1450
|
-
background: var(--blue);
|
|
1451
|
-
color: var(--bg);
|
|
1452
|
-
}
|
|
1535
|
+
.sovereignty-badge.inactive {
|
|
1536
|
+
background-color: rgba(248, 81, 73, 0.1);
|
|
1537
|
+
border-color: rgba(248, 81, 73, 0.3);
|
|
1538
|
+
}
|
|
1453
1539
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1540
|
+
.sovereignty-score {
|
|
1541
|
+
font-weight: 700;
|
|
1542
|
+
color: var(--green);
|
|
1543
|
+
}
|
|
1457
1544
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1545
|
+
.sovereignty-badge.degraded .sovereignty-score {
|
|
1546
|
+
color: var(--amber);
|
|
1547
|
+
}
|
|
1461
1548
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1549
|
+
.sovereignty-badge.inactive .sovereignty-score {
|
|
1550
|
+
color: var(--red);
|
|
1551
|
+
}
|
|
1465
1552
|
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1553
|
+
.status-bar-right {
|
|
1554
|
+
display: flex;
|
|
1555
|
+
align-items: center;
|
|
1556
|
+
gap: 16px;
|
|
1557
|
+
flex: 0 0 auto;
|
|
1558
|
+
}
|
|
1472
1559
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
}
|
|
1560
|
+
.status-item {
|
|
1561
|
+
display: flex;
|
|
1562
|
+
align-items: center;
|
|
1563
|
+
gap: 6px;
|
|
1564
|
+
font-size: 12px;
|
|
1565
|
+
color: var(--text-secondary);
|
|
1566
|
+
}
|
|
1481
1567
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1568
|
+
.status-item strong {
|
|
1569
|
+
color: var(--text-primary);
|
|
1570
|
+
font-weight: 500;
|
|
1571
|
+
}
|
|
1486
1572
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
font-family: var(--mono);
|
|
1494
|
-
}
|
|
1573
|
+
.status-dot {
|
|
1574
|
+
width: 8px;
|
|
1575
|
+
height: 8px;
|
|
1576
|
+
border-radius: 50%;
|
|
1577
|
+
background-color: var(--green);
|
|
1578
|
+
}
|
|
1495
1579
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
border-radius: 50%;
|
|
1500
|
-
background: var(--green);
|
|
1501
|
-
animation: pulse 2s ease-in-out infinite;
|
|
1502
|
-
}
|
|
1580
|
+
.status-dot.disconnected {
|
|
1581
|
+
background-color: var(--red);
|
|
1582
|
+
}
|
|
1503
1583
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1584
|
+
.pending-badge {
|
|
1585
|
+
display: flex;
|
|
1586
|
+
align-items: center;
|
|
1587
|
+
gap: 6px;
|
|
1588
|
+
padding: 4px 8px;
|
|
1589
|
+
background-color: var(--blue);
|
|
1590
|
+
color: var(--bg);
|
|
1591
|
+
border-radius: 4px;
|
|
1592
|
+
font-size: 11px;
|
|
1593
|
+
font-weight: 600;
|
|
1594
|
+
cursor: pointer;
|
|
1595
|
+
}
|
|
1508
1596
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1597
|
+
/* Main Content */
|
|
1598
|
+
.main-content {
|
|
1599
|
+
flex: 1;
|
|
1600
|
+
margin-top: 56px;
|
|
1601
|
+
overflow-y: auto;
|
|
1602
|
+
padding: 24px;
|
|
1603
|
+
}
|
|
1513
1604
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
min-width: 24px;
|
|
1519
|
-
height: 24px;
|
|
1520
|
-
padding: 0 6px;
|
|
1521
|
-
background: var(--red);
|
|
1522
|
-
color: white;
|
|
1523
|
-
border-radius: 12px;
|
|
1524
|
-
font-size: 11px;
|
|
1525
|
-
font-weight: 700;
|
|
1526
|
-
animation: pulse 1s ease-in-out infinite;
|
|
1527
|
-
}
|
|
1605
|
+
.grid {
|
|
1606
|
+
display: grid;
|
|
1607
|
+
gap: 20px;
|
|
1608
|
+
}
|
|
1528
1609
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1610
|
+
/* Row 1: Sovereignty Layers */
|
|
1611
|
+
.sovereignty-layers {
|
|
1612
|
+
display: grid;
|
|
1613
|
+
grid-template-columns: repeat(4, 1fr);
|
|
1614
|
+
gap: 16px;
|
|
1615
|
+
}
|
|
1532
1616
|
|
|
1533
|
-
|
|
1617
|
+
.layer-card {
|
|
1618
|
+
background-color: var(--surface);
|
|
1619
|
+
border: 1px solid var(--border);
|
|
1620
|
+
border-radius: 8px;
|
|
1621
|
+
padding: 20px;
|
|
1622
|
+
display: flex;
|
|
1623
|
+
flex-direction: column;
|
|
1624
|
+
gap: 12px;
|
|
1625
|
+
}
|
|
1534
1626
|
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
overflow: hidden;
|
|
1540
|
-
}
|
|
1627
|
+
.layer-card.degraded {
|
|
1628
|
+
border-color: var(--amber);
|
|
1629
|
+
background-color: rgba(210, 153, 34, 0.05);
|
|
1630
|
+
}
|
|
1541
1631
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
border-right: 1px solid var(--border);
|
|
1547
|
-
overflow: hidden;
|
|
1548
|
-
}
|
|
1632
|
+
.layer-card.inactive {
|
|
1633
|
+
border-color: var(--red);
|
|
1634
|
+
background-color: rgba(248, 81, 73, 0.05);
|
|
1635
|
+
}
|
|
1549
1636
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
font-weight: 600;
|
|
1558
|
-
text-transform: uppercase;
|
|
1559
|
-
letter-spacing: 0.5px;
|
|
1560
|
-
color: var(--text-secondary);
|
|
1561
|
-
}
|
|
1637
|
+
.layer-name {
|
|
1638
|
+
font-size: 12px;
|
|
1639
|
+
font-weight: 600;
|
|
1640
|
+
color: var(--text-secondary);
|
|
1641
|
+
text-transform: uppercase;
|
|
1642
|
+
letter-spacing: 0.5px;
|
|
1643
|
+
}
|
|
1562
1644
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
}
|
|
1645
|
+
.layer-title {
|
|
1646
|
+
font-size: 14px;
|
|
1647
|
+
font-weight: 600;
|
|
1648
|
+
color: var(--text-primary);
|
|
1649
|
+
}
|
|
1569
1650
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1651
|
+
.layer-status {
|
|
1652
|
+
display: inline-flex;
|
|
1653
|
+
align-items: center;
|
|
1654
|
+
gap: 6px;
|
|
1655
|
+
padding: 4px 8px;
|
|
1656
|
+
background-color: rgba(63, 185, 80, 0.15);
|
|
1657
|
+
color: var(--green);
|
|
1658
|
+
border-radius: 4px;
|
|
1659
|
+
font-size: 11px;
|
|
1660
|
+
font-weight: 600;
|
|
1661
|
+
width: fit-content;
|
|
1662
|
+
}
|
|
1575
1663
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
font-family: var(--mono);
|
|
1581
|
-
cursor: pointer;
|
|
1582
|
-
transition: background 0.15s;
|
|
1583
|
-
display: flex;
|
|
1584
|
-
align-items: flex-start;
|
|
1585
|
-
gap: 10px;
|
|
1586
|
-
}
|
|
1664
|
+
.layer-card.degraded .layer-status {
|
|
1665
|
+
background-color: rgba(210, 153, 34, 0.15);
|
|
1666
|
+
color: var(--amber);
|
|
1667
|
+
}
|
|
1587
1668
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1669
|
+
.layer-card.inactive .layer-status {
|
|
1670
|
+
background-color: rgba(248, 81, 73, 0.15);
|
|
1671
|
+
color: var(--red);
|
|
1672
|
+
}
|
|
1591
1673
|
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1674
|
+
.layer-detail {
|
|
1675
|
+
font-size: 12px;
|
|
1676
|
+
color: var(--text-secondary);
|
|
1677
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1678
|
+
padding: 8px;
|
|
1679
|
+
background-color: var(--bg);
|
|
1680
|
+
border-radius: 4px;
|
|
1681
|
+
border-left: 2px solid var(--blue);
|
|
1682
|
+
}
|
|
1600
1683
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1684
|
+
/* Row 2: Info Cards */
|
|
1685
|
+
.info-cards {
|
|
1686
|
+
display: grid;
|
|
1687
|
+
grid-template-columns: repeat(3, 1fr);
|
|
1688
|
+
gap: 16px;
|
|
1689
|
+
}
|
|
1605
1690
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1691
|
+
.info-card {
|
|
1692
|
+
background-color: var(--surface);
|
|
1693
|
+
border: 1px solid var(--border);
|
|
1694
|
+
border-radius: 8px;
|
|
1695
|
+
padding: 20px;
|
|
1696
|
+
}
|
|
1611
1697
|
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1698
|
+
.card-header {
|
|
1699
|
+
font-size: 12px;
|
|
1700
|
+
font-weight: 600;
|
|
1701
|
+
color: var(--text-secondary);
|
|
1702
|
+
text-transform: uppercase;
|
|
1703
|
+
letter-spacing: 0.5px;
|
|
1704
|
+
margin-bottom: 16px;
|
|
1705
|
+
}
|
|
1618
1706
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
font-weight: 700;
|
|
1627
|
-
border-radius: 3px;
|
|
1628
|
-
text-transform: uppercase;
|
|
1629
|
-
flex: 0 0 auto;
|
|
1630
|
-
}
|
|
1707
|
+
.card-row {
|
|
1708
|
+
display: flex;
|
|
1709
|
+
justify-content: space-between;
|
|
1710
|
+
align-items: center;
|
|
1711
|
+
margin-bottom: 12px;
|
|
1712
|
+
font-size: 13px;
|
|
1713
|
+
}
|
|
1631
1714
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
}
|
|
1715
|
+
.card-row:last-child {
|
|
1716
|
+
margin-bottom: 0;
|
|
1717
|
+
}
|
|
1636
1718
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
}
|
|
1719
|
+
.card-label {
|
|
1720
|
+
color: var(--text-secondary);
|
|
1721
|
+
}
|
|
1641
1722
|
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1723
|
+
.card-value {
|
|
1724
|
+
color: var(--text-primary);
|
|
1725
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1726
|
+
font-weight: 500;
|
|
1727
|
+
}
|
|
1646
1728
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1729
|
+
.identity-badge {
|
|
1730
|
+
display: inline-flex;
|
|
1731
|
+
align-items: center;
|
|
1732
|
+
gap: 4px;
|
|
1733
|
+
padding: 2px 6px;
|
|
1734
|
+
background-color: rgba(88, 166, 255, 0.15);
|
|
1735
|
+
color: var(--blue);
|
|
1736
|
+
border-radius: 3px;
|
|
1737
|
+
font-size: 10px;
|
|
1738
|
+
font-weight: 600;
|
|
1739
|
+
text-transform: uppercase;
|
|
1740
|
+
}
|
|
1651
1741
|
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1742
|
+
.trust-tier-badge {
|
|
1743
|
+
display: inline-flex;
|
|
1744
|
+
align-items: center;
|
|
1745
|
+
gap: 4px;
|
|
1746
|
+
padding: 2px 6px;
|
|
1747
|
+
background-color: rgba(63, 185, 80, 0.15);
|
|
1748
|
+
color: var(--green);
|
|
1749
|
+
border-radius: 3px;
|
|
1750
|
+
font-size: 10px;
|
|
1751
|
+
font-weight: 600;
|
|
1752
|
+
}
|
|
1655
1753
|
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1754
|
+
.truncated {
|
|
1755
|
+
max-width: 200px;
|
|
1756
|
+
overflow: hidden;
|
|
1757
|
+
text-overflow: ellipsis;
|
|
1758
|
+
white-space: nowrap;
|
|
1759
|
+
}
|
|
1659
1760
|
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1761
|
+
/* Row 3: SHR & Activity */
|
|
1762
|
+
.main-panels {
|
|
1763
|
+
display: grid;
|
|
1764
|
+
grid-template-columns: 1fr 1fr;
|
|
1765
|
+
gap: 16px;
|
|
1766
|
+
min-height: 400px;
|
|
1767
|
+
}
|
|
1665
1768
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1769
|
+
.panel {
|
|
1770
|
+
background-color: var(--surface);
|
|
1771
|
+
border: 1px solid var(--border);
|
|
1772
|
+
border-radius: 8px;
|
|
1773
|
+
display: flex;
|
|
1774
|
+
flex-direction: column;
|
|
1775
|
+
overflow: hidden;
|
|
1776
|
+
}
|
|
1674
1777
|
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
}
|
|
1778
|
+
.panel-header {
|
|
1779
|
+
padding: 16px 20px;
|
|
1780
|
+
border-bottom: 1px solid var(--border);
|
|
1781
|
+
display: flex;
|
|
1782
|
+
justify-content: space-between;
|
|
1783
|
+
align-items: center;
|
|
1784
|
+
}
|
|
1683
1785
|
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1786
|
+
.panel-title {
|
|
1787
|
+
font-size: 14px;
|
|
1788
|
+
font-weight: 600;
|
|
1789
|
+
color: var(--text-primary);
|
|
1790
|
+
}
|
|
1688
1791
|
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1792
|
+
.panel-action {
|
|
1793
|
+
background: none;
|
|
1794
|
+
border: none;
|
|
1795
|
+
color: var(--blue);
|
|
1796
|
+
cursor: pointer;
|
|
1797
|
+
font-size: 12px;
|
|
1798
|
+
padding: 0;
|
|
1799
|
+
font-weight: 500;
|
|
1800
|
+
transition: color 0.2s;
|
|
1801
|
+
}
|
|
1692
1802
|
|
|
1693
|
-
|
|
1803
|
+
.panel-action:hover {
|
|
1804
|
+
color: #79c0ff;
|
|
1805
|
+
}
|
|
1694
1806
|
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
overflow: hidden;
|
|
1701
|
-
}
|
|
1807
|
+
.panel-content {
|
|
1808
|
+
flex: 1;
|
|
1809
|
+
overflow-y: auto;
|
|
1810
|
+
padding: 20px;
|
|
1811
|
+
}
|
|
1702
1812
|
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
color: var(--text-secondary);
|
|
1711
|
-
display: flex;
|
|
1712
|
-
align-items: center;
|
|
1713
|
-
gap: 8px;
|
|
1714
|
-
}
|
|
1813
|
+
/* SHR Viewer */
|
|
1814
|
+
.shr-json {
|
|
1815
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1816
|
+
font-size: 12px;
|
|
1817
|
+
line-height: 1.6;
|
|
1818
|
+
color: var(--text-secondary);
|
|
1819
|
+
}
|
|
1715
1820
|
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
padding: 16px 16px;
|
|
1720
|
-
display: grid;
|
|
1721
|
-
grid-template-columns: 1fr 1fr;
|
|
1722
|
-
gap: 12px;
|
|
1723
|
-
}
|
|
1821
|
+
.shr-section {
|
|
1822
|
+
margin-bottom: 12px;
|
|
1823
|
+
}
|
|
1724
1824
|
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1825
|
+
.shr-section-header {
|
|
1826
|
+
display: flex;
|
|
1827
|
+
align-items: center;
|
|
1828
|
+
gap: 8px;
|
|
1829
|
+
cursor: pointer;
|
|
1830
|
+
font-weight: 600;
|
|
1831
|
+
color: var(--text-primary);
|
|
1832
|
+
padding: 8px;
|
|
1833
|
+
background-color: var(--bg);
|
|
1834
|
+
border-radius: 4px;
|
|
1835
|
+
user-select: none;
|
|
1836
|
+
}
|
|
1734
1837
|
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1838
|
+
.shr-section-header:hover {
|
|
1839
|
+
background-color: var(--muted);
|
|
1840
|
+
}
|
|
1738
1841
|
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1842
|
+
.shr-toggle {
|
|
1843
|
+
width: 16px;
|
|
1844
|
+
height: 16px;
|
|
1845
|
+
display: flex;
|
|
1846
|
+
align-items: center;
|
|
1847
|
+
justify-content: center;
|
|
1848
|
+
font-size: 10px;
|
|
1849
|
+
transition: transform 0.2s;
|
|
1850
|
+
}
|
|
1746
1851
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
gap: 6px;
|
|
1751
|
-
font-size: 12px;
|
|
1752
|
-
font-weight: 600;
|
|
1753
|
-
}
|
|
1852
|
+
.shr-section.collapsed .shr-toggle {
|
|
1853
|
+
transform: rotate(-90deg);
|
|
1854
|
+
}
|
|
1754
1855
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1856
|
+
.shr-section-content {
|
|
1857
|
+
padding: 8px 16px;
|
|
1858
|
+
background-color: rgba(0, 0, 0, 0.2);
|
|
1859
|
+
border-radius: 4px;
|
|
1860
|
+
margin-top: 4px;
|
|
1861
|
+
}
|
|
1758
1862
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1863
|
+
.shr-section.collapsed .shr-section-content {
|
|
1864
|
+
display: none;
|
|
1865
|
+
}
|
|
1762
1866
|
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
margin-top: 4px;
|
|
1768
|
-
}
|
|
1867
|
+
.shr-item {
|
|
1868
|
+
display: flex;
|
|
1869
|
+
margin-bottom: 4px;
|
|
1870
|
+
}
|
|
1769
1871
|
|
|
1770
|
-
|
|
1872
|
+
.shr-key {
|
|
1873
|
+
color: var(--blue);
|
|
1874
|
+
margin-right: 8px;
|
|
1875
|
+
min-width: 120px;
|
|
1876
|
+
}
|
|
1771
1877
|
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
bottom: 0;
|
|
1777
|
-
width: 0;
|
|
1778
|
-
background: var(--surface);
|
|
1779
|
-
border-left: 1px solid var(--border);
|
|
1780
|
-
z-index: 999;
|
|
1781
|
-
overflow-y: auto;
|
|
1782
|
-
transition: width 0.3s ease-out;
|
|
1783
|
-
display: flex;
|
|
1784
|
-
flex-direction: column;
|
|
1785
|
-
}
|
|
1878
|
+
.shr-value {
|
|
1879
|
+
color: var(--green);
|
|
1880
|
+
word-break: break-all;
|
|
1881
|
+
}
|
|
1786
1882
|
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1883
|
+
/* Activity Feed */
|
|
1884
|
+
.activity-feed {
|
|
1885
|
+
display: flex;
|
|
1886
|
+
flex-direction: column;
|
|
1887
|
+
gap: 12px;
|
|
1888
|
+
}
|
|
1790
1889
|
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1890
|
+
.activity-item {
|
|
1891
|
+
padding: 12px;
|
|
1892
|
+
background-color: var(--bg);
|
|
1893
|
+
border-left: 2px solid var(--border);
|
|
1894
|
+
border-radius: 4px;
|
|
1895
|
+
font-size: 12px;
|
|
1796
1896
|
}
|
|
1797
|
-
}
|
|
1798
1897
|
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
display: flex;
|
|
1803
|
-
align-items: center;
|
|
1804
|
-
justify-content: space-between;
|
|
1805
|
-
flex: 0 0 auto;
|
|
1806
|
-
}
|
|
1898
|
+
.activity-item.tool-call {
|
|
1899
|
+
border-left-color: var(--blue);
|
|
1900
|
+
}
|
|
1807
1901
|
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
text-transform: uppercase;
|
|
1812
|
-
letter-spacing: 0.5px;
|
|
1813
|
-
color: var(--text-primary);
|
|
1814
|
-
}
|
|
1902
|
+
.activity-item.context-gate {
|
|
1903
|
+
border-left-color: var(--amber);
|
|
1904
|
+
}
|
|
1815
1905
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
color: var(--text-secondary);
|
|
1820
|
-
cursor: pointer;
|
|
1821
|
-
font-size: 18px;
|
|
1822
|
-
padding: 0;
|
|
1823
|
-
display: flex;
|
|
1824
|
-
align-items: center;
|
|
1825
|
-
justify-content: center;
|
|
1826
|
-
}
|
|
1906
|
+
.activity-item.injection {
|
|
1907
|
+
border-left-color: var(--red);
|
|
1908
|
+
}
|
|
1827
1909
|
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1910
|
+
.activity-item.protection {
|
|
1911
|
+
border-left-color: var(--green);
|
|
1912
|
+
}
|
|
1831
1913
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1914
|
+
.activity-type {
|
|
1915
|
+
font-weight: 600;
|
|
1916
|
+
color: var(--text-primary);
|
|
1917
|
+
margin-bottom: 4px;
|
|
1918
|
+
text-transform: uppercase;
|
|
1919
|
+
font-size: 11px;
|
|
1920
|
+
letter-spacing: 0.5px;
|
|
1921
|
+
}
|
|
1836
1922
|
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
}
|
|
1923
|
+
.activity-content {
|
|
1924
|
+
color: var(--text-secondary);
|
|
1925
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1926
|
+
margin-bottom: 4px;
|
|
1927
|
+
word-break: break-all;
|
|
1928
|
+
}
|
|
1844
1929
|
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
}
|
|
1930
|
+
.activity-time {
|
|
1931
|
+
font-size: 11px;
|
|
1932
|
+
color: var(--text-secondary);
|
|
1933
|
+
}
|
|
1850
1934
|
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1935
|
+
.empty-state {
|
|
1936
|
+
display: flex;
|
|
1937
|
+
align-items: center;
|
|
1938
|
+
justify-content: center;
|
|
1939
|
+
height: 100%;
|
|
1940
|
+
color: var(--text-secondary);
|
|
1941
|
+
font-size: 13px;
|
|
1942
|
+
}
|
|
1858
1943
|
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
font-weight: 700;
|
|
1867
|
-
border-radius: 3px;
|
|
1868
|
-
text-transform: uppercase;
|
|
1869
|
-
color: white;
|
|
1870
|
-
}
|
|
1944
|
+
/* Row 4: Handshake History */
|
|
1945
|
+
.handshake-table {
|
|
1946
|
+
background-color: var(--surface);
|
|
1947
|
+
border: 1px solid var(--border);
|
|
1948
|
+
border-radius: 8px;
|
|
1949
|
+
overflow: hidden;
|
|
1950
|
+
}
|
|
1871
1951
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1952
|
+
.table-header {
|
|
1953
|
+
display: grid;
|
|
1954
|
+
grid-template-columns: 2fr 1fr 1fr 1fr 1.5fr 1.5fr;
|
|
1955
|
+
gap: 16px;
|
|
1956
|
+
padding: 16px 20px;
|
|
1957
|
+
border-bottom: 1px solid var(--border);
|
|
1958
|
+
background-color: var(--bg);
|
|
1959
|
+
font-size: 12px;
|
|
1960
|
+
font-weight: 600;
|
|
1961
|
+
color: var(--text-secondary);
|
|
1962
|
+
text-transform: uppercase;
|
|
1963
|
+
letter-spacing: 0.5px;
|
|
1964
|
+
}
|
|
1875
1965
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1966
|
+
.table-rows {
|
|
1967
|
+
max-height: 300px;
|
|
1968
|
+
overflow-y: auto;
|
|
1969
|
+
}
|
|
1879
1970
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1971
|
+
.table-row {
|
|
1972
|
+
display: grid;
|
|
1973
|
+
grid-template-columns: 2fr 1fr 1fr 1fr 1.5fr 1.5fr;
|
|
1974
|
+
gap: 16px;
|
|
1975
|
+
padding: 14px 20px;
|
|
1976
|
+
border-bottom: 1px solid var(--border);
|
|
1977
|
+
align-items: center;
|
|
1978
|
+
font-size: 12px;
|
|
1979
|
+
cursor: pointer;
|
|
1980
|
+
transition: background-color 0.2s;
|
|
1981
|
+
}
|
|
1884
1982
|
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
gap: 6px;
|
|
1889
|
-
font-size: 11px;
|
|
1890
|
-
font-family: var(--mono);
|
|
1891
|
-
color: var(--text-secondary);
|
|
1892
|
-
}
|
|
1983
|
+
.table-row:hover {
|
|
1984
|
+
background-color: var(--bg);
|
|
1985
|
+
}
|
|
1893
1986
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
background: rgba(48, 54, 61, 0.8);
|
|
1898
|
-
border-radius: 2px;
|
|
1899
|
-
overflow: hidden;
|
|
1900
|
-
}
|
|
1987
|
+
.table-row:last-child {
|
|
1988
|
+
border-bottom: none;
|
|
1989
|
+
}
|
|
1901
1990
|
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
}
|
|
1991
|
+
.table-cell {
|
|
1992
|
+
color: var(--text-secondary);
|
|
1993
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1994
|
+
}
|
|
1907
1995
|
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1996
|
+
.table-cell.strong {
|
|
1997
|
+
color: var(--text-primary);
|
|
1998
|
+
font-weight: 500;
|
|
1999
|
+
}
|
|
1911
2000
|
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2001
|
+
.table-empty {
|
|
2002
|
+
padding: 40px 20px;
|
|
2003
|
+
text-align: center;
|
|
2004
|
+
color: var(--text-secondary);
|
|
2005
|
+
font-size: 13px;
|
|
2006
|
+
}
|
|
1916
2007
|
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
2008
|
+
/* Pending Overlay */
|
|
2009
|
+
.pending-overlay {
|
|
2010
|
+
position: fixed;
|
|
2011
|
+
top: 0;
|
|
2012
|
+
right: -400px;
|
|
2013
|
+
width: 400px;
|
|
2014
|
+
height: 100vh;
|
|
2015
|
+
background-color: var(--surface);
|
|
2016
|
+
border-left: 1px solid var(--border);
|
|
2017
|
+
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3);
|
|
2018
|
+
z-index: 200;
|
|
2019
|
+
transition: right 0.3s ease;
|
|
2020
|
+
display: flex;
|
|
2021
|
+
flex-direction: column;
|
|
2022
|
+
overflow-y: auto;
|
|
2023
|
+
}
|
|
1928
2024
|
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
}
|
|
2025
|
+
.pending-overlay.show {
|
|
2026
|
+
right: 0;
|
|
2027
|
+
}
|
|
1933
2028
|
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
2029
|
+
.pending-header {
|
|
2030
|
+
padding: 16px 20px;
|
|
2031
|
+
border-bottom: 1px solid var(--border);
|
|
2032
|
+
font-weight: 600;
|
|
2033
|
+
color: var(--text-primary);
|
|
2034
|
+
}
|
|
1937
2035
|
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2036
|
+
.pending-items {
|
|
2037
|
+
flex: 1;
|
|
2038
|
+
overflow-y: auto;
|
|
2039
|
+
padding: 16px;
|
|
2040
|
+
}
|
|
1942
2041
|
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2042
|
+
.pending-item {
|
|
2043
|
+
background-color: var(--bg);
|
|
2044
|
+
border: 1px solid var(--border);
|
|
2045
|
+
border-radius: 6px;
|
|
2046
|
+
padding: 16px;
|
|
2047
|
+
margin-bottom: 12px;
|
|
2048
|
+
}
|
|
1946
2049
|
|
|
1947
|
-
|
|
2050
|
+
.pending-title {
|
|
2051
|
+
font-weight: 600;
|
|
2052
|
+
color: var(--text-primary);
|
|
2053
|
+
margin-bottom: 8px;
|
|
2054
|
+
word-break: break-word;
|
|
2055
|
+
}
|
|
1948
2056
|
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
border-top: 1px solid var(--border);
|
|
1956
|
-
max-height: 240px;
|
|
1957
|
-
z-index: 500;
|
|
1958
|
-
display: flex;
|
|
1959
|
-
flex-direction: column;
|
|
1960
|
-
transition: max-height 0.3s ease-out;
|
|
1961
|
-
}
|
|
2057
|
+
.pending-countdown {
|
|
2058
|
+
font-size: 12px;
|
|
2059
|
+
color: var(--amber);
|
|
2060
|
+
margin-bottom: 12px;
|
|
2061
|
+
font-weight: 500;
|
|
2062
|
+
}
|
|
1962
2063
|
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2064
|
+
.pending-actions {
|
|
2065
|
+
display: flex;
|
|
2066
|
+
gap: 8px;
|
|
2067
|
+
}
|
|
1966
2068
|
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
color: var(--text-secondary);
|
|
1978
|
-
flex: 0 0 auto;
|
|
1979
|
-
}
|
|
2069
|
+
.pending-btn {
|
|
2070
|
+
flex: 1;
|
|
2071
|
+
padding: 8px 12px;
|
|
2072
|
+
border: none;
|
|
2073
|
+
border-radius: 4px;
|
|
2074
|
+
font-size: 12px;
|
|
2075
|
+
font-weight: 600;
|
|
2076
|
+
cursor: pointer;
|
|
2077
|
+
transition: background-color 0.2s;
|
|
2078
|
+
}
|
|
1980
2079
|
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2080
|
+
.pending-approve {
|
|
2081
|
+
background-color: var(--green);
|
|
2082
|
+
color: var(--bg);
|
|
2083
|
+
}
|
|
1984
2084
|
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2085
|
+
.pending-approve:hover {
|
|
2086
|
+
background-color: #3fa040;
|
|
2087
|
+
}
|
|
1988
2088
|
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
display: flex;
|
|
1994
|
-
flex-direction: column;
|
|
1995
|
-
gap: 10px;
|
|
1996
|
-
}
|
|
2089
|
+
.pending-deny {
|
|
2090
|
+
background-color: var(--red);
|
|
2091
|
+
color: var(--bg);
|
|
2092
|
+
}
|
|
1997
2093
|
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
border-left: 2px solid var(--red);
|
|
2002
|
-
border-radius: 4px;
|
|
2003
|
-
font-size: 11px;
|
|
2004
|
-
color: var(--text-secondary);
|
|
2005
|
-
}
|
|
2094
|
+
.pending-deny:hover {
|
|
2095
|
+
background-color: #e03c3c;
|
|
2096
|
+
}
|
|
2006
2097
|
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2098
|
+
/* Threat Panel */
|
|
2099
|
+
.threat-panel {
|
|
2100
|
+
background-color: var(--surface);
|
|
2101
|
+
border: 1px solid var(--border);
|
|
2102
|
+
border-radius: 8px;
|
|
2103
|
+
margin-top: 20px;
|
|
2104
|
+
overflow: hidden;
|
|
2105
|
+
}
|
|
2012
2106
|
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2107
|
+
.threat-header {
|
|
2108
|
+
padding: 16px 20px;
|
|
2109
|
+
border-bottom: 1px solid var(--border);
|
|
2110
|
+
display: flex;
|
|
2111
|
+
justify-content: space-between;
|
|
2112
|
+
align-items: center;
|
|
2113
|
+
cursor: pointer;
|
|
2114
|
+
user-select: none;
|
|
2115
|
+
}
|
|
2019
2116
|
|
|
2020
|
-
|
|
2117
|
+
.threat-title {
|
|
2118
|
+
font-weight: 600;
|
|
2119
|
+
color: var(--text-primary);
|
|
2120
|
+
}
|
|
2021
2121
|
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2122
|
+
.threat-toggle {
|
|
2123
|
+
font-size: 10px;
|
|
2124
|
+
color: var(--text-secondary);
|
|
2125
|
+
transition: transform 0.2s;
|
|
2126
|
+
}
|
|
2025
2127
|
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2128
|
+
.threat-panel.collapsed .threat-toggle {
|
|
2129
|
+
transform: rotate(-90deg);
|
|
2130
|
+
}
|
|
2029
2131
|
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2132
|
+
.threat-content {
|
|
2133
|
+
padding: 16px 20px;
|
|
2134
|
+
max-height: 300px;
|
|
2135
|
+
overflow-y: auto;
|
|
2136
|
+
}
|
|
2034
2137
|
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2138
|
+
.threat-panel.collapsed .threat-content {
|
|
2139
|
+
display: none;
|
|
2140
|
+
}
|
|
2038
2141
|
|
|
2039
|
-
|
|
2142
|
+
.threat-alert {
|
|
2143
|
+
background-color: rgba(248, 81, 73, 0.1);
|
|
2144
|
+
border: 1px solid var(--red);
|
|
2145
|
+
border-radius: 4px;
|
|
2146
|
+
padding: 12px;
|
|
2147
|
+
margin-bottom: 8px;
|
|
2148
|
+
font-size: 12px;
|
|
2149
|
+
}
|
|
2040
2150
|
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
display: none;
|
|
2151
|
+
.threat-alert:last-child {
|
|
2152
|
+
margin-bottom: 0;
|
|
2044
2153
|
}
|
|
2045
2154
|
|
|
2046
|
-
.
|
|
2047
|
-
|
|
2155
|
+
.threat-type {
|
|
2156
|
+
font-weight: 600;
|
|
2157
|
+
color: var(--red);
|
|
2158
|
+
margin-bottom: 4px;
|
|
2159
|
+
text-transform: uppercase;
|
|
2160
|
+
font-size: 10px;
|
|
2161
|
+
letter-spacing: 0.5px;
|
|
2048
2162
|
}
|
|
2049
|
-
}
|
|
2050
2163
|
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
padding: 0 12px;
|
|
2054
|
-
gap: 12px;
|
|
2055
|
-
height: 48px;
|
|
2164
|
+
.threat-message {
|
|
2165
|
+
color: var(--text-secondary);
|
|
2056
2166
|
}
|
|
2057
2167
|
|
|
2058
|
-
|
|
2059
|
-
|
|
2168
|
+
/* Scrollbar */
|
|
2169
|
+
::-webkit-scrollbar {
|
|
2170
|
+
width: 8px;
|
|
2060
2171
|
}
|
|
2061
2172
|
|
|
2062
|
-
|
|
2063
|
-
|
|
2173
|
+
::-webkit-scrollbar-track {
|
|
2174
|
+
background-color: transparent;
|
|
2064
2175
|
}
|
|
2065
2176
|
|
|
2066
|
-
|
|
2067
|
-
|
|
2177
|
+
::-webkit-scrollbar-thumb {
|
|
2178
|
+
background-color: var(--border);
|
|
2179
|
+
border-radius: 4px;
|
|
2068
2180
|
}
|
|
2069
2181
|
|
|
2070
|
-
|
|
2071
|
-
|
|
2182
|
+
::-webkit-scrollbar-thumb:hover {
|
|
2183
|
+
background-color: var(--text-secondary);
|
|
2072
2184
|
}
|
|
2073
2185
|
|
|
2074
|
-
|
|
2075
|
-
|
|
2186
|
+
/* Responsive */
|
|
2187
|
+
@media (max-width: 1400px) {
|
|
2188
|
+
.sovereignty-layers {
|
|
2189
|
+
grid-template-columns: repeat(2, 1fr);
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
.main-panels {
|
|
2193
|
+
grid-template-columns: 1fr;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
.pending-overlay {
|
|
2197
|
+
width: 100%;
|
|
2198
|
+
right: -100%;
|
|
2199
|
+
}
|
|
2076
2200
|
}
|
|
2077
2201
|
|
|
2078
|
-
|
|
2079
|
-
|
|
2202
|
+
@media (max-width: 768px) {
|
|
2203
|
+
.status-bar {
|
|
2204
|
+
flex-wrap: wrap;
|
|
2205
|
+
height: auto;
|
|
2206
|
+
padding: 12px;
|
|
2207
|
+
gap: 12px;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
.status-bar-center {
|
|
2211
|
+
order: 3;
|
|
2212
|
+
flex-basis: 100%;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
.main-content {
|
|
2216
|
+
margin-top: auto;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
.info-cards {
|
|
2220
|
+
grid-template-columns: 1fr;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
.table-header,
|
|
2224
|
+
.table-row {
|
|
2225
|
+
grid-template-columns: 1fr;
|
|
2226
|
+
}
|
|
2080
2227
|
}
|
|
2081
|
-
|
|
2082
|
-
</style>
|
|
2228
|
+
</style>
|
|
2083
2229
|
</head>
|
|
2084
2230
|
<body>
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
<div class="status-bar">
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
<div class="sovereignty-badge">
|
|
2094
|
-
<div class="sovereignty-score" id="sovereigntyScore">85</div>
|
|
2095
|
-
<span>Sovereignty Health</span>
|
|
2231
|
+
<!-- Status Bar -->
|
|
2232
|
+
<div class="status-bar">
|
|
2233
|
+
<div class="status-bar-left">
|
|
2234
|
+
<div class="logo-icon">\u25C6</div>
|
|
2235
|
+
<div class="logo-info">
|
|
2236
|
+
<div class="logo-title">SANCTUARY</div>
|
|
2237
|
+
<div class="logo-version">v${options.serverVersion}</div>
|
|
2238
|
+
</div>
|
|
2096
2239
|
</div>
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2240
|
+
|
|
2241
|
+
<div class="status-bar-center">
|
|
2242
|
+
<div id="sovereignty-badge" class="sovereignty-badge">
|
|
2243
|
+
<span>Sovereignty Health:</span>
|
|
2244
|
+
<span class="sovereignty-score" id="sovereignty-score">\u2014</span>
|
|
2245
|
+
<span>/ 100</span>
|
|
2246
|
+
</div>
|
|
2101
2247
|
</div>
|
|
2102
|
-
|
|
2103
|
-
|
|
2248
|
+
|
|
2249
|
+
<div class="status-bar-right">
|
|
2250
|
+
<div class="status-item">
|
|
2251
|
+
<strong id="protections-count">\u2014</strong>
|
|
2252
|
+
<span>Protections</span>
|
|
2253
|
+
</div>
|
|
2254
|
+
<div class="status-item">
|
|
2255
|
+
<strong id="uptime-value">\u2014</strong>
|
|
2256
|
+
<span>Uptime</span>
|
|
2257
|
+
</div>
|
|
2258
|
+
<div class="status-dot" id="connection-status"></div>
|
|
2259
|
+
<div id="pending-item-badge" class="pending-badge" style="display: none;">
|
|
2260
|
+
<span>\u23F3</span>
|
|
2261
|
+
<span id="pending-count">0</span>
|
|
2262
|
+
</div>
|
|
2104
2263
|
</div>
|
|
2105
|
-
<div class="status-dot" id="statusDot"></div>
|
|
2106
|
-
<div class="pending-badge hidden" id="pendingBadge">0</div>
|
|
2107
2264
|
</div>
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
<div class="
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
<div class="
|
|
2121
|
-
|
|
2265
|
+
|
|
2266
|
+
<!-- Main Content -->
|
|
2267
|
+
<div class="main-content">
|
|
2268
|
+
<div class="grid">
|
|
2269
|
+
<!-- Row 1: Sovereignty Layers -->
|
|
2270
|
+
<div class="sovereignty-layers" id="sovereignty-layers">
|
|
2271
|
+
<div class="layer-card" data-layer="l1">
|
|
2272
|
+
<div class="layer-name">Layer 1</div>
|
|
2273
|
+
<div class="layer-title">Cognitive Sovereignty</div>
|
|
2274
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l1-status">\u2014</span></div>
|
|
2275
|
+
<div class="layer-detail" id="l1-detail">Loading...</div>
|
|
2276
|
+
</div>
|
|
2277
|
+
<div class="layer-card" data-layer="l2">
|
|
2278
|
+
<div class="layer-name">Layer 2</div>
|
|
2279
|
+
<div class="layer-title">Operational Isolation</div>
|
|
2280
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l2-status">\u2014</span></div>
|
|
2281
|
+
<div class="layer-detail" id="l2-detail">Loading...</div>
|
|
2282
|
+
</div>
|
|
2283
|
+
<div class="layer-card" data-layer="l3">
|
|
2284
|
+
<div class="layer-name">Layer 3</div>
|
|
2285
|
+
<div class="layer-title">Selective Disclosure</div>
|
|
2286
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l3-status">\u2014</span></div>
|
|
2287
|
+
<div class="layer-detail" id="l3-detail">Loading...</div>
|
|
2288
|
+
</div>
|
|
2289
|
+
<div class="layer-card" data-layer="l4">
|
|
2290
|
+
<div class="layer-name">Layer 4</div>
|
|
2291
|
+
<div class="layer-title">Verifiable Reputation</div>
|
|
2292
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l4-status">\u2014</span></div>
|
|
2293
|
+
<div class="layer-detail" id="l4-detail">Loading...</div>
|
|
2294
|
+
</div>
|
|
2295
|
+
</div>
|
|
2296
|
+
|
|
2297
|
+
<!-- Row 2: Info Cards -->
|
|
2298
|
+
<div class="info-cards">
|
|
2299
|
+
<div class="info-card">
|
|
2300
|
+
<div class="card-header">Identity</div>
|
|
2301
|
+
<div class="card-row">
|
|
2302
|
+
<span class="card-label">Primary</span>
|
|
2303
|
+
<span class="card-value" id="identity-label">\u2014</span>
|
|
2304
|
+
</div>
|
|
2305
|
+
<div class="card-row">
|
|
2306
|
+
<span class="card-label">DID</span>
|
|
2307
|
+
<span class="card-value truncated" id="identity-did" title="">\u2014</span>
|
|
2308
|
+
</div>
|
|
2309
|
+
<div class="card-row">
|
|
2310
|
+
<span class="card-label">Public Key</span>
|
|
2311
|
+
<span class="card-value truncated" id="identity-pubkey" title="">\u2014</span>
|
|
2312
|
+
</div>
|
|
2313
|
+
<div class="card-row">
|
|
2314
|
+
<span class="card-label">Type</span>
|
|
2315
|
+
<span class="identity-badge">Ed25519</span>
|
|
2316
|
+
</div>
|
|
2317
|
+
<div class="card-row">
|
|
2318
|
+
<span class="card-label">Created</span>
|
|
2319
|
+
<span class="card-value" id="identity-created">\u2014</span>
|
|
2320
|
+
</div>
|
|
2321
|
+
<div class="card-row">
|
|
2322
|
+
<span class="card-label">Identities</span>
|
|
2323
|
+
<span class="card-value" id="identity-count">\u2014</span>
|
|
2324
|
+
</div>
|
|
2325
|
+
</div>
|
|
2326
|
+
|
|
2327
|
+
<div class="info-card">
|
|
2328
|
+
<div class="card-header">Handshakes</div>
|
|
2329
|
+
<div class="card-row">
|
|
2330
|
+
<span class="card-label">Total</span>
|
|
2331
|
+
<span class="card-value" id="handshake-count">\u2014</span>
|
|
2332
|
+
</div>
|
|
2333
|
+
<div class="card-row">
|
|
2334
|
+
<span class="card-label">Latest Peer</span>
|
|
2335
|
+
<span class="card-value truncated" id="handshake-latest">\u2014</span>
|
|
2336
|
+
</div>
|
|
2337
|
+
<div class="card-row">
|
|
2338
|
+
<span class="card-label">Trust Tier</span>
|
|
2339
|
+
<span class="trust-tier-badge" id="handshake-tier">Unverified</span>
|
|
2340
|
+
</div>
|
|
2341
|
+
<div class="card-row">
|
|
2342
|
+
<span class="card-label">Timestamp</span>
|
|
2343
|
+
<span class="card-value" id="handshake-time">\u2014</span>
|
|
2344
|
+
</div>
|
|
2345
|
+
</div>
|
|
2346
|
+
|
|
2347
|
+
<div class="info-card">
|
|
2348
|
+
<div class="card-header">Reputation</div>
|
|
2349
|
+
<div class="card-row">
|
|
2350
|
+
<span class="card-label">Weighted Score</span>
|
|
2351
|
+
<span class="card-value" id="reputation-score">\u2014</span>
|
|
2352
|
+
</div>
|
|
2353
|
+
<div class="card-row">
|
|
2354
|
+
<span class="card-label">Attestations</span>
|
|
2355
|
+
<span class="card-value" id="reputation-attestations">\u2014</span>
|
|
2356
|
+
</div>
|
|
2357
|
+
<div class="card-row">
|
|
2358
|
+
<span class="card-label">Verified Sovereign</span>
|
|
2359
|
+
<span class="card-value" id="reputation-verified">\u2014</span>
|
|
2360
|
+
</div>
|
|
2361
|
+
<div class="card-row">
|
|
2362
|
+
<span class="card-label">Verified Degraded</span>
|
|
2363
|
+
<span class="card-value" id="reputation-degraded">\u2014</span>
|
|
2364
|
+
</div>
|
|
2365
|
+
<div class="card-row">
|
|
2366
|
+
<span class="card-label">Unverified</span>
|
|
2367
|
+
<span class="card-value" id="reputation-unverified">\u2014</span>
|
|
2368
|
+
</div>
|
|
2369
|
+
</div>
|
|
2370
|
+
</div>
|
|
2371
|
+
|
|
2372
|
+
<!-- Row 3: SHR & Activity -->
|
|
2373
|
+
<div class="main-panels">
|
|
2374
|
+
<div class="panel">
|
|
2375
|
+
<div class="panel-header">
|
|
2376
|
+
<div class="panel-title">Sovereignty Health Report</div>
|
|
2377
|
+
<button class="panel-action" id="copy-shr-btn">Copy JSON</button>
|
|
2378
|
+
</div>
|
|
2379
|
+
<div class="panel-content">
|
|
2380
|
+
<div class="shr-json" id="shr-viewer">
|
|
2381
|
+
<div class="empty-state">Loading SHR...</div>
|
|
2382
|
+
</div>
|
|
2383
|
+
</div>
|
|
2384
|
+
</div>
|
|
2385
|
+
|
|
2386
|
+
<div class="panel">
|
|
2387
|
+
<div class="panel-header">
|
|
2388
|
+
<div class="panel-title">Activity Feed</div>
|
|
2389
|
+
</div>
|
|
2390
|
+
<div class="panel-content">
|
|
2391
|
+
<div id="activity-feed" class="activity-feed">
|
|
2392
|
+
<div class="empty-state">Waiting for activity...</div>
|
|
2393
|
+
</div>
|
|
2394
|
+
</div>
|
|
2395
|
+
</div>
|
|
2396
|
+
</div>
|
|
2397
|
+
|
|
2398
|
+
<!-- Row 4: Handshake History -->
|
|
2399
|
+
<div class="handshake-table">
|
|
2400
|
+
<div class="table-header">
|
|
2401
|
+
<div>Counterparty</div>
|
|
2402
|
+
<div>Trust Tier</div>
|
|
2403
|
+
<div>Sovereignty</div>
|
|
2404
|
+
<div>Verified</div>
|
|
2405
|
+
<div>Completed</div>
|
|
2406
|
+
<div>Expires</div>
|
|
2407
|
+
</div>
|
|
2408
|
+
<div class="table-rows" id="handshake-table">
|
|
2409
|
+
<div class="table-empty">No handshakes completed yet</div>
|
|
2410
|
+
</div>
|
|
2411
|
+
</div>
|
|
2412
|
+
|
|
2413
|
+
<!-- Threat Panel -->
|
|
2414
|
+
<div class="threat-panel collapsed">
|
|
2415
|
+
<div class="threat-header">
|
|
2416
|
+
<div class="threat-title">Security Threats</div>
|
|
2417
|
+
<div class="threat-toggle">\u25B6</div>
|
|
2418
|
+
</div>
|
|
2419
|
+
<div class="threat-content" id="threat-alerts">
|
|
2420
|
+
<div class="empty-state">No threats detected</div>
|
|
2421
|
+
</div>
|
|
2122
2422
|
</div>
|
|
2123
2423
|
</div>
|
|
2124
2424
|
</div>
|
|
2125
2425
|
|
|
2126
|
-
<!--
|
|
2127
|
-
<div class="
|
|
2128
|
-
<div class="
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2426
|
+
<!-- Pending Overlay -->
|
|
2427
|
+
<div class="pending-overlay" id="pending-overlay">
|
|
2428
|
+
<div class="pending-header">Pending Approvals</div>
|
|
2429
|
+
<div class="pending-items" id="pending-items"></div>
|
|
2430
|
+
</div>
|
|
2431
|
+
|
|
2432
|
+
<script>
|
|
2433
|
+
// Constants
|
|
2434
|
+
const AUTH_TOKEN = '${options.authToken || ""}' || sessionStorage.getItem('authToken') || '';
|
|
2435
|
+
const TIMEOUT_SECONDS = ${options.timeoutSeconds};
|
|
2436
|
+
const API_BASE = '';
|
|
2437
|
+
|
|
2438
|
+
// State
|
|
2439
|
+
let apiState = {
|
|
2440
|
+
sovereignty: null,
|
|
2441
|
+
identity: null,
|
|
2442
|
+
handshakes: [],
|
|
2443
|
+
shr: null,
|
|
2444
|
+
status: null,
|
|
2445
|
+
};
|
|
2446
|
+
|
|
2447
|
+
let pendingRequests = new Map();
|
|
2448
|
+
let activityLog = [];
|
|
2449
|
+
const maxActivityItems = 50;
|
|
2450
|
+
|
|
2451
|
+
// Helpers
|
|
2452
|
+
function esc(text) {
|
|
2453
|
+
if (!text) return '';
|
|
2454
|
+
const div = document.createElement('div');
|
|
2455
|
+
div.textContent = text;
|
|
2456
|
+
return div.innerHTML;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function formatTime(isoString) {
|
|
2460
|
+
if (!isoString) return '\u2014';
|
|
2461
|
+
const date = new Date(isoString);
|
|
2462
|
+
return date.toLocaleString('en-US', {
|
|
2463
|
+
month: 'short',
|
|
2464
|
+
day: 'numeric',
|
|
2465
|
+
hour: '2-digit',
|
|
2466
|
+
minute: '2-digit',
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
function truncate(str, len = 16) {
|
|
2471
|
+
if (!str) return '\u2014';
|
|
2472
|
+
if (str.length <= len) return str;
|
|
2473
|
+
return str.slice(0, len) + '...';
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
function calculateSovereigntyScore(shr) {
|
|
2477
|
+
if (!shr || !shr.layers) return 0;
|
|
2478
|
+
const layers = shr.layers;
|
|
2479
|
+
let score = 100;
|
|
2480
|
+
|
|
2481
|
+
if (layers.l1?.status === 'degraded') score -= 20;
|
|
2482
|
+
if (layers.l1?.status === 'inactive') score -= 35;
|
|
2483
|
+
if (layers.l2?.status === 'degraded') score -= 15;
|
|
2484
|
+
if (layers.l2?.status === 'inactive') score -= 25;
|
|
2485
|
+
if (layers.l3?.status === 'degraded') score -= 15;
|
|
2486
|
+
if (layers.l3?.status === 'inactive') score -= 25;
|
|
2487
|
+
if (layers.l4?.status === 'degraded') score -= 10;
|
|
2488
|
+
if (layers.l4?.status === 'inactive') score -= 20;
|
|
2489
|
+
|
|
2490
|
+
return Math.max(0, Math.min(100, score));
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
async function fetchAPI(endpoint) {
|
|
2494
|
+
try {
|
|
2495
|
+
const response = await fetch(API_BASE + endpoint, {
|
|
2496
|
+
headers: {
|
|
2497
|
+
'Authorization': 'Bearer ' + AUTH_TOKEN,
|
|
2498
|
+
},
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2501
|
+
if (response.status === 401) {
|
|
2502
|
+
redirectToLogin();
|
|
2503
|
+
return null;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
if (!response.ok) {
|
|
2507
|
+
console.error('API Error:', response.status);
|
|
2508
|
+
return null;
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
return await response.json();
|
|
2512
|
+
} catch (err) {
|
|
2513
|
+
console.error('Fetch error:', err);
|
|
2514
|
+
return null;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
function redirectToLogin() {
|
|
2519
|
+
sessionStorage.removeItem('authToken');
|
|
2520
|
+
window.location.href = '/';
|
|
2521
|
+
}
|
|
2138
2522
|
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
<div class="protection-card-stat" id="approvalStat">T1: 2 | T2: 3</div>
|
|
2144
|
-
</div>
|
|
2523
|
+
// API Updates
|
|
2524
|
+
async function updateSovereignty() {
|
|
2525
|
+
const data = await fetchAPI('/api/sovereignty');
|
|
2526
|
+
if (!data) return;
|
|
2145
2527
|
|
|
2146
|
-
|
|
2147
|
-
<div class="protection-card-icon">\u{1F3AF}</div>
|
|
2148
|
-
<div class="protection-card-label">Context Gating</div>
|
|
2149
|
-
<div class="protection-card-status active" id="contextStatus">\u2713 Active</div>
|
|
2150
|
-
<div class="protection-card-stat" id="contextStat">12 filtered</div>
|
|
2151
|
-
</div>
|
|
2528
|
+
apiState.sovereignty = data;
|
|
2152
2529
|
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
<div class="protection-card-status active" id="injectionStatus">\u2713 Active</div>
|
|
2157
|
-
<div class="protection-card-stat" id="injectionStat">3 flags today</div>
|
|
2158
|
-
</div>
|
|
2530
|
+
const score = calculateSovereigntyScore(data.shr);
|
|
2531
|
+
const badge = document.getElementById('sovereignty-badge');
|
|
2532
|
+
const scoreEl = document.getElementById('sovereignty-score');
|
|
2159
2533
|
|
|
2160
|
-
|
|
2161
|
-
<div class="protection-card-icon">\u{1F4CA}</div>
|
|
2162
|
-
<div class="protection-card-label">Behavioral Baseline</div>
|
|
2163
|
-
<div class="protection-card-status active" id="baselineStatus">\u2713 Active</div>
|
|
2164
|
-
<div class="protection-card-stat" id="baselineStat">0 anomalies</div>
|
|
2165
|
-
</div>
|
|
2534
|
+
scoreEl.textContent = score;
|
|
2166
2535
|
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
<div class="protection-card-status active" id="auditStatus">\u2713 Active</div>
|
|
2171
|
-
<div class="protection-card-stat" id="auditStat">284 entries</div>
|
|
2172
|
-
</div>
|
|
2173
|
-
</div>
|
|
2174
|
-
</div>
|
|
2175
|
-
</div>
|
|
2536
|
+
badge.classList.remove('degraded', 'inactive');
|
|
2537
|
+
if (score < 70) badge.classList.add('degraded');
|
|
2538
|
+
if (score < 40) badge.classList.add('inactive');
|
|
2176
2539
|
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
<div class="pending-overlay-header">
|
|
2180
|
-
<div class="pending-overlay-title">Pending Approvals</div>
|
|
2181
|
-
<button class="pending-overlay-close" onclick="closePendingOverlay()">\xD7</button>
|
|
2182
|
-
</div>
|
|
2183
|
-
<div class="pending-list" id="pendingList"></div>
|
|
2184
|
-
</div>
|
|
2185
|
-
|
|
2186
|
-
<!-- Threat Panel (collapsible footer) -->
|
|
2187
|
-
<div class="threat-panel collapsed" id="threatPanel">
|
|
2188
|
-
<div class="threat-header" onclick="toggleThreatPanel()">
|
|
2189
|
-
<span class="threat-icon">\u26A0</span>
|
|
2190
|
-
Recent Threats
|
|
2191
|
-
<span id="threatCount" style="margin-left: auto; color: var(--red); font-weight: 700;">0</span>
|
|
2192
|
-
</div>
|
|
2193
|
-
<div class="threat-content" id="threatContent">
|
|
2194
|
-
<div class="threat-empty">No threats detected</div>
|
|
2195
|
-
</div>
|
|
2196
|
-
</div>
|
|
2540
|
+
updateLayerCards(data.shr);
|
|
2541
|
+
}
|
|
2197
2542
|
|
|
2198
|
-
|
|
2199
|
-
(
|
|
2200
|
-
'use strict';
|
|
2543
|
+
function updateLayerCards(shr) {
|
|
2544
|
+
if (!shr || !shr.layers) return;
|
|
2201
2545
|
|
|
2202
|
-
|
|
2546
|
+
const layers = shr.layers;
|
|
2203
2547
|
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
const MAX_THREAT_ITEMS = 20;
|
|
2548
|
+
updateLayerCard('l1', layers.l1, layers.l1?.encryption || 'AES-256-GCM');
|
|
2549
|
+
updateLayerCard('l2', layers.l2, layers.l2?.isolation_type || 'Process-level');
|
|
2550
|
+
updateLayerCard('l3', layers.l3, layers.l3?.proof_system || 'Schnorr-Pedersen');
|
|
2551
|
+
updateLayerCard('l4', layers.l4, layers.l4?.reputation_mode || 'Weighted');
|
|
2552
|
+
}
|
|
2210
2553
|
|
|
2211
|
-
|
|
2554
|
+
function updateLayerCard(layer, layerData, detail) {
|
|
2555
|
+
if (!layerData) return;
|
|
2212
2556
|
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
let startTime = Date.now();
|
|
2216
|
-
let activityCount = 0;
|
|
2217
|
-
let threatCount = 0;
|
|
2218
|
-
const pendingRequests = new Map();
|
|
2219
|
-
const activityItems = [];
|
|
2220
|
-
const threatItems = [];
|
|
2221
|
-
let sovereigntyScore = 85;
|
|
2222
|
-
let sessionRenewalTimer = null;
|
|
2557
|
+
const card = document.querySelector(\`[data-layer="\${layer}"]\`);
|
|
2558
|
+
if (!card) return;
|
|
2223
2559
|
|
|
2224
|
-
|
|
2560
|
+
const status = layerData.status || 'inactive';
|
|
2561
|
+
card.classList.remove('degraded', 'inactive');
|
|
2225
2562
|
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2563
|
+
if (status === 'degraded') {
|
|
2564
|
+
card.classList.add('degraded');
|
|
2565
|
+
} else if (status === 'inactive') {
|
|
2566
|
+
card.classList.add('inactive');
|
|
2567
|
+
}
|
|
2231
2568
|
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
return url + sep + 'session=' + SESSION_ID;
|
|
2236
|
-
}
|
|
2569
|
+
document.getElementById(\`\${layer}-status\`).textContent = status.toUpperCase();
|
|
2570
|
+
document.getElementById(\`\${layer}-detail\`).textContent = detail;
|
|
2571
|
+
}
|
|
2237
2572
|
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
}
|
|
2573
|
+
async function updateIdentity() {
|
|
2574
|
+
const data = await fetchAPI('/api/identity');
|
|
2575
|
+
if (!data) return;
|
|
2242
2576
|
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
// Schedule renewal at 80% of TTL
|
|
2254
|
-
if (sessionRenewalTimer) clearTimeout(sessionRenewalTimer);
|
|
2255
|
-
sessionRenewalTimer = setTimeout(function() {
|
|
2256
|
-
exchangeSession().then(function() { reconnectSSE(); });
|
|
2257
|
-
}, ttl * 800);
|
|
2258
|
-
} else if (resp.status === 401) {
|
|
2259
|
-
// Token invalid or expired \u2014 show non-destructive re-login overlay
|
|
2260
|
-
showSessionExpired();
|
|
2261
|
-
}
|
|
2262
|
-
} catch (e) {
|
|
2263
|
-
// Network error \u2014 retry in 30s
|
|
2264
|
-
if (sessionRenewalTimer) clearTimeout(sessionRenewalTimer);
|
|
2265
|
-
sessionRenewalTimer = setTimeout(function() {
|
|
2266
|
-
exchangeSession().then(function() { reconnectSSE(); });
|
|
2267
|
-
}, 30000);
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
function showSessionExpired() {
|
|
2272
|
-
// Clear stored token
|
|
2273
|
-
try { sessionStorage.removeItem('sanctuary_token'); } catch(_) {}
|
|
2274
|
-
// Redirect to login page
|
|
2275
|
-
document.cookie = 'sanctuary_session=; path=/; max-age=0';
|
|
2276
|
-
window.location.reload();
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
// \u2500\u2500 UI Utilities \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2280
|
-
|
|
2281
|
-
function esc(s) {
|
|
2282
|
-
const d = document.createElement('div');
|
|
2283
|
-
d.textContent = String(s || '');
|
|
2284
|
-
return d.innerHTML;
|
|
2285
|
-
}
|
|
2286
|
-
|
|
2287
|
-
function closePendingOverlay() {
|
|
2288
|
-
document.getElementById('pendingOverlay').classList.remove('active');
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
function toggleThreatPanel() {
|
|
2292
|
-
document.getElementById('threatPanel').classList.toggle('collapsed');
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
function updateUptime() {
|
|
2296
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
2297
|
-
const hours = Math.floor(elapsed / 3600);
|
|
2298
|
-
const mins = Math.floor((elapsed % 3600) / 60);
|
|
2299
|
-
const secs = elapsed % 60;
|
|
2300
|
-
let uptimeStr = '';
|
|
2301
|
-
if (hours > 0) uptimeStr += hours + 'h ';
|
|
2302
|
-
if (mins > 0) uptimeStr += mins + 'm ';
|
|
2303
|
-
uptimeStr += secs + 's';
|
|
2304
|
-
document.getElementById('uptimeText').textContent = uptimeStr;
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
// \u2500\u2500 Sovereignty Score \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2308
|
-
|
|
2309
|
-
function updateSovereigntyScore(score) {
|
|
2310
|
-
sovereigntyScore = Math.min(100, Math.max(0, score || 85));
|
|
2311
|
-
const badge = document.getElementById('sovereigntyScore');
|
|
2312
|
-
badge.textContent = sovereigntyScore;
|
|
2313
|
-
badge.className = 'sovereignty-score';
|
|
2314
|
-
if (sovereigntyScore >= 80) {
|
|
2315
|
-
badge.classList.add('high');
|
|
2316
|
-
} else if (sovereigntyScore >= 50) {
|
|
2317
|
-
badge.classList.add('medium');
|
|
2318
|
-
} else {
|
|
2319
|
-
badge.classList.add('low');
|
|
2577
|
+
apiState.identity = data;
|
|
2578
|
+
|
|
2579
|
+
const primary = data.primary || {};
|
|
2580
|
+
document.getElementById('identity-label').textContent = primary.label || '\u2014';
|
|
2581
|
+
document.getElementById('identity-did').textContent = truncate(primary.did, 24);
|
|
2582
|
+
document.getElementById('identity-did').title = primary.did || '';
|
|
2583
|
+
document.getElementById('identity-pubkey').textContent = truncate(primary.publicKey, 24);
|
|
2584
|
+
document.getElementById('identity-pubkey').title = primary.publicKey || '';
|
|
2585
|
+
document.getElementById('identity-created').textContent = formatTime(primary.createdAt);
|
|
2586
|
+
document.getElementById('identity-count').textContent = data.identities?.length || '\u2014';
|
|
2320
2587
|
}
|
|
2321
|
-
}
|
|
2322
2588
|
|
|
2323
|
-
|
|
2589
|
+
async function updateHandshakes() {
|
|
2590
|
+
const data = await fetchAPI('/api/handshakes');
|
|
2591
|
+
if (!data) return;
|
|
2324
2592
|
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
tool: tool || 'unknown_tool',
|
|
2341
|
-
outcome: outcome || 'executed',
|
|
2342
|
-
detail: detail || '',
|
|
2343
|
-
hasInjection: !!hasInjection,
|
|
2344
|
-
isContextGated: !!isContextGated
|
|
2345
|
-
};
|
|
2593
|
+
apiState.handshakes = data.handshakes || [];
|
|
2594
|
+
|
|
2595
|
+
document.getElementById('handshake-count').textContent = data.handshakes?.length || '0';
|
|
2596
|
+
|
|
2597
|
+
if (data.handshakes && data.handshakes.length > 0) {
|
|
2598
|
+
const latest = data.handshakes[0];
|
|
2599
|
+
document.getElementById('handshake-latest').textContent = truncate(latest.counterpartyId, 20);
|
|
2600
|
+
document.getElementById('handshake-latest').title = latest.counterpartyId || '';
|
|
2601
|
+
document.getElementById('handshake-tier').textContent = (latest.trustTier || 'Unverified').toUpperCase();
|
|
2602
|
+
document.getElementById('handshake-time').textContent = formatTime(latest.completedAt);
|
|
2603
|
+
} else {
|
|
2604
|
+
document.getElementById('handshake-latest').textContent = '\u2014';
|
|
2605
|
+
document.getElementById('handshake-tier').textContent = 'Unverified';
|
|
2606
|
+
document.getElementById('handshake-time').textContent = '\u2014';
|
|
2607
|
+
}
|
|
2346
2608
|
|
|
2347
|
-
|
|
2348
|
-
if (activityItems.length > MAX_ACTIVITY_ITEMS) {
|
|
2349
|
-
activityItems.pop();
|
|
2609
|
+
updateHandshakeTable(data.handshakes || []);
|
|
2350
2610
|
}
|
|
2351
2611
|
|
|
2352
|
-
|
|
2353
|
-
|
|
2612
|
+
function updateHandshakeTable(handshakes) {
|
|
2613
|
+
const table = document.getElementById('handshake-table');
|
|
2354
2614
|
|
|
2355
|
-
|
|
2356
|
-
|
|
2615
|
+
if (!handshakes || handshakes.length === 0) {
|
|
2616
|
+
table.innerHTML = '<div class="table-empty">No handshakes completed yet</div>';
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2357
2619
|
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2620
|
+
table.innerHTML = handshakes
|
|
2621
|
+
.map(
|
|
2622
|
+
(hs) => \`
|
|
2623
|
+
<div class="table-row">
|
|
2624
|
+
<div class="table-cell strong">\${esc(truncate(hs.counterpartyId, 24))}</div>
|
|
2625
|
+
<div class="table-cell">\${esc(hs.trustTier || 'Unverified')}</div>
|
|
2626
|
+
<div class="table-cell">\${esc(hs.sovereigntyLevel || '\u2014')}</div>
|
|
2627
|
+
<div class="table-cell">\${hs.verified ? 'Yes' : 'No'}</div>
|
|
2628
|
+
<div class="table-cell">\${formatTime(hs.completedAt)}</div>
|
|
2629
|
+
<div class="table-cell">\${formatTime(hs.expiresAt)}</div>
|
|
2630
|
+
</div>
|
|
2631
|
+
\`
|
|
2632
|
+
)
|
|
2633
|
+
.join('');
|
|
2361
2634
|
}
|
|
2362
2635
|
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
tr.className = 'activity-item';
|
|
2367
|
-
tr.id = item.id;
|
|
2368
|
-
|
|
2369
|
-
const time = new Date(item.timestamp);
|
|
2370
|
-
const timeStr = time.toLocaleTimeString();
|
|
2371
|
-
|
|
2372
|
-
const tierClass = 't' + item.tier;
|
|
2373
|
-
const outcomeClass = item.outcome === 'denied' ? 'outcome denied' : 'outcome';
|
|
2374
|
-
|
|
2375
|
-
let icon = '\u25CF';
|
|
2376
|
-
if (item.isContextGated) icon = '\u{1F3AF}';
|
|
2377
|
-
else if (item.hasInjection) icon = '\u26A0';
|
|
2378
|
-
else if (item.outcome === 'denied') icon = '\u2717';
|
|
2379
|
-
else icon = '\u2713';
|
|
2380
|
-
|
|
2381
|
-
tr.innerHTML =
|
|
2382
|
-
'<div class="activity-item-icon">' + esc(icon) + '</div>' +
|
|
2383
|
-
'<div class="activity-item-content">' +
|
|
2384
|
-
'<div class="activity-time">' + esc(timeStr) + '</div>' +
|
|
2385
|
-
'<div class="activity-main">' +
|
|
2386
|
-
'<span class="activity-tier ' + tierClass + '">T' + item.tier + '</span>' +
|
|
2387
|
-
'<span class="activity-tool">' + esc(item.tool) + '</span>' +
|
|
2388
|
-
'<span class="activity-outcome ' + (outcomeClass === 'outcome denied' ? 'denied' : '') + '">' + (item.outcome === 'denied' ? '\u2717 denied' : '\u2713 allowed') + '</span>' +
|
|
2389
|
-
'</div>' +
|
|
2390
|
-
'<div class="activity-detail">' + esc(item.detail) + '</div>' +
|
|
2391
|
-
'</div>' +
|
|
2392
|
-
'';
|
|
2393
|
-
|
|
2394
|
-
tr.addEventListener('click', () => {
|
|
2395
|
-
tr.classList.toggle('expanded');
|
|
2396
|
-
});
|
|
2636
|
+
async function updateSHR() {
|
|
2637
|
+
const data = await fetchAPI('/api/shr');
|
|
2638
|
+
if (!data) return;
|
|
2397
2639
|
|
|
2398
|
-
|
|
2640
|
+
apiState.shr = data;
|
|
2641
|
+
renderSHRViewer(data);
|
|
2399
2642
|
}
|
|
2400
|
-
}
|
|
2401
2643
|
|
|
2402
|
-
|
|
2644
|
+
function renderSHRViewer(shr) {
|
|
2645
|
+
const viewer = document.getElementById('shr-viewer');
|
|
2403
2646
|
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
tier,
|
|
2409
|
-
reason,
|
|
2410
|
-
context,
|
|
2411
|
-
timestamp
|
|
2412
|
-
} = data;
|
|
2413
|
-
|
|
2414
|
-
const pending = {
|
|
2415
|
-
id: request_id,
|
|
2416
|
-
operation: operation || 'unknown',
|
|
2417
|
-
tier: tier || 1,
|
|
2418
|
-
reason: reason || '',
|
|
2419
|
-
context: context || {},
|
|
2420
|
-
timestamp: timestamp || new Date().toISOString(),
|
|
2421
|
-
remaining: TIMEOUT_SECONDS
|
|
2422
|
-
};
|
|
2647
|
+
if (!shr) {
|
|
2648
|
+
viewer.innerHTML = '<div class="empty-state">No SHR available</div>';
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2423
2651
|
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2652
|
+
let html = '';
|
|
2653
|
+
|
|
2654
|
+
// Implementation
|
|
2655
|
+
html += \`
|
|
2656
|
+
<div class="shr-section">
|
|
2657
|
+
<div class="shr-section-header">
|
|
2658
|
+
<div class="shr-toggle">\u25BC</div>
|
|
2659
|
+
<div>Implementation</div>
|
|
2660
|
+
</div>
|
|
2661
|
+
<div class="shr-section-content">
|
|
2662
|
+
<div class="shr-item">
|
|
2663
|
+
<div class="shr-key">sanctuary_version:</div>
|
|
2664
|
+
<div class="shr-value">\${esc(shr.implementation?.sanctuary_version || '\u2014')}</div>
|
|
2665
|
+
</div>
|
|
2666
|
+
<div class="shr-item">
|
|
2667
|
+
<div class="shr-key">node_version:</div>
|
|
2668
|
+
<div class="shr-value">\${esc(shr.implementation?.node_version || '\u2014')}</div>
|
|
2669
|
+
</div>
|
|
2670
|
+
<div class="shr-item">
|
|
2671
|
+
<div class="shr-key">generated_by:</div>
|
|
2672
|
+
<div class="shr-value">\${esc(shr.implementation?.generated_by || '\u2014')}</div>
|
|
2673
|
+
</div>
|
|
2674
|
+
</div>
|
|
2675
|
+
</div>
|
|
2676
|
+
\`;
|
|
2677
|
+
|
|
2678
|
+
// Metadata
|
|
2679
|
+
html += \`
|
|
2680
|
+
<div class="shr-section">
|
|
2681
|
+
<div class="shr-section-header">
|
|
2682
|
+
<div class="shr-toggle">\u25BC</div>
|
|
2683
|
+
<div>Metadata</div>
|
|
2684
|
+
</div>
|
|
2685
|
+
<div class="shr-section-content">
|
|
2686
|
+
<div class="shr-item">
|
|
2687
|
+
<div class="shr-key">instance_id:</div>
|
|
2688
|
+
<div class="shr-value">\${esc(truncate(shr.instance_id, 20))}</div>
|
|
2689
|
+
</div>
|
|
2690
|
+
<div class="shr-item">
|
|
2691
|
+
<div class="shr-key">generated_at:</div>
|
|
2692
|
+
<div class="shr-value">\${formatTime(shr.generated_at)}</div>
|
|
2693
|
+
</div>
|
|
2694
|
+
<div class="shr-item">
|
|
2695
|
+
<div class="shr-key">expires_at:</div>
|
|
2696
|
+
<div class="shr-value">\${formatTime(shr.expires_at)}</div>
|
|
2697
|
+
</div>
|
|
2698
|
+
</div>
|
|
2699
|
+
</div>
|
|
2700
|
+
\`;
|
|
2701
|
+
|
|
2702
|
+
// Layers
|
|
2703
|
+
if (shr.layers) {
|
|
2704
|
+
html += \`<div class="shr-section">
|
|
2705
|
+
<div class="shr-section-header">
|
|
2706
|
+
<div class="shr-toggle">\u25BC</div>
|
|
2707
|
+
<div>Layers</div>
|
|
2708
|
+
</div>
|
|
2709
|
+
<div class="shr-section-content">
|
|
2710
|
+
\`;
|
|
2711
|
+
|
|
2712
|
+
for (const [key, layer] of Object.entries(shr.layers)) {
|
|
2713
|
+
html += \`
|
|
2714
|
+
<div style="margin-bottom: 12px;">
|
|
2715
|
+
<div style="color: var(--blue); font-weight: 600; margin-bottom: 4px;">\${esc(key)}</div>
|
|
2716
|
+
<div style="padding-left: 12px;">
|
|
2717
|
+
\`;
|
|
2718
|
+
|
|
2719
|
+
for (const [lkey, lvalue] of Object.entries(layer || {})) {
|
|
2720
|
+
const displayValue =
|
|
2721
|
+
typeof lvalue === 'boolean'
|
|
2722
|
+
? lvalue
|
|
2723
|
+
? 'true'
|
|
2724
|
+
: 'false'
|
|
2725
|
+
: esc(String(lvalue));
|
|
2726
|
+
html += \`
|
|
2727
|
+
<div class="shr-item">
|
|
2728
|
+
<div class="shr-key">\${esc(lkey)}:</div>
|
|
2729
|
+
<div class="shr-value">\${displayValue}</div>
|
|
2730
|
+
</div>
|
|
2731
|
+
\`;
|
|
2732
|
+
}
|
|
2427
2733
|
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2734
|
+
html += \`
|
|
2735
|
+
</div>
|
|
2736
|
+
</div>
|
|
2737
|
+
\`;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
html += \`
|
|
2741
|
+
</div>
|
|
2742
|
+
</div>
|
|
2743
|
+
\`;
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
// Capabilities
|
|
2747
|
+
if (shr.capabilities) {
|
|
2748
|
+
html += \`
|
|
2749
|
+
<div class="shr-section">
|
|
2750
|
+
<div class="shr-section-header">
|
|
2751
|
+
<div class="shr-toggle">\u25BC</div>
|
|
2752
|
+
<div>Capabilities</div>
|
|
2753
|
+
</div>
|
|
2754
|
+
<div class="shr-section-content">
|
|
2755
|
+
\`;
|
|
2756
|
+
|
|
2757
|
+
for (const [key, value] of Object.entries(shr.capabilities)) {
|
|
2758
|
+
const displayValue = value ? 'true' : 'false';
|
|
2759
|
+
html += \`
|
|
2760
|
+
<div class="shr-item">
|
|
2761
|
+
<div class="shr-key">\${esc(key)}:</div>
|
|
2762
|
+
<div class="shr-value">\${displayValue}</div>
|
|
2763
|
+
</div>
|
|
2764
|
+
\`;
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
html += \`
|
|
2768
|
+
</div>
|
|
2769
|
+
</div>
|
|
2770
|
+
\`;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
// Signature
|
|
2774
|
+
html += \`
|
|
2775
|
+
<div class="shr-section">
|
|
2776
|
+
<div class="shr-section-header">
|
|
2777
|
+
<div class="shr-toggle">\u25BC</div>
|
|
2778
|
+
<div>Signature</div>
|
|
2779
|
+
</div>
|
|
2780
|
+
<div class="shr-section-content">
|
|
2781
|
+
<div class="shr-item">
|
|
2782
|
+
<div class="shr-key">signed_by:</div>
|
|
2783
|
+
<div class="shr-value">\${esc(truncate(shr.signed_by, 20))}</div>
|
|
2784
|
+
</div>
|
|
2785
|
+
<div class="shr-item">
|
|
2786
|
+
<div class="shr-key">signature:</div>
|
|
2787
|
+
<div class="shr-value">\${esc(truncate(shr.signature, 32))}</div>
|
|
2788
|
+
</div>
|
|
2789
|
+
</div>
|
|
2790
|
+
</div>
|
|
2791
|
+
\`;
|
|
2792
|
+
|
|
2793
|
+
viewer.innerHTML = html;
|
|
2794
|
+
|
|
2795
|
+
// Add collapse functionality
|
|
2796
|
+
document.querySelectorAll('.shr-section-header').forEach((header) => {
|
|
2797
|
+
header.addEventListener('click', () => {
|
|
2798
|
+
header.closest('.shr-section').classList.toggle('collapsed');
|
|
2799
|
+
});
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2432
2802
|
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2803
|
+
async function updateStatus() {
|
|
2804
|
+
const data = await fetchAPI('/api/status');
|
|
2805
|
+
if (!data) return;
|
|
2436
2806
|
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
document.getElementById('
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2807
|
+
apiState.status = data;
|
|
2808
|
+
|
|
2809
|
+
document.getElementById('protections-count').textContent = data.protectionsCount || '0';
|
|
2810
|
+
document.getElementById('uptime-value').textContent = formatUptime(data.uptime);
|
|
2811
|
+
|
|
2812
|
+
const connectionStatus = document.getElementById('connection-status');
|
|
2813
|
+
connectionStatus.classList.toggle('disconnected', !data.connected);
|
|
2444
2814
|
}
|
|
2445
2815
|
|
|
2446
|
-
|
|
2447
|
-
|
|
2816
|
+
function formatUptime(seconds) {
|
|
2817
|
+
if (!seconds) return '\u2014';
|
|
2818
|
+
const hours = Math.floor(seconds / 3600);
|
|
2819
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
2820
|
+
if (hours > 0) return \`\${hours}h \${minutes}m\`;
|
|
2821
|
+
return \`\${minutes}m\`;
|
|
2822
|
+
}
|
|
2448
2823
|
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2824
|
+
// SSE Setup
|
|
2825
|
+
function setupSSE() {
|
|
2826
|
+
const eventSource = new EventSource(API_BASE + '/api/events', {
|
|
2827
|
+
headers: {
|
|
2828
|
+
'Authorization': 'Bearer ' + AUTH_TOKEN,
|
|
2829
|
+
},
|
|
2830
|
+
});
|
|
2452
2831
|
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2832
|
+
eventSource.addEventListener('init', (e) => {
|
|
2833
|
+
console.log('Connected to SSE');
|
|
2834
|
+
});
|
|
2456
2835
|
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
const isUrgent = req.remaining <= 30;
|
|
2836
|
+
eventSource.addEventListener('sovereignty-update', () => {
|
|
2837
|
+
updateSovereignty();
|
|
2838
|
+
});
|
|
2461
2839
|
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
'<div class="pending-item-tier ' + tierClass + '">T' + tier + '</div>' +
|
|
2466
|
-
'</div>' +
|
|
2467
|
-
'<div class="pending-item-reason">' + esc(req.reason) + '</div>' +
|
|
2468
|
-
'<div class="pending-item-timer ' + (isUrgent ? 'urgent' : '') + '">' +
|
|
2469
|
-
'<div class="pending-item-timer-bar">' +
|
|
2470
|
-
'<div class="pending-item-timer-fill" style="width: ' + pct + '%"></div>' +
|
|
2471
|
-
'</div>' +
|
|
2472
|
-
'<span id="timer-' + id + '">' + req.remaining + 's</span>' +
|
|
2473
|
-
'</div>' +
|
|
2474
|
-
'<div class="pending-item-actions">' +
|
|
2475
|
-
'<button class="btn btn-approve" onclick="handleApprove('' + id + '')">Approve</button>' +
|
|
2476
|
-
'<button class="btn btn-deny" onclick="handleDeny('' + id + '')">Deny</button>' +
|
|
2477
|
-
'</div>' +
|
|
2478
|
-
'';
|
|
2840
|
+
eventSource.addEventListener('handshake-update', () => {
|
|
2841
|
+
updateHandshakes();
|
|
2842
|
+
});
|
|
2479
2843
|
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2844
|
+
eventSource.addEventListener('tool-call', (e) => {
|
|
2845
|
+
const data = JSON.parse(e.data);
|
|
2846
|
+
addActivityItem({
|
|
2847
|
+
type: 'tool-call',
|
|
2848
|
+
title: 'Tool Call',
|
|
2849
|
+
content: data.toolName,
|
|
2850
|
+
timestamp: new Date().toISOString(),
|
|
2851
|
+
});
|
|
2852
|
+
});
|
|
2483
2853
|
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2854
|
+
eventSource.addEventListener('context-gate-decision', (e) => {
|
|
2855
|
+
const data = JSON.parse(e.data);
|
|
2856
|
+
addActivityItem({
|
|
2857
|
+
type: 'context-gate',
|
|
2858
|
+
title: 'Context Gate',
|
|
2859
|
+
content: data.decision,
|
|
2860
|
+
timestamp: new Date().toISOString(),
|
|
2861
|
+
});
|
|
2862
|
+
});
|
|
2489
2863
|
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2864
|
+
eventSource.addEventListener('injection-alert', (e) => {
|
|
2865
|
+
const data = JSON.parse(e.data);
|
|
2866
|
+
addActivityItem({
|
|
2867
|
+
type: 'injection',
|
|
2868
|
+
title: 'Injection Alert',
|
|
2869
|
+
content: data.pattern,
|
|
2870
|
+
timestamp: new Date().toISOString(),
|
|
2871
|
+
});
|
|
2872
|
+
addThreatAlert(data);
|
|
2873
|
+
});
|
|
2495
2874
|
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
timestamp,
|
|
2501
|
-
severity,
|
|
2502
|
-
type,
|
|
2503
|
-
details
|
|
2504
|
-
} = data;
|
|
2505
|
-
|
|
2506
|
-
const threat = {
|
|
2507
|
-
id: 'threat-' + threatCount++,
|
|
2508
|
-
timestamp: timestamp || new Date().toISOString(),
|
|
2509
|
-
severity: severity || 'medium',
|
|
2510
|
-
type: type || 'unknown',
|
|
2511
|
-
details: details || ''
|
|
2512
|
-
};
|
|
2875
|
+
eventSource.addEventListener('pending-request', (e) => {
|
|
2876
|
+
const data = JSON.parse(e.data);
|
|
2877
|
+
addPendingRequest(data);
|
|
2878
|
+
});
|
|
2513
2879
|
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2880
|
+
eventSource.addEventListener('request-resolved', (e) => {
|
|
2881
|
+
const data = JSON.parse(e.data);
|
|
2882
|
+
removePendingRequest(data.requestId);
|
|
2883
|
+
});
|
|
2518
2884
|
|
|
2519
|
-
|
|
2520
|
-
|
|
2885
|
+
eventSource.onerror = () => {
|
|
2886
|
+
console.error('SSE error');
|
|
2887
|
+
setTimeout(setupSSE, 5000);
|
|
2888
|
+
};
|
|
2521
2889
|
}
|
|
2522
2890
|
|
|
2523
|
-
|
|
2524
|
-
|
|
2891
|
+
// Activity Feed
|
|
2892
|
+
function addActivityItem(item) {
|
|
2893
|
+
activityLog.unshift(item);
|
|
2894
|
+
if (activityLog.length > maxActivityItems) {
|
|
2895
|
+
activityLog.pop();
|
|
2896
|
+
}
|
|
2525
2897
|
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2898
|
+
const feed = document.getElementById('activity-feed');
|
|
2899
|
+
const html = \`
|
|
2900
|
+
<div class="activity-item \${item.type}">
|
|
2901
|
+
<div class="activity-type">\${esc(item.title)}</div>
|
|
2902
|
+
<div class="activity-content">\${esc(item.content)}</div>
|
|
2903
|
+
<div class="activity-time">\${formatTime(item.timestamp)}</div>
|
|
2904
|
+
</div>
|
|
2905
|
+
\`;
|
|
2529
2906
|
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
return;
|
|
2534
|
-
}
|
|
2907
|
+
if (feed.querySelector('.empty-state')) {
|
|
2908
|
+
feed.innerHTML = '';
|
|
2909
|
+
}
|
|
2535
2910
|
|
|
2536
|
-
|
|
2537
|
-
content.innerHTML = '';
|
|
2911
|
+
feed.insertAdjacentHTML('afterbegin', html);
|
|
2538
2912
|
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
const time = new Date(threat.timestamp).toLocaleTimeString();
|
|
2543
|
-
div.innerHTML =
|
|
2544
|
-
'<div style="margin-bottom: 3px;">' +
|
|
2545
|
-
'<span class="threat-item-type">' + esc(threat.type) + '</span>' +
|
|
2546
|
-
'<span style="font-size: 10px; color: var(--text-secondary); margin-left: 6px;">' + esc(time) + '</span>' +
|
|
2547
|
-
'</div>' +
|
|
2548
|
-
'<div>' + esc(threat.details) + '</div>' +
|
|
2549
|
-
'';
|
|
2550
|
-
content.appendChild(div);
|
|
2913
|
+
if (feed.children.length > maxActivityItems) {
|
|
2914
|
+
feed.lastChild.remove();
|
|
2915
|
+
}
|
|
2551
2916
|
}
|
|
2552
|
-
}
|
|
2553
|
-
|
|
2554
|
-
// \u2500\u2500 SSE Connection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2555
2917
|
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2918
|
+
// Pending Requests
|
|
2919
|
+
function addPendingRequest(request) {
|
|
2920
|
+
pendingRequests.set(request.requestId, {
|
|
2921
|
+
id: request.requestId,
|
|
2922
|
+
title: request.title,
|
|
2923
|
+
details: request.details,
|
|
2924
|
+
expiresAt: new Date(Date.now() + TIMEOUT_SECONDS * 1000),
|
|
2925
|
+
});
|
|
2560
2926
|
|
|
2561
|
-
|
|
2562
|
-
|
|
2927
|
+
updatePendingDisplay();
|
|
2928
|
+
}
|
|
2563
2929
|
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2930
|
+
function removePendingRequest(requestId) {
|
|
2931
|
+
pendingRequests.delete(requestId);
|
|
2932
|
+
updatePendingDisplay();
|
|
2933
|
+
}
|
|
2567
2934
|
|
|
2568
|
-
|
|
2569
|
-
document.getElementById('
|
|
2570
|
-
|
|
2935
|
+
function updatePendingDisplay() {
|
|
2936
|
+
const badge = document.getElementById('pending-item-badge');
|
|
2937
|
+
const count = pendingRequests.size;
|
|
2571
2938
|
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
if (data.policy) {
|
|
2578
|
-
updatePolicy(data.policy);
|
|
2939
|
+
if (count > 0) {
|
|
2940
|
+
document.getElementById('pending-count').textContent = count;
|
|
2941
|
+
badge.style.display = 'flex';
|
|
2942
|
+
} else {
|
|
2943
|
+
badge.style.display = 'none';
|
|
2579
2944
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2945
|
+
|
|
2946
|
+
const overlay = document.getElementById('pending-overlay');
|
|
2947
|
+
const items = document.getElementById('pending-items');
|
|
2948
|
+
|
|
2949
|
+
if (count === 0) {
|
|
2950
|
+
items.innerHTML = '';
|
|
2951
|
+
overlay.classList.remove('show');
|
|
2952
|
+
return;
|
|
2582
2953
|
}
|
|
2583
|
-
});
|
|
2584
2954
|
|
|
2585
|
-
|
|
2586
|
-
const
|
|
2587
|
-
|
|
2588
|
-
|
|
2955
|
+
let html = '';
|
|
2956
|
+
for (const req of pendingRequests.values()) {
|
|
2957
|
+
const remaining = Math.max(0, Math.floor((req.expiresAt - Date.now()) / 1000));
|
|
2958
|
+
html += \`
|
|
2959
|
+
<div class="pending-item">
|
|
2960
|
+
<div class="pending-title">\${esc(req.title)}</div>
|
|
2961
|
+
<div class="pending-countdown">Expires in \${remaining}s</div>
|
|
2962
|
+
<div class="pending-actions">
|
|
2963
|
+
<button class="pending-btn pending-approve" data-id="\${req.id}">Approve</button>
|
|
2964
|
+
<button class="pending-btn pending-deny" data-id="\${req.id}">Deny</button>
|
|
2965
|
+
</div>
|
|
2966
|
+
</div>
|
|
2967
|
+
\`;
|
|
2968
|
+
}
|
|
2589
2969
|
|
|
2590
|
-
|
|
2591
|
-
const data = JSON.parse(e.data);
|
|
2592
|
-
removePendingRequest(data.request_id);
|
|
2593
|
-
});
|
|
2970
|
+
items.innerHTML = html;
|
|
2594
2971
|
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
tool: data.tool || 'unknown',
|
|
2601
|
-
outcome: data.outcome || 'executed',
|
|
2602
|
-
detail: data.detail || ''
|
|
2972
|
+
document.querySelectorAll('.pending-approve').forEach((btn) => {
|
|
2973
|
+
btn.addEventListener('click', async () => {
|
|
2974
|
+
const id = btn.getAttribute('data-id');
|
|
2975
|
+
await fetchAPI(\`/api/approve/\${id}\`);
|
|
2976
|
+
});
|
|
2603
2977
|
});
|
|
2604
|
-
});
|
|
2605
2978
|
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
tool: data.tool || 'unknown',
|
|
2612
|
-
outcome: data.outcome || 'gated',
|
|
2613
|
-
detail: data.fields_filtered ? 'Filtered ' + data.fields_filtered + ' fields' : data.reason || '',
|
|
2614
|
-
isContextGated: true
|
|
2979
|
+
document.querySelectorAll('.pending-deny').forEach((btn) => {
|
|
2980
|
+
btn.addEventListener('click', async () => {
|
|
2981
|
+
const id = btn.getAttribute('data-id');
|
|
2982
|
+
await fetchAPI(\`/api/deny/\${id}\`);
|
|
2983
|
+
});
|
|
2615
2984
|
});
|
|
2616
|
-
}
|
|
2985
|
+
}
|
|
2617
2986
|
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
tier: data.tier || 2,
|
|
2623
|
-
tool: data.tool || 'unknown',
|
|
2624
|
-
outcome: data.allowed ? 'allowed' : 'denied',
|
|
2625
|
-
detail: data.signal || 'Injection detected',
|
|
2626
|
-
hasInjection: true
|
|
2627
|
-
});
|
|
2628
|
-
addThreat({
|
|
2629
|
-
timestamp: data.timestamp,
|
|
2630
|
-
severity: data.severity || 'medium',
|
|
2631
|
-
type: 'Injection Alert',
|
|
2632
|
-
details: data.signal || 'Suspicious pattern detected'
|
|
2633
|
-
});
|
|
2634
|
-
});
|
|
2987
|
+
// Threat Panel
|
|
2988
|
+
function addThreatAlert(alert) {
|
|
2989
|
+
const panel = document.querySelector('.threat-panel');
|
|
2990
|
+
const content = document.getElementById('threat-alerts');
|
|
2635
2991
|
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
});
|
|
2992
|
+
if (content.querySelector('.empty-state')) {
|
|
2993
|
+
content.innerHTML = '';
|
|
2994
|
+
}
|
|
2640
2995
|
|
|
2641
|
-
|
|
2642
|
-
const data = JSON.parse(e.data);
|
|
2643
|
-
// Audit entries don't show in activity by default, but we could add them
|
|
2644
|
-
});
|
|
2996
|
+
panel.classList.remove('collapsed');
|
|
2645
2997
|
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2998
|
+
const html = \`
|
|
2999
|
+
<div class="threat-alert">
|
|
3000
|
+
<div class="threat-type">\${esc(alert.type || 'Injection Alert')}</div>
|
|
3001
|
+
<div class="threat-message">\${esc(alert.message || alert.pattern || '\u2014')}</div>
|
|
3002
|
+
</div>
|
|
3003
|
+
\`;
|
|
2651
3004
|
|
|
2652
|
-
|
|
2653
|
-
if (!baseline) return;
|
|
2654
|
-
// Update baseline-derived stats if needed
|
|
2655
|
-
}
|
|
3005
|
+
content.insertAdjacentHTML('afterbegin', html);
|
|
2656
3006
|
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
// Policy info updated
|
|
3007
|
+
const alerts = content.querySelectorAll('.threat-alert');
|
|
3008
|
+
if (alerts.length > 10) {
|
|
3009
|
+
alerts[alerts.length - 1].remove();
|
|
3010
|
+
}
|
|
2662
3011
|
}
|
|
2663
|
-
}
|
|
2664
3012
|
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
}
|
|
2669
|
-
if (status.active_protections !== undefined) {
|
|
2670
|
-
document.getElementById('activeProtections').textContent = status.active_protections;
|
|
2671
|
-
}
|
|
2672
|
-
// Update individual protection cards
|
|
2673
|
-
if (status.encryption !== undefined) {
|
|
2674
|
-
const el = document.getElementById('encryptionStatus');
|
|
2675
|
-
el.className = 'protection-card-status ' + (status.encryption ? 'active' : 'inactive');
|
|
2676
|
-
el.textContent = status.encryption ? '\u2713 Active' : '\u2717 Inactive';
|
|
2677
|
-
}
|
|
2678
|
-
if (status.approval_gate !== undefined) {
|
|
2679
|
-
const el = document.getElementById('approvalStatus');
|
|
2680
|
-
el.className = 'protection-card-status ' + (status.approval_gate ? 'active' : 'inactive');
|
|
2681
|
-
el.textContent = status.approval_gate ? '\u2713 Active' : '\u2717 Inactive';
|
|
2682
|
-
}
|
|
2683
|
-
if (status.context_gating !== undefined) {
|
|
2684
|
-
const el = document.getElementById('contextStatus');
|
|
2685
|
-
el.className = 'protection-card-status ' + (status.context_gating ? 'active' : 'inactive');
|
|
2686
|
-
el.textContent = status.context_gating ? '\u2713 Active' : '\u2717 Inactive';
|
|
2687
|
-
}
|
|
2688
|
-
if (status.injection_detection !== undefined) {
|
|
2689
|
-
const el = document.getElementById('injectionStatus');
|
|
2690
|
-
el.className = 'protection-card-status ' + (status.injection_detection ? 'active' : 'inactive');
|
|
2691
|
-
el.textContent = status.injection_detection ? '\u2713 Active' : '\u2717 Inactive';
|
|
2692
|
-
}
|
|
2693
|
-
if (status.baseline !== undefined) {
|
|
2694
|
-
const el = document.getElementById('baselineStatus');
|
|
2695
|
-
el.className = 'protection-card-status ' + (status.baseline ? 'active' : 'inactive');
|
|
2696
|
-
el.textContent = status.baseline ? '\u2713 Active' : '\u2717 Inactive';
|
|
2697
|
-
}
|
|
2698
|
-
if (status.audit_trail !== undefined) {
|
|
2699
|
-
const el = document.getElementById('auditStatus');
|
|
2700
|
-
el.className = 'protection-card-status ' + (status.audit_trail ? 'active' : 'inactive');
|
|
2701
|
-
el.textContent = status.audit_trail ? '\u2713 Active' : '\u2717 Inactive';
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
3013
|
+
// Threat Panel Toggle
|
|
3014
|
+
document.querySelector('.threat-header').addEventListener('click', () => {
|
|
3015
|
+
document.querySelector('.threat-panel').classList.toggle('collapsed');
|
|
3016
|
+
});
|
|
2704
3017
|
|
|
2705
|
-
|
|
3018
|
+
// SHR Copy Button
|
|
3019
|
+
document.getElementById('copy-shr-btn').addEventListener('click', async () => {
|
|
3020
|
+
if (!apiState.shr) return;
|
|
2706
3021
|
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
3022
|
+
const json = JSON.stringify(apiState.shr, null, 2);
|
|
3023
|
+
try {
|
|
3024
|
+
await navigator.clipboard.writeText(json);
|
|
3025
|
+
const btn = document.getElementById('copy-shr-btn');
|
|
3026
|
+
const original = btn.textContent;
|
|
3027
|
+
btn.textContent = 'Copied!';
|
|
3028
|
+
setTimeout(() => {
|
|
3029
|
+
btn.textContent = original;
|
|
3030
|
+
}, 2000);
|
|
3031
|
+
} catch (err) {
|
|
3032
|
+
console.error('Copy failed:', err);
|
|
3033
|
+
}
|
|
3034
|
+
});
|
|
2714
3035
|
|
|
2715
|
-
//
|
|
2716
|
-
|
|
2717
|
-
|
|
3036
|
+
// Pending Overlay Toggle
|
|
3037
|
+
document.getElementById('pending-item-badge').addEventListener('click', () => {
|
|
3038
|
+
document.getElementById('pending-overlay').classList.toggle('show');
|
|
3039
|
+
});
|
|
2718
3040
|
|
|
2719
|
-
//
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
if (el) {
|
|
2725
|
-
el.textContent = req.remaining + 's';
|
|
2726
|
-
}
|
|
3041
|
+
// Initialize
|
|
3042
|
+
async function initialize() {
|
|
3043
|
+
if (!AUTH_TOKEN) {
|
|
3044
|
+
redirectToLogin();
|
|
3045
|
+
return;
|
|
2727
3046
|
}
|
|
2728
|
-
}, 1000);
|
|
2729
3047
|
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
//
|
|
2740
|
-
|
|
2741
|
-
})();
|
|
3048
|
+
// Initial data fetch
|
|
3049
|
+
await Promise.all([
|
|
3050
|
+
updateSovereignty(),
|
|
3051
|
+
updateIdentity(),
|
|
3052
|
+
updateHandshakes(),
|
|
3053
|
+
updateSHR(),
|
|
3054
|
+
updateStatus(),
|
|
3055
|
+
]);
|
|
3056
|
+
|
|
3057
|
+
// Setup SSE for real-time updates
|
|
3058
|
+
setupSSE();
|
|
2742
3059
|
|
|
2743
|
-
|
|
2744
|
-
|
|
3060
|
+
// Refresh status periodically
|
|
3061
|
+
setInterval(updateStatus, 30000);
|
|
3062
|
+
}
|
|
2745
3063
|
|
|
3064
|
+
// Start
|
|
3065
|
+
initialize();
|
|
3066
|
+
</script>
|
|
2746
3067
|
</body>
|
|
2747
3068
|
</html>`;
|
|
2748
3069
|
}
|
|
@@ -7633,15 +7954,6 @@ function generateSHR(identityId, opts) {
|
|
|
7633
7954
|
mitigation: "TEE attestation planned for a future release"
|
|
7634
7955
|
});
|
|
7635
7956
|
}
|
|
7636
|
-
if (config.disclosure.proof_system === "commitment-only") {
|
|
7637
|
-
degradations.push({
|
|
7638
|
-
layer: "l3",
|
|
7639
|
-
code: "COMMITMENT_ONLY",
|
|
7640
|
-
severity: "info",
|
|
7641
|
-
description: "Commitment schemes only (no ZK proofs)",
|
|
7642
|
-
mitigation: "ZK proof support planned for future release"
|
|
7643
|
-
});
|
|
7644
|
-
}
|
|
7645
7957
|
const body = {
|
|
7646
7958
|
shr_version: "1.0",
|
|
7647
7959
|
implementation: {
|
|
@@ -7667,9 +7979,9 @@ function generateSHR(identityId, opts) {
|
|
|
7667
7979
|
attestation_available: config.execution.attestation
|
|
7668
7980
|
},
|
|
7669
7981
|
l3: {
|
|
7670
|
-
status:
|
|
7982
|
+
status: "active",
|
|
7671
7983
|
proof_system: config.disclosure.proof_system,
|
|
7672
|
-
selective_disclosure:
|
|
7984
|
+
selective_disclosure: true
|
|
7673
7985
|
},
|
|
7674
7986
|
l4: {
|
|
7675
7987
|
status: "active",
|
|
@@ -7882,7 +8194,7 @@ function extractAuthorizationSignals(body) {
|
|
|
7882
8194
|
behavioral_baseline_active: false,
|
|
7883
8195
|
// Would need explicit field in SHR v1.1
|
|
7884
8196
|
identity_verified: l1.identity_type === "ed25519" || l1.identity_type !== "none",
|
|
7885
|
-
zero_knowledge_capable: l3.status === "active"
|
|
8197
|
+
zero_knowledge_capable: l3.status === "active",
|
|
7886
8198
|
selective_disclosure_active: l3.selective_disclosure,
|
|
7887
8199
|
reputation_portable: l4.reputation_portable,
|
|
7888
8200
|
handshake_capable: body.capabilities.handshake
|
|
@@ -7960,14 +8272,6 @@ function generateAuthorizationConstraints(body, _degradations) {
|
|
|
7960
8272
|
priority: "high"
|
|
7961
8273
|
});
|
|
7962
8274
|
}
|
|
7963
|
-
if (layers.l3.proof_system === "commitment-only") {
|
|
7964
|
-
constraints.push({
|
|
7965
|
-
type: "restricted_scope",
|
|
7966
|
-
description: "No zero-knowledge proofs available \u2014 entire state context may be visible",
|
|
7967
|
-
rationale: "Proof system is commitment-only (no ZK)",
|
|
7968
|
-
priority: "medium"
|
|
7969
|
-
});
|
|
7970
|
-
}
|
|
7971
8275
|
if (layers.l4.status === "degraded") {
|
|
7972
8276
|
constraints.push({
|
|
7973
8277
|
type: "known_agents_only",
|
|
@@ -8301,6 +8605,155 @@ function deriveTrustTier(level) {
|
|
|
8301
8605
|
}
|
|
8302
8606
|
}
|
|
8303
8607
|
|
|
8608
|
+
// src/handshake/attestation.ts
|
|
8609
|
+
init_encoding();
|
|
8610
|
+
init_key_derivation();
|
|
8611
|
+
init_encoding();
|
|
8612
|
+
var ATTESTATION_VERSION = "1.0";
|
|
8613
|
+
function deriveTrustTier2(level) {
|
|
8614
|
+
switch (level) {
|
|
8615
|
+
case "full":
|
|
8616
|
+
return "verified-sovereign";
|
|
8617
|
+
case "degraded":
|
|
8618
|
+
return "verified-degraded";
|
|
8619
|
+
default:
|
|
8620
|
+
return "unverified";
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
function generateAttestation(opts) {
|
|
8624
|
+
const {
|
|
8625
|
+
attesterSHR,
|
|
8626
|
+
subjectSHR,
|
|
8627
|
+
verificationResult,
|
|
8628
|
+
mutual = false,
|
|
8629
|
+
identityManager,
|
|
8630
|
+
masterKey,
|
|
8631
|
+
identityId
|
|
8632
|
+
} = opts;
|
|
8633
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
8634
|
+
if (!identity) {
|
|
8635
|
+
return { error: "No identity available for signing attestation" };
|
|
8636
|
+
}
|
|
8637
|
+
const now = /* @__PURE__ */ new Date();
|
|
8638
|
+
const attesterExpiry = new Date(attesterSHR.body.expires_at);
|
|
8639
|
+
const subjectExpiry = new Date(subjectSHR.body.expires_at);
|
|
8640
|
+
const earliestExpiry = attesterExpiry < subjectExpiry ? attesterExpiry : subjectExpiry;
|
|
8641
|
+
const sovereigntyLevel = verificationResult.valid ? verificationResult.sovereignty_level : "unverified";
|
|
8642
|
+
const body = {
|
|
8643
|
+
attestation_version: ATTESTATION_VERSION,
|
|
8644
|
+
attester_id: attesterSHR.body.instance_id,
|
|
8645
|
+
subject_id: subjectSHR.body.instance_id,
|
|
8646
|
+
attester_shr: attesterSHR,
|
|
8647
|
+
subject_shr: subjectSHR,
|
|
8648
|
+
verification: {
|
|
8649
|
+
subject_shr_valid: verificationResult.valid,
|
|
8650
|
+
subject_sovereignty_level: sovereigntyLevel,
|
|
8651
|
+
subject_trust_tier: deriveTrustTier2(sovereigntyLevel),
|
|
8652
|
+
mutual,
|
|
8653
|
+
errors: verificationResult.errors,
|
|
8654
|
+
warnings: verificationResult.warnings
|
|
8655
|
+
},
|
|
8656
|
+
attested_at: now.toISOString(),
|
|
8657
|
+
expires_at: earliestExpiry.toISOString()
|
|
8658
|
+
};
|
|
8659
|
+
const canonical = JSON.stringify(deepSortKeys(body));
|
|
8660
|
+
const payload = stringToBytes(canonical);
|
|
8661
|
+
const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
8662
|
+
const signatureBytes = sign(
|
|
8663
|
+
payload,
|
|
8664
|
+
identity.encrypted_private_key,
|
|
8665
|
+
encryptionKey
|
|
8666
|
+
);
|
|
8667
|
+
const summary = generateSummary(body);
|
|
8668
|
+
return {
|
|
8669
|
+
body,
|
|
8670
|
+
signed_by: identity.public_key,
|
|
8671
|
+
signature: toBase64url(signatureBytes),
|
|
8672
|
+
summary
|
|
8673
|
+
};
|
|
8674
|
+
}
|
|
8675
|
+
function layerLine(label, status) {
|
|
8676
|
+
const icon = status === "active" ? "\u2713" : status === "degraded" ? "~" : "x";
|
|
8677
|
+
return ` ${icon} ${label}: ${status}`;
|
|
8678
|
+
}
|
|
8679
|
+
function generateSummary(body) {
|
|
8680
|
+
const v = body.verification;
|
|
8681
|
+
const sLayers = body.subject_shr.body.layers;
|
|
8682
|
+
const aLayers = body.attester_shr.body.layers;
|
|
8683
|
+
const tierLabel = v.subject_trust_tier === "verified-sovereign" ? "Verified Sovereign" : v.subject_trust_tier === "verified-degraded" ? "Verified (Degraded)" : "Unverified";
|
|
8684
|
+
const lines = [
|
|
8685
|
+
`--- Sovereignty Attestation ---`,
|
|
8686
|
+
``,
|
|
8687
|
+
`Attester: ${body.attester_id.slice(0, 16)}...`,
|
|
8688
|
+
`Subject: ${body.subject_id.slice(0, 16)}...`,
|
|
8689
|
+
`Result: ${tierLabel}`,
|
|
8690
|
+
``,
|
|
8691
|
+
`Subject Sovereignty Posture:`,
|
|
8692
|
+
layerLine("L1 Cognitive Sovereignty", sLayers.l1.status),
|
|
8693
|
+
layerLine("L2 Operational Isolation", sLayers.l2.status),
|
|
8694
|
+
layerLine("L3 Selective Disclosure", sLayers.l3.status),
|
|
8695
|
+
layerLine("L4 Verifiable Reputation", sLayers.l4.status),
|
|
8696
|
+
``,
|
|
8697
|
+
`Attester Sovereignty Posture:`,
|
|
8698
|
+
layerLine("L1 Cognitive Sovereignty", aLayers.l1.status),
|
|
8699
|
+
layerLine("L2 Operational Isolation", aLayers.l2.status),
|
|
8700
|
+
layerLine("L3 Selective Disclosure", aLayers.l3.status),
|
|
8701
|
+
layerLine("L4 Verifiable Reputation", aLayers.l4.status),
|
|
8702
|
+
``,
|
|
8703
|
+
`Mutual: ${v.mutual ? "Yes" : "One-sided"}`,
|
|
8704
|
+
`Attested: ${body.attested_at}`,
|
|
8705
|
+
`Expires: ${body.expires_at}`,
|
|
8706
|
+
`Signature: ${body.attestation_version} / Ed25519`
|
|
8707
|
+
];
|
|
8708
|
+
if (v.warnings.length > 0) {
|
|
8709
|
+
lines.push(``, `Warnings: ${v.warnings.join("; ")}`);
|
|
8710
|
+
}
|
|
8711
|
+
if (v.errors.length > 0) {
|
|
8712
|
+
lines.push(``, `Errors: ${v.errors.join("; ")}`);
|
|
8713
|
+
}
|
|
8714
|
+
lines.push(``, `--- Verify: compare signed_by against attester's known public key ---`);
|
|
8715
|
+
return lines.join("\n");
|
|
8716
|
+
}
|
|
8717
|
+
function verifyAttestation(attestation, now) {
|
|
8718
|
+
const errors = [];
|
|
8719
|
+
const currentTime = /* @__PURE__ */ new Date();
|
|
8720
|
+
if (attestation.body.attestation_version !== ATTESTATION_VERSION) {
|
|
8721
|
+
errors.push(
|
|
8722
|
+
`Unsupported attestation version: ${attestation.body.attestation_version}`
|
|
8723
|
+
);
|
|
8724
|
+
}
|
|
8725
|
+
if (!attestation.body.attester_id || !attestation.body.subject_id) {
|
|
8726
|
+
errors.push("Missing attester_id or subject_id");
|
|
8727
|
+
}
|
|
8728
|
+
if (!attestation.body.attester_shr || !attestation.body.subject_shr) {
|
|
8729
|
+
errors.push("Missing attester or subject SHR");
|
|
8730
|
+
}
|
|
8731
|
+
const expired = new Date(attestation.body.expires_at) <= currentTime;
|
|
8732
|
+
if (expired) {
|
|
8733
|
+
errors.push("Attestation has expired");
|
|
8734
|
+
}
|
|
8735
|
+
try {
|
|
8736
|
+
const publicKey = fromBase64url(attestation.signed_by);
|
|
8737
|
+
const canonical = JSON.stringify(deepSortKeys(attestation.body));
|
|
8738
|
+
const payload = stringToBytes(canonical);
|
|
8739
|
+
const signatureBytes = fromBase64url(attestation.signature);
|
|
8740
|
+
const signatureValid = verify(payload, signatureBytes, publicKey);
|
|
8741
|
+
if (!signatureValid) {
|
|
8742
|
+
errors.push("Attestation signature is invalid");
|
|
8743
|
+
}
|
|
8744
|
+
} catch (e) {
|
|
8745
|
+
errors.push(`Signature verification error: ${e.message}`);
|
|
8746
|
+
}
|
|
8747
|
+
return {
|
|
8748
|
+
valid: errors.length === 0,
|
|
8749
|
+
errors,
|
|
8750
|
+
attester_id: attestation.body.attester_id ?? "unknown",
|
|
8751
|
+
subject_id: attestation.body.subject_id ?? "unknown",
|
|
8752
|
+
trust_tier: errors.length === 0 ? attestation.body.verification.subject_trust_tier : "unverified",
|
|
8753
|
+
expired
|
|
8754
|
+
};
|
|
8755
|
+
}
|
|
8756
|
+
|
|
8304
8757
|
// src/handshake/tools.ts
|
|
8305
8758
|
function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
8306
8759
|
const sessions = /* @__PURE__ */ new Map();
|
|
@@ -8486,6 +8939,103 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
|
8486
8939
|
result: session.result ?? null
|
|
8487
8940
|
});
|
|
8488
8941
|
}
|
|
8942
|
+
},
|
|
8943
|
+
// ─── Streamlined Exchange ─────────────────────────────────────────
|
|
8944
|
+
{
|
|
8945
|
+
name: "sanctuary/handshake_exchange",
|
|
8946
|
+
description: "One-shot sovereignty exchange. Accepts a counterparty's signed SHR, verifies it, generates our SHR, and produces a signed attestation artifact \u2014 all in a single call. Returns a shareable attestation with human-readable summary. Use this instead of the 4-step handshake protocol when you want a quick, portable sovereignty verification (e.g., for social posting or async exchanges).",
|
|
8947
|
+
inputSchema: {
|
|
8948
|
+
type: "object",
|
|
8949
|
+
properties: {
|
|
8950
|
+
counterparty_shr: {
|
|
8951
|
+
type: "object",
|
|
8952
|
+
description: "The counterparty's signed SHR (SignedSHR object with body, signed_by, signature)."
|
|
8953
|
+
},
|
|
8954
|
+
identity_id: {
|
|
8955
|
+
type: "string",
|
|
8956
|
+
description: "Identity to use for the exchange. Defaults to primary identity."
|
|
8957
|
+
}
|
|
8958
|
+
},
|
|
8959
|
+
required: ["counterparty_shr"]
|
|
8960
|
+
},
|
|
8961
|
+
handler: async (args) => {
|
|
8962
|
+
const counterpartySHR = args.counterparty_shr;
|
|
8963
|
+
const ourSHR = generateSHR(args.identity_id, shrOpts);
|
|
8964
|
+
if (typeof ourSHR === "string") {
|
|
8965
|
+
return toolResult({ error: ourSHR });
|
|
8966
|
+
}
|
|
8967
|
+
const verificationResult = verifySHR(counterpartySHR);
|
|
8968
|
+
const attestation = generateAttestation({
|
|
8969
|
+
attesterSHR: ourSHR,
|
|
8970
|
+
subjectSHR: counterpartySHR,
|
|
8971
|
+
verificationResult,
|
|
8972
|
+
mutual: false,
|
|
8973
|
+
identityManager,
|
|
8974
|
+
masterKey,
|
|
8975
|
+
identityId: args.identity_id
|
|
8976
|
+
});
|
|
8977
|
+
if ("error" in attestation) {
|
|
8978
|
+
auditLog.append("l4", "handshake_exchange", ourSHR.body.instance_id, void 0, "failure");
|
|
8979
|
+
return toolResult({ error: attestation.error });
|
|
8980
|
+
}
|
|
8981
|
+
if (verificationResult.valid) {
|
|
8982
|
+
const sovereigntyLevel = verificationResult.sovereignty_level;
|
|
8983
|
+
const trustTier = sovereigntyLevel === "full" ? "verified-sovereign" : sovereigntyLevel === "degraded" ? "verified-degraded" : "unverified";
|
|
8984
|
+
handshakeResults.set(verificationResult.counterparty_id, {
|
|
8985
|
+
counterparty_id: verificationResult.counterparty_id,
|
|
8986
|
+
counterparty_shr: counterpartySHR,
|
|
8987
|
+
verified: true,
|
|
8988
|
+
sovereignty_level: sovereigntyLevel,
|
|
8989
|
+
trust_tier: trustTier,
|
|
8990
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8991
|
+
expires_at: verificationResult.expires_at,
|
|
8992
|
+
errors: []
|
|
8993
|
+
});
|
|
8994
|
+
}
|
|
8995
|
+
auditLog.append("l4", "handshake_exchange", ourSHR.body.instance_id);
|
|
8996
|
+
return toolResult({
|
|
8997
|
+
attestation,
|
|
8998
|
+
our_shr: ourSHR,
|
|
8999
|
+
verification: {
|
|
9000
|
+
counterparty_valid: verificationResult.valid,
|
|
9001
|
+
counterparty_sovereignty: verificationResult.sovereignty_level,
|
|
9002
|
+
counterparty_id: verificationResult.counterparty_id,
|
|
9003
|
+
errors: verificationResult.errors,
|
|
9004
|
+
warnings: verificationResult.warnings
|
|
9005
|
+
},
|
|
9006
|
+
instructions: "The 'attestation' object is a signed, portable sovereignty verification artifact. Share it with the counterparty or post attestation.summary publicly. The counterparty can verify the attestation signature using your public key. Our SHR is included so the counterparty can perform their own verification of us.",
|
|
9007
|
+
_content_trust: "external"
|
|
9008
|
+
});
|
|
9009
|
+
}
|
|
9010
|
+
},
|
|
9011
|
+
{
|
|
9012
|
+
name: "sanctuary/handshake_verify_attestation",
|
|
9013
|
+
description: "Verify a signed attestation artifact from another agent. Checks the Ed25519 signature, temporal validity, and structural integrity.",
|
|
9014
|
+
inputSchema: {
|
|
9015
|
+
type: "object",
|
|
9016
|
+
properties: {
|
|
9017
|
+
attestation: {
|
|
9018
|
+
type: "object",
|
|
9019
|
+
description: "The SignedAttestation object to verify (body, signed_by, signature, summary)."
|
|
9020
|
+
}
|
|
9021
|
+
},
|
|
9022
|
+
required: ["attestation"]
|
|
9023
|
+
},
|
|
9024
|
+
handler: async (args) => {
|
|
9025
|
+
const attestation = args.attestation;
|
|
9026
|
+
const result = verifyAttestation(attestation);
|
|
9027
|
+
auditLog.append(
|
|
9028
|
+
"l4",
|
|
9029
|
+
"handshake_verify_attestation",
|
|
9030
|
+
result.attester_id,
|
|
9031
|
+
void 0,
|
|
9032
|
+
result.valid ? "success" : "failure"
|
|
9033
|
+
);
|
|
9034
|
+
return toolResult({
|
|
9035
|
+
...result,
|
|
9036
|
+
_content_trust: "external"
|
|
9037
|
+
});
|
|
9038
|
+
}
|
|
8489
9039
|
}
|
|
8490
9040
|
];
|
|
8491
9041
|
return { tools, handshakeResults };
|
|
@@ -12056,11 +12606,6 @@ async function createSanctuaryServer(options) {
|
|
|
12056
12606
|
degradations.push(
|
|
12057
12607
|
"L2 isolation is process-level only; no TEE available"
|
|
12058
12608
|
);
|
|
12059
|
-
if (config.disclosure.proof_system === "commitment-only") {
|
|
12060
|
-
degradations.push(
|
|
12061
|
-
"L3 proofs are commitment-based only; ZK proofs not yet available"
|
|
12062
|
-
);
|
|
12063
|
-
}
|
|
12064
12609
|
return toolResult({
|
|
12065
12610
|
attestation: {
|
|
12066
12611
|
environment_type: config.execution.environment,
|
|
@@ -12086,7 +12631,7 @@ async function createSanctuaryServer(options) {
|
|
|
12086
12631
|
l1_state_encrypted: true,
|
|
12087
12632
|
l2_execution_isolated: false,
|
|
12088
12633
|
l2_isolation_type: "process-level",
|
|
12089
|
-
l3_proofs_available:
|
|
12634
|
+
l3_proofs_available: true,
|
|
12090
12635
|
l4_reputation_active: true,
|
|
12091
12636
|
overall_level: "mvs",
|
|
12092
12637
|
degradations
|
|
@@ -12109,14 +12654,6 @@ async function createSanctuaryServer(options) {
|
|
|
12109
12654
|
severity: "warning",
|
|
12110
12655
|
mitigation: "TEE support planned for a future release"
|
|
12111
12656
|
});
|
|
12112
|
-
if (config.disclosure.proof_system === "commitment-only") {
|
|
12113
|
-
degradations.push({
|
|
12114
|
-
layer: "l3",
|
|
12115
|
-
description: "Commitment schemes only (no ZK proofs)",
|
|
12116
|
-
severity: "info",
|
|
12117
|
-
mitigation: "ZK proof support planned for v0.2.0"
|
|
12118
|
-
});
|
|
12119
|
-
}
|
|
12120
12657
|
return toolResult({
|
|
12121
12658
|
status: degradations.some((d) => d.severity === "critical") ? "compromised" : degradations.some((d) => d.severity === "warning") ? "degraded" : "healthy",
|
|
12122
12659
|
storage_bytes: storageSizeBytes,
|
|
@@ -12135,7 +12672,7 @@ async function createSanctuaryServer(options) {
|
|
|
12135
12672
|
last_attestation: (/* @__PURE__ */ new Date()).toISOString()
|
|
12136
12673
|
},
|
|
12137
12674
|
l3: {
|
|
12138
|
-
status:
|
|
12675
|
+
status: "active",
|
|
12139
12676
|
proof_system: config.disclosure.proof_system,
|
|
12140
12677
|
circuits_loaded: 0,
|
|
12141
12678
|
proofs_generated_total: 0
|