@sanctuary-framework/mcp-server 0.5.5 → 0.5.7
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 +2345 -1624
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2345 -1624
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2149 -1455
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +155 -48
- package/dist/index.d.ts +155 -48
- package/dist/index.js +2147 -1456
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -9,6 +9,7 @@ var crypto = require('crypto');
|
|
|
9
9
|
var aes_js = require('@noble/ciphers/aes.js');
|
|
10
10
|
var sha256 = require('@noble/hashes/sha256');
|
|
11
11
|
var hmac = require('@noble/hashes/hmac');
|
|
12
|
+
var ed25519 = require('@noble/curves/ed25519');
|
|
12
13
|
var hashWasm = require('hash-wasm');
|
|
13
14
|
var hkdf = require('@noble/hashes/hkdf');
|
|
14
15
|
var http = require('http');
|
|
@@ -16,7 +17,6 @@ var https = require('https');
|
|
|
16
17
|
var fs = require('fs');
|
|
17
18
|
var child_process = require('child_process');
|
|
18
19
|
var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
19
|
-
var ed25519 = require('@noble/curves/ed25519');
|
|
20
20
|
var index_js = require('@modelcontextprotocol/sdk/server/index.js');
|
|
21
21
|
var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
22
22
|
|
|
@@ -560,6 +560,111 @@ var init_hashing = __esm({
|
|
|
560
560
|
init_encoding();
|
|
561
561
|
}
|
|
562
562
|
});
|
|
563
|
+
function generateKeypair() {
|
|
564
|
+
const privateKey = randomBytes(32);
|
|
565
|
+
const publicKey = ed25519.ed25519.getPublicKey(privateKey);
|
|
566
|
+
return { publicKey, privateKey };
|
|
567
|
+
}
|
|
568
|
+
function publicKeyToDid(publicKey) {
|
|
569
|
+
const multicodec = new Uint8Array([237, 1, ...publicKey]);
|
|
570
|
+
return `did:key:z${toBase64url(multicodec)}`;
|
|
571
|
+
}
|
|
572
|
+
function generateIdentityId(publicKey) {
|
|
573
|
+
const keyHash = hash(publicKey);
|
|
574
|
+
return Array.from(keyHash.slice(0, 16)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
575
|
+
}
|
|
576
|
+
function createIdentity(label, encryptionKey, keyProtection) {
|
|
577
|
+
const { publicKey, privateKey } = generateKeypair();
|
|
578
|
+
const identityId = generateIdentityId(publicKey);
|
|
579
|
+
const did = publicKeyToDid(publicKey);
|
|
580
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
581
|
+
const encryptedPrivateKey = encrypt(privateKey, encryptionKey);
|
|
582
|
+
privateKey.fill(0);
|
|
583
|
+
const publicIdentity = {
|
|
584
|
+
identity_id: identityId,
|
|
585
|
+
label,
|
|
586
|
+
public_key: toBase64url(publicKey),
|
|
587
|
+
did,
|
|
588
|
+
created_at: now,
|
|
589
|
+
key_type: "ed25519",
|
|
590
|
+
key_protection: keyProtection
|
|
591
|
+
};
|
|
592
|
+
const storedIdentity = {
|
|
593
|
+
...publicIdentity,
|
|
594
|
+
encrypted_private_key: encryptedPrivateKey,
|
|
595
|
+
rotation_history: []
|
|
596
|
+
};
|
|
597
|
+
return { publicIdentity, storedIdentity };
|
|
598
|
+
}
|
|
599
|
+
function sign(payload, encryptedPrivateKey, encryptionKey) {
|
|
600
|
+
const privateKey = decrypt(encryptedPrivateKey, encryptionKey);
|
|
601
|
+
try {
|
|
602
|
+
return ed25519.ed25519.sign(payload, privateKey);
|
|
603
|
+
} finally {
|
|
604
|
+
privateKey.fill(0);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function verify(payload, signature, publicKey) {
|
|
608
|
+
try {
|
|
609
|
+
return ed25519.ed25519.verify(signature, payload, publicKey);
|
|
610
|
+
} catch {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function rotateKeys(storedIdentity, encryptionKey, reason) {
|
|
615
|
+
const { publicKey: newPublicKey, privateKey: newPrivateKey } = generateKeypair();
|
|
616
|
+
const newIdentityDid = publicKeyToDid(newPublicKey);
|
|
617
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
618
|
+
const eventData = JSON.stringify({
|
|
619
|
+
old_public_key: storedIdentity.public_key,
|
|
620
|
+
new_public_key: toBase64url(newPublicKey),
|
|
621
|
+
identity_id: storedIdentity.identity_id,
|
|
622
|
+
reason,
|
|
623
|
+
rotated_at: now
|
|
624
|
+
});
|
|
625
|
+
const eventBytes = new TextEncoder().encode(eventData);
|
|
626
|
+
const signature = sign(
|
|
627
|
+
eventBytes,
|
|
628
|
+
storedIdentity.encrypted_private_key,
|
|
629
|
+
encryptionKey
|
|
630
|
+
);
|
|
631
|
+
const rotationEvent = {
|
|
632
|
+
old_public_key: storedIdentity.public_key,
|
|
633
|
+
new_public_key: toBase64url(newPublicKey),
|
|
634
|
+
identity_id: storedIdentity.identity_id,
|
|
635
|
+
reason,
|
|
636
|
+
rotated_at: now,
|
|
637
|
+
signature: toBase64url(signature)
|
|
638
|
+
};
|
|
639
|
+
const encryptedNewPrivateKey = encrypt(newPrivateKey, encryptionKey);
|
|
640
|
+
newPrivateKey.fill(0);
|
|
641
|
+
const updatedIdentity = {
|
|
642
|
+
...storedIdentity,
|
|
643
|
+
public_key: toBase64url(newPublicKey),
|
|
644
|
+
did: newIdentityDid,
|
|
645
|
+
encrypted_private_key: encryptedNewPrivateKey,
|
|
646
|
+
rotation_history: [
|
|
647
|
+
...storedIdentity.rotation_history,
|
|
648
|
+
{
|
|
649
|
+
old_public_key: storedIdentity.public_key,
|
|
650
|
+
new_public_key: toBase64url(newPublicKey),
|
|
651
|
+
rotation_event: toBase64url(
|
|
652
|
+
new TextEncoder().encode(JSON.stringify(rotationEvent))
|
|
653
|
+
),
|
|
654
|
+
rotated_at: now
|
|
655
|
+
}
|
|
656
|
+
]
|
|
657
|
+
};
|
|
658
|
+
return { updatedIdentity, rotationEvent };
|
|
659
|
+
}
|
|
660
|
+
var init_identity = __esm({
|
|
661
|
+
"src/core/identity.ts"() {
|
|
662
|
+
init_encoding();
|
|
663
|
+
init_encryption();
|
|
664
|
+
init_hashing();
|
|
665
|
+
init_random();
|
|
666
|
+
}
|
|
667
|
+
});
|
|
563
668
|
async function deriveMasterKey(passphrase, existingParams) {
|
|
564
669
|
const salt = existingParams ? fromBase64url(existingParams.salt) : generateSalt();
|
|
565
670
|
const params = existingParams ?? {
|
|
@@ -870,6 +975,8 @@ tier3_always_allow:
|
|
|
870
975
|
- handshake_respond
|
|
871
976
|
- handshake_complete
|
|
872
977
|
- handshake_status
|
|
978
|
+
- handshake_exchange
|
|
979
|
+
- handshake_verify_attestation
|
|
873
980
|
- reputation_query_weighted
|
|
874
981
|
- federation_peers
|
|
875
982
|
- federation_trust_evaluate
|
|
@@ -971,6 +1078,8 @@ var init_loader = __esm({
|
|
|
971
1078
|
"handshake_respond",
|
|
972
1079
|
"handshake_complete",
|
|
973
1080
|
"handshake_status",
|
|
1081
|
+
"handshake_exchange",
|
|
1082
|
+
"handshake_verify_attestation",
|
|
974
1083
|
"reputation_query_weighted",
|
|
975
1084
|
"federation_peers",
|
|
976
1085
|
"federation_trust_evaluate",
|
|
@@ -1160,1592 +1269,2023 @@ var init_baseline = __esm({
|
|
|
1160
1269
|
}
|
|
1161
1270
|
});
|
|
1162
1271
|
|
|
1163
|
-
// src/
|
|
1164
|
-
function
|
|
1165
|
-
return
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
<title>Sanctuary \u2014 Login</title>
|
|
1171
|
-
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
|
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
|
-
--red: #f85149;
|
|
1181
|
-
--blue: #58a6ff;
|
|
1182
|
-
--mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
|
1183
|
-
--sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
1184
|
-
--radius: 6px;
|
|
1185
|
-
}
|
|
1186
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1187
|
-
html, body { width: 100%; height: 100%; }
|
|
1188
|
-
body {
|
|
1189
|
-
font-family: var(--sans);
|
|
1190
|
-
background: var(--bg);
|
|
1191
|
-
color: var(--text-primary);
|
|
1192
|
-
display: flex;
|
|
1193
|
-
align-items: center;
|
|
1194
|
-
justify-content: center;
|
|
1195
|
-
}
|
|
1196
|
-
.login-container {
|
|
1197
|
-
width: 100%;
|
|
1198
|
-
max-width: 400px;
|
|
1199
|
-
padding: 40px 32px;
|
|
1200
|
-
background: var(--surface);
|
|
1201
|
-
border: 1px solid var(--border);
|
|
1202
|
-
border-radius: 12px;
|
|
1203
|
-
}
|
|
1204
|
-
.login-logo {
|
|
1205
|
-
text-align: center;
|
|
1206
|
-
font-size: 20px;
|
|
1207
|
-
font-weight: 700;
|
|
1208
|
-
letter-spacing: -0.5px;
|
|
1209
|
-
margin-bottom: 8px;
|
|
1210
|
-
}
|
|
1211
|
-
.login-logo span { color: var(--blue); }
|
|
1212
|
-
.login-version {
|
|
1213
|
-
text-align: center;
|
|
1214
|
-
font-size: 11px;
|
|
1215
|
-
color: var(--text-secondary);
|
|
1216
|
-
font-family: var(--mono);
|
|
1217
|
-
margin-bottom: 32px;
|
|
1218
|
-
}
|
|
1219
|
-
.login-label {
|
|
1220
|
-
display: block;
|
|
1221
|
-
font-size: 13px;
|
|
1222
|
-
font-weight: 600;
|
|
1223
|
-
color: var(--text-secondary);
|
|
1224
|
-
margin-bottom: 8px;
|
|
1225
|
-
}
|
|
1226
|
-
.login-input {
|
|
1227
|
-
width: 100%;
|
|
1228
|
-
padding: 10px 14px;
|
|
1229
|
-
background: var(--bg);
|
|
1230
|
-
border: 1px solid var(--border);
|
|
1231
|
-
border-radius: var(--radius);
|
|
1232
|
-
color: var(--text-primary);
|
|
1233
|
-
font-family: var(--mono);
|
|
1234
|
-
font-size: 14px;
|
|
1235
|
-
outline: none;
|
|
1236
|
-
transition: border-color 0.15s;
|
|
1237
|
-
}
|
|
1238
|
-
.login-input:focus { border-color: var(--blue); }
|
|
1239
|
-
.login-input::placeholder { color: var(--text-secondary); opacity: 0.5; }
|
|
1240
|
-
.login-btn {
|
|
1241
|
-
width: 100%;
|
|
1242
|
-
margin-top: 20px;
|
|
1243
|
-
padding: 10px;
|
|
1244
|
-
background: var(--blue);
|
|
1245
|
-
color: var(--bg);
|
|
1246
|
-
border: none;
|
|
1247
|
-
border-radius: var(--radius);
|
|
1248
|
-
font-size: 14px;
|
|
1249
|
-
font-weight: 600;
|
|
1250
|
-
cursor: pointer;
|
|
1251
|
-
transition: opacity 0.15s;
|
|
1252
|
-
font-family: var(--sans);
|
|
1253
|
-
}
|
|
1254
|
-
.login-btn:hover { opacity: 0.9; }
|
|
1255
|
-
.login-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1256
|
-
.login-error {
|
|
1257
|
-
margin-top: 16px;
|
|
1258
|
-
padding: 10px 14px;
|
|
1259
|
-
background: rgba(248, 81, 73, 0.1);
|
|
1260
|
-
border: 1px solid var(--red);
|
|
1261
|
-
border-radius: var(--radius);
|
|
1262
|
-
font-size: 12px;
|
|
1263
|
-
color: var(--red);
|
|
1264
|
-
display: none;
|
|
1265
|
-
}
|
|
1266
|
-
.login-hint {
|
|
1267
|
-
margin-top: 24px;
|
|
1268
|
-
padding-top: 16px;
|
|
1269
|
-
border-top: 1px solid var(--border);
|
|
1270
|
-
font-size: 11px;
|
|
1271
|
-
color: var(--text-secondary);
|
|
1272
|
-
line-height: 1.5;
|
|
1273
|
-
}
|
|
1274
|
-
.login-hint code {
|
|
1275
|
-
font-family: var(--mono);
|
|
1276
|
-
background: var(--bg);
|
|
1277
|
-
padding: 1px 4px;
|
|
1278
|
-
border-radius: 3px;
|
|
1279
|
-
font-size: 10px;
|
|
1280
|
-
}
|
|
1281
|
-
</style>
|
|
1282
|
-
</head>
|
|
1283
|
-
<body>
|
|
1284
|
-
<div class="login-container">
|
|
1285
|
-
<div class="login-logo"><span>◆</span> SANCTUARY</div>
|
|
1286
|
-
<div class="login-version">Principal Dashboard v${options.serverVersion}</div>
|
|
1287
|
-
<form id="loginForm" onsubmit="return handleLogin(event)">
|
|
1288
|
-
<label class="login-label" for="tokenInput">Dashboard Auth Token</label>
|
|
1289
|
-
<input class="login-input" type="password" id="tokenInput"
|
|
1290
|
-
placeholder="Enter your auth token" autocomplete="off" autofocus required>
|
|
1291
|
-
<button class="login-btn" type="submit" id="loginBtn">Open Dashboard</button>
|
|
1292
|
-
</form>
|
|
1293
|
-
<div class="login-error" id="loginError"></div>
|
|
1294
|
-
<div class="login-hint">
|
|
1295
|
-
Your token is set via <code>SANCTUARY_DASHBOARD_AUTH_TOKEN</code> environment variable,
|
|
1296
|
-
or check your server's startup output.
|
|
1297
|
-
</div>
|
|
1298
|
-
</div>
|
|
1299
|
-
<script>
|
|
1300
|
-
async function handleLogin(e) {
|
|
1301
|
-
e.preventDefault();
|
|
1302
|
-
var btn = document.getElementById('loginBtn');
|
|
1303
|
-
var errEl = document.getElementById('loginError');
|
|
1304
|
-
var token = document.getElementById('tokenInput').value.trim();
|
|
1305
|
-
if (!token) return false;
|
|
1306
|
-
btn.disabled = true;
|
|
1307
|
-
btn.textContent = 'Authenticating...';
|
|
1308
|
-
errEl.style.display = 'none';
|
|
1309
|
-
try {
|
|
1310
|
-
var resp = await fetch('/auth/session', {
|
|
1311
|
-
method: 'POST',
|
|
1312
|
-
headers: { 'Authorization': 'Bearer ' + token }
|
|
1313
|
-
});
|
|
1314
|
-
if (!resp.ok) {
|
|
1315
|
-
var data = await resp.json().catch(function() { return {}; });
|
|
1316
|
-
throw new Error(data.error || 'Authentication failed');
|
|
1317
|
-
}
|
|
1318
|
-
var result = await resp.json();
|
|
1319
|
-
// Store token in sessionStorage for auto-renewal inside the dashboard
|
|
1320
|
-
try { sessionStorage.setItem('sanctuary_token', token); } catch(_) {}
|
|
1321
|
-
// Set session cookie
|
|
1322
|
-
var maxAge = result.expires_in_seconds || 300;
|
|
1323
|
-
document.cookie = 'sanctuary_session=' + result.session_id +
|
|
1324
|
-
'; path=/; SameSite=Strict; max-age=' + maxAge;
|
|
1325
|
-
// Reload to enter the dashboard
|
|
1326
|
-
window.location.reload();
|
|
1327
|
-
} catch (err) {
|
|
1328
|
-
errEl.textContent = err.message || 'Authentication failed. Check your token.';
|
|
1329
|
-
errEl.style.display = 'block';
|
|
1330
|
-
btn.disabled = false;
|
|
1331
|
-
btn.textContent = 'Open Dashboard';
|
|
1272
|
+
// src/shr/types.ts
|
|
1273
|
+
function deepSortKeys(obj) {
|
|
1274
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
1275
|
+
if (Array.isArray(obj)) return obj.map(deepSortKeys);
|
|
1276
|
+
const sorted = {};
|
|
1277
|
+
for (const key of Object.keys(obj).sort()) {
|
|
1278
|
+
sorted[key] = deepSortKeys(obj[key]);
|
|
1332
1279
|
}
|
|
1333
|
-
return
|
|
1280
|
+
return sorted;
|
|
1334
1281
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
</html>`;
|
|
1282
|
+
function canonicalizeForSigning(body) {
|
|
1283
|
+
return JSON.stringify(deepSortKeys(body));
|
|
1338
1284
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
<html lang="en">
|
|
1342
|
-
<head>
|
|
1343
|
-
<meta charset="utf-8">
|
|
1344
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1345
|
-
<title>Sanctuary \u2014 Principal Dashboard</title>
|
|
1346
|
-
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
|
1347
|
-
<style>
|
|
1348
|
-
:root {
|
|
1349
|
-
--bg: #0d1117;
|
|
1350
|
-
--surface: #161b22;
|
|
1351
|
-
--border: #30363d;
|
|
1352
|
-
--text-primary: #e6edf3;
|
|
1353
|
-
--text-secondary: #8b949e;
|
|
1354
|
-
--green: #3fb950;
|
|
1355
|
-
--amber: #d29922;
|
|
1356
|
-
--red: #f85149;
|
|
1357
|
-
--blue: #58a6ff;
|
|
1358
|
-
--mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
|
1359
|
-
--sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
1360
|
-
--radius: 6px;
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
* {
|
|
1364
|
-
box-sizing: border-box;
|
|
1365
|
-
margin: 0;
|
|
1366
|
-
padding: 0;
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
html, body {
|
|
1370
|
-
width: 100%;
|
|
1371
|
-
height: 100%;
|
|
1372
|
-
overflow: hidden;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
body {
|
|
1376
|
-
font-family: var(--sans);
|
|
1377
|
-
background: var(--bg);
|
|
1378
|
-
color: var(--text-primary);
|
|
1379
|
-
display: flex;
|
|
1380
|
-
flex-direction: column;
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
/* \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 */
|
|
1384
|
-
|
|
1385
|
-
.status-bar {
|
|
1386
|
-
position: fixed;
|
|
1387
|
-
top: 0;
|
|
1388
|
-
left: 0;
|
|
1389
|
-
right: 0;
|
|
1390
|
-
height: 56px;
|
|
1391
|
-
background: var(--surface);
|
|
1392
|
-
border-bottom: 1px solid var(--border);
|
|
1393
|
-
display: flex;
|
|
1394
|
-
align-items: center;
|
|
1395
|
-
padding: 0 20px;
|
|
1396
|
-
gap: 24px;
|
|
1397
|
-
z-index: 1000;
|
|
1285
|
+
var init_types = __esm({
|
|
1286
|
+
"src/shr/types.ts"() {
|
|
1398
1287
|
}
|
|
1288
|
+
});
|
|
1399
1289
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1290
|
+
// src/shr/generator.ts
|
|
1291
|
+
function generateSHR(identityId, opts) {
|
|
1292
|
+
const { config, identityManager, masterKey, validityMs } = opts;
|
|
1293
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
1294
|
+
if (!identity) {
|
|
1295
|
+
return "No identity available for signing. Create an identity first.";
|
|
1405
1296
|
}
|
|
1406
|
-
|
|
1407
|
-
.
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1297
|
+
const now = /* @__PURE__ */ new Date();
|
|
1298
|
+
const expiresAt = new Date(now.getTime() + (validityMs ?? DEFAULT_VALIDITY_MS));
|
|
1299
|
+
const degradations = [];
|
|
1300
|
+
if (config.execution.environment === "local-process") {
|
|
1301
|
+
degradations.push({
|
|
1302
|
+
layer: "l2",
|
|
1303
|
+
code: "PROCESS_ISOLATION_ONLY",
|
|
1304
|
+
severity: "warning",
|
|
1305
|
+
description: "Process-level isolation only (no TEE)",
|
|
1306
|
+
mitigation: "TEE support planned for a future release"
|
|
1307
|
+
});
|
|
1308
|
+
degradations.push({
|
|
1309
|
+
layer: "l2",
|
|
1310
|
+
code: "SELF_REPORTED_ATTESTATION",
|
|
1311
|
+
severity: "warning",
|
|
1312
|
+
description: "Attestation is self-reported (no hardware root of trust)",
|
|
1313
|
+
mitigation: "TEE attestation planned for a future release"
|
|
1314
|
+
});
|
|
1412
1315
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1316
|
+
const body = {
|
|
1317
|
+
shr_version: "1.0",
|
|
1318
|
+
implementation: {
|
|
1319
|
+
sanctuary_version: config.version,
|
|
1320
|
+
node_version: process.versions.node,
|
|
1321
|
+
generated_by: "sanctuary-mcp-server"
|
|
1322
|
+
},
|
|
1323
|
+
instance_id: identity.identity_id,
|
|
1324
|
+
generated_at: now.toISOString(),
|
|
1325
|
+
expires_at: expiresAt.toISOString(),
|
|
1326
|
+
layers: {
|
|
1327
|
+
l1: {
|
|
1328
|
+
status: "active",
|
|
1329
|
+
encryption: config.state.encryption,
|
|
1330
|
+
key_custody: "self",
|
|
1331
|
+
integrity: config.state.integrity,
|
|
1332
|
+
identity_type: config.state.identity_provider,
|
|
1333
|
+
state_portable: true
|
|
1334
|
+
},
|
|
1335
|
+
l2: {
|
|
1336
|
+
status: config.execution.environment === "local-process" ? "degraded" : "active",
|
|
1337
|
+
isolation_type: config.execution.environment,
|
|
1338
|
+
attestation_available: config.execution.attestation
|
|
1339
|
+
},
|
|
1340
|
+
l3: {
|
|
1341
|
+
status: "active",
|
|
1342
|
+
proof_system: config.disclosure.proof_system,
|
|
1343
|
+
selective_disclosure: true
|
|
1344
|
+
},
|
|
1345
|
+
l4: {
|
|
1346
|
+
status: "active",
|
|
1347
|
+
reputation_mode: config.reputation.mode,
|
|
1348
|
+
attestation_format: config.reputation.attestation_format,
|
|
1349
|
+
reputation_portable: true
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
capabilities: {
|
|
1353
|
+
handshake: true,
|
|
1354
|
+
shr_exchange: true,
|
|
1355
|
+
reputation_verify: true,
|
|
1356
|
+
encrypted_channel: false
|
|
1357
|
+
// Not yet implemented
|
|
1358
|
+
},
|
|
1359
|
+
degradations
|
|
1360
|
+
};
|
|
1361
|
+
const canonical = canonicalizeForSigning(body);
|
|
1362
|
+
const payload = stringToBytes(canonical);
|
|
1363
|
+
const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
1364
|
+
const signatureBytes = sign(
|
|
1365
|
+
payload,
|
|
1366
|
+
identity.encrypted_private_key,
|
|
1367
|
+
encryptionKey
|
|
1368
|
+
);
|
|
1369
|
+
return {
|
|
1370
|
+
body,
|
|
1371
|
+
signed_by: identity.public_key,
|
|
1372
|
+
signature: toBase64url(signatureBytes)
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
var DEFAULT_VALIDITY_MS;
|
|
1376
|
+
var init_generator = __esm({
|
|
1377
|
+
"src/shr/generator.ts"() {
|
|
1378
|
+
init_types();
|
|
1379
|
+
init_identity();
|
|
1380
|
+
init_encoding();
|
|
1381
|
+
init_key_derivation();
|
|
1382
|
+
DEFAULT_VALIDITY_MS = 60 * 60 * 1e3;
|
|
1416
1383
|
}
|
|
1384
|
+
});
|
|
1417
1385
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1386
|
+
// src/principal-policy/dashboard-html.ts
|
|
1387
|
+
function generateLoginHTML(options) {
|
|
1388
|
+
return `<!DOCTYPE html>
|
|
1389
|
+
<html lang="en">
|
|
1390
|
+
<head>
|
|
1391
|
+
<meta charset="UTF-8">
|
|
1392
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1393
|
+
<title>Sanctuary \u2014 Principal Dashboard</title>
|
|
1394
|
+
<style>
|
|
1395
|
+
:root {
|
|
1396
|
+
--bg: #0d1117;
|
|
1397
|
+
--surface: #161b22;
|
|
1398
|
+
--border: #30363d;
|
|
1399
|
+
--text-primary: #e6edf3;
|
|
1400
|
+
--text-secondary: #8b949e;
|
|
1401
|
+
--green: #3fb950;
|
|
1402
|
+
--amber: #d29922;
|
|
1403
|
+
--red: #f85149;
|
|
1404
|
+
--blue: #58a6ff;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
* {
|
|
1408
|
+
margin: 0;
|
|
1409
|
+
padding: 0;
|
|
1410
|
+
box-sizing: border-box;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
body {
|
|
1414
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
1415
|
+
background-color: var(--bg);
|
|
1416
|
+
color: var(--text-primary);
|
|
1417
|
+
min-height: 100vh;
|
|
1418
|
+
display: flex;
|
|
1419
|
+
align-items: center;
|
|
1420
|
+
justify-content: center;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
.login-container {
|
|
1424
|
+
width: 100%;
|
|
1425
|
+
max-width: 400px;
|
|
1426
|
+
padding: 20px;
|
|
1427
|
+
}
|
|
1423
1428
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
.sovereignty-badge {
|
|
1432
|
-
display: flex;
|
|
1433
|
-
align-items: center;
|
|
1434
|
-
gap: 8px;
|
|
1435
|
-
padding: 6px 12px;
|
|
1436
|
-
background: rgba(88, 166, 255, 0.1);
|
|
1437
|
-
border: 1px solid var(--blue);
|
|
1438
|
-
border-radius: 20px;
|
|
1439
|
-
font-size: 13px;
|
|
1440
|
-
font-weight: 600;
|
|
1441
|
-
}
|
|
1429
|
+
.login-card {
|
|
1430
|
+
background-color: var(--surface);
|
|
1431
|
+
border: 1px solid var(--border);
|
|
1432
|
+
border-radius: 8px;
|
|
1433
|
+
padding: 40px 32px;
|
|
1434
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
1435
|
+
}
|
|
1442
1436
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
border-radius: 50%;
|
|
1450
|
-
font-family: var(--mono);
|
|
1451
|
-
font-weight: 700;
|
|
1452
|
-
font-size: 12px;
|
|
1453
|
-
background: var(--blue);
|
|
1454
|
-
color: var(--bg);
|
|
1455
|
-
}
|
|
1437
|
+
.login-header {
|
|
1438
|
+
display: flex;
|
|
1439
|
+
align-items: center;
|
|
1440
|
+
gap: 12px;
|
|
1441
|
+
margin-bottom: 32px;
|
|
1442
|
+
}
|
|
1456
1443
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1444
|
+
.logo {
|
|
1445
|
+
font-size: 24px;
|
|
1446
|
+
font-weight: 700;
|
|
1447
|
+
color: var(--blue);
|
|
1448
|
+
}
|
|
1460
1449
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1450
|
+
.logo-text {
|
|
1451
|
+
display: flex;
|
|
1452
|
+
flex-direction: column;
|
|
1453
|
+
}
|
|
1464
1454
|
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1455
|
+
.logo-text .title {
|
|
1456
|
+
font-size: 18px;
|
|
1457
|
+
font-weight: 600;
|
|
1458
|
+
letter-spacing: -0.5px;
|
|
1459
|
+
}
|
|
1468
1460
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
}
|
|
1461
|
+
.logo-text .version {
|
|
1462
|
+
font-size: 12px;
|
|
1463
|
+
color: var(--text-secondary);
|
|
1464
|
+
margin-top: 2px;
|
|
1465
|
+
}
|
|
1475
1466
|
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
gap: 6px;
|
|
1480
|
-
font-size: 12px;
|
|
1481
|
-
color: var(--text-secondary);
|
|
1482
|
-
font-family: var(--mono);
|
|
1483
|
-
}
|
|
1467
|
+
.form-group {
|
|
1468
|
+
margin-bottom: 24px;
|
|
1469
|
+
}
|
|
1484
1470
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1471
|
+
label {
|
|
1472
|
+
display: block;
|
|
1473
|
+
font-size: 14px;
|
|
1474
|
+
font-weight: 500;
|
|
1475
|
+
margin-bottom: 8px;
|
|
1476
|
+
color: var(--text-primary);
|
|
1477
|
+
}
|
|
1489
1478
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1479
|
+
input[type="text"],
|
|
1480
|
+
input[type="password"] {
|
|
1481
|
+
width: 100%;
|
|
1482
|
+
padding: 10px 12px;
|
|
1483
|
+
background-color: var(--bg);
|
|
1484
|
+
border: 1px solid var(--border);
|
|
1485
|
+
border-radius: 6px;
|
|
1486
|
+
color: var(--text-primary);
|
|
1487
|
+
font-size: 14px;
|
|
1488
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1489
|
+
transition: border-color 0.2s;
|
|
1490
|
+
}
|
|
1498
1491
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
}
|
|
1492
|
+
input[type="text"]:focus,
|
|
1493
|
+
input[type="password"]:focus {
|
|
1494
|
+
outline: none;
|
|
1495
|
+
border-color: var(--blue);
|
|
1496
|
+
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.1);
|
|
1497
|
+
}
|
|
1506
1498
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1499
|
+
.error-message {
|
|
1500
|
+
display: none;
|
|
1501
|
+
background-color: rgba(248, 81, 73, 0.1);
|
|
1502
|
+
border: 1px solid var(--red);
|
|
1503
|
+
color: #ff9999;
|
|
1504
|
+
padding: 12px;
|
|
1505
|
+
border-radius: 6px;
|
|
1506
|
+
font-size: 13px;
|
|
1507
|
+
margin-bottom: 20px;
|
|
1508
|
+
}
|
|
1511
1509
|
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
}
|
|
1510
|
+
.error-message.show {
|
|
1511
|
+
display: block;
|
|
1512
|
+
}
|
|
1516
1513
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
animation: pulse 1s ease-in-out infinite;
|
|
1530
|
-
}
|
|
1514
|
+
button {
|
|
1515
|
+
width: 100%;
|
|
1516
|
+
padding: 10px 16px;
|
|
1517
|
+
background-color: var(--blue);
|
|
1518
|
+
color: var(--bg);
|
|
1519
|
+
border: none;
|
|
1520
|
+
border-radius: 6px;
|
|
1521
|
+
font-size: 14px;
|
|
1522
|
+
font-weight: 600;
|
|
1523
|
+
cursor: pointer;
|
|
1524
|
+
transition: background-color 0.2s;
|
|
1525
|
+
}
|
|
1531
1526
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1527
|
+
button:hover {
|
|
1528
|
+
background-color: #79c0ff;
|
|
1529
|
+
}
|
|
1535
1530
|
|
|
1536
|
-
|
|
1531
|
+
button:active {
|
|
1532
|
+
background-color: #4184e4;
|
|
1533
|
+
}
|
|
1537
1534
|
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
}
|
|
1535
|
+
button:disabled {
|
|
1536
|
+
background-color: var(--text-secondary);
|
|
1537
|
+
cursor: not-allowed;
|
|
1538
|
+
opacity: 0.5;
|
|
1539
|
+
}
|
|
1544
1540
|
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1541
|
+
.info-text {
|
|
1542
|
+
font-size: 12px;
|
|
1543
|
+
color: var(--text-secondary);
|
|
1544
|
+
margin-top: 16px;
|
|
1545
|
+
text-align: center;
|
|
1546
|
+
}
|
|
1547
|
+
</style>
|
|
1548
|
+
</head>
|
|
1549
|
+
<body>
|
|
1550
|
+
<div class="login-container">
|
|
1551
|
+
<div class="login-card">
|
|
1552
|
+
<div class="login-header">
|
|
1553
|
+
<div class="logo">\u25C6</div>
|
|
1554
|
+
<div class="logo-text">
|
|
1555
|
+
<div class="title">SANCTUARY</div>
|
|
1556
|
+
<div class="version">v${options.serverVersion}</div>
|
|
1557
|
+
</div>
|
|
1558
|
+
</div>
|
|
1552
1559
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1560
|
+
<div id="error-message" class="error-message"></div>
|
|
1561
|
+
|
|
1562
|
+
<form id="login-form">
|
|
1563
|
+
<div class="form-group">
|
|
1564
|
+
<label for="auth-token">Auth Token</label>
|
|
1565
|
+
<input
|
|
1566
|
+
type="text"
|
|
1567
|
+
id="auth-token"
|
|
1568
|
+
name="token"
|
|
1569
|
+
placeholder="Paste your session token..."
|
|
1570
|
+
autocomplete="off"
|
|
1571
|
+
spellcheck="false"
|
|
1572
|
+
required
|
|
1573
|
+
/>
|
|
1574
|
+
</div>
|
|
1575
|
+
|
|
1576
|
+
<button type="submit" id="login-button">Open Dashboard</button>
|
|
1577
|
+
</form>
|
|
1578
|
+
|
|
1579
|
+
<div class="info-text">
|
|
1580
|
+
Session tokens expire after 1 hour of inactivity
|
|
1581
|
+
</div>
|
|
1582
|
+
</div>
|
|
1583
|
+
</div>
|
|
1565
1584
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
}
|
|
1585
|
+
<script>
|
|
1586
|
+
const loginForm = document.getElementById('login-form');
|
|
1587
|
+
const authTokenInput = document.getElementById('auth-token');
|
|
1588
|
+
const errorMessage = document.getElementById('error-message');
|
|
1589
|
+
const loginButton = document.getElementById('login-button');
|
|
1572
1590
|
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
overflow-x: hidden;
|
|
1577
|
-
}
|
|
1591
|
+
loginForm.addEventListener('submit', async (e) => {
|
|
1592
|
+
e.preventDefault();
|
|
1593
|
+
const token = authTokenInput.value.trim();
|
|
1578
1594
|
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
font-family: var(--mono);
|
|
1584
|
-
cursor: pointer;
|
|
1585
|
-
transition: background 0.15s;
|
|
1586
|
-
display: flex;
|
|
1587
|
-
align-items: flex-start;
|
|
1588
|
-
gap: 10px;
|
|
1589
|
-
}
|
|
1595
|
+
if (!token) {
|
|
1596
|
+
showError('Token is required');
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1590
1599
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1600
|
+
loginButton.disabled = true;
|
|
1601
|
+
loginButton.textContent = 'Verifying...';
|
|
1602
|
+
errorMessage.classList.remove('show');
|
|
1594
1603
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1604
|
+
try {
|
|
1605
|
+
const response = await fetch('/auth/session', {
|
|
1606
|
+
method: 'POST',
|
|
1607
|
+
headers: {
|
|
1608
|
+
'Content-Type': 'application/json',
|
|
1609
|
+
'Authorization': 'Bearer ' + token,
|
|
1610
|
+
},
|
|
1611
|
+
body: JSON.stringify({ token }),
|
|
1612
|
+
});
|
|
1603
1613
|
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1614
|
+
if (response.ok) {
|
|
1615
|
+
const data = await response.json();
|
|
1616
|
+
sessionStorage.setItem('authToken', token);
|
|
1617
|
+
window.location.href = '/dashboard';
|
|
1618
|
+
} else if (response.status === 401) {
|
|
1619
|
+
showError('Invalid token. Please check and try again.');
|
|
1620
|
+
} else {
|
|
1621
|
+
showError('Authentication failed. Please try again.');
|
|
1622
|
+
}
|
|
1623
|
+
} catch (err) {
|
|
1624
|
+
showError('Connection error. Please check your network.');
|
|
1625
|
+
} finally {
|
|
1626
|
+
loginButton.disabled = false;
|
|
1627
|
+
loginButton.textContent = 'Open Dashboard';
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1608
1630
|
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
}
|
|
1631
|
+
function showError(message) {
|
|
1632
|
+
errorMessage.textContent = message;
|
|
1633
|
+
errorMessage.classList.add('show');
|
|
1634
|
+
}
|
|
1614
1635
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1636
|
+
authTokenInput.addEventListener('input', () => {
|
|
1637
|
+
errorMessage.classList.remove('show');
|
|
1638
|
+
});
|
|
1639
|
+
</script>
|
|
1640
|
+
</body>
|
|
1641
|
+
</html>`;
|
|
1642
|
+
}
|
|
1643
|
+
function generateDashboardHTML(options) {
|
|
1644
|
+
return `<!DOCTYPE html>
|
|
1645
|
+
<html lang="en">
|
|
1646
|
+
<head>
|
|
1647
|
+
<meta charset="UTF-8">
|
|
1648
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1649
|
+
<title>Sanctuary \u2014 Principal Dashboard</title>
|
|
1650
|
+
<style>
|
|
1651
|
+
:root {
|
|
1652
|
+
--bg: #0d1117;
|
|
1653
|
+
--surface: #161b22;
|
|
1654
|
+
--border: #30363d;
|
|
1655
|
+
--text-primary: #e6edf3;
|
|
1656
|
+
--text-secondary: #8b949e;
|
|
1657
|
+
--green: #3fb950;
|
|
1658
|
+
--amber: #d29922;
|
|
1659
|
+
--red: #f85149;
|
|
1660
|
+
--blue: #58a6ff;
|
|
1661
|
+
--success: #3fb950;
|
|
1662
|
+
--warning: #d29922;
|
|
1663
|
+
--error: #f85149;
|
|
1664
|
+
--muted: #21262d;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
* {
|
|
1668
|
+
margin: 0;
|
|
1669
|
+
padding: 0;
|
|
1670
|
+
box-sizing: border-box;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
html, body {
|
|
1674
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
1675
|
+
background-color: var(--bg);
|
|
1676
|
+
color: var(--text-primary);
|
|
1677
|
+
height: 100%;
|
|
1678
|
+
overflow: hidden;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
body {
|
|
1682
|
+
display: flex;
|
|
1683
|
+
flex-direction: column;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/* Status Bar */
|
|
1687
|
+
.status-bar {
|
|
1688
|
+
position: fixed;
|
|
1689
|
+
top: 0;
|
|
1690
|
+
left: 0;
|
|
1691
|
+
right: 0;
|
|
1692
|
+
height: 56px;
|
|
1693
|
+
background-color: var(--surface);
|
|
1694
|
+
border-bottom: 1px solid var(--border);
|
|
1695
|
+
display: flex;
|
|
1696
|
+
align-items: center;
|
|
1697
|
+
padding: 0 24px;
|
|
1698
|
+
gap: 24px;
|
|
1699
|
+
z-index: 100;
|
|
1700
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
.status-bar-left {
|
|
1704
|
+
display: flex;
|
|
1705
|
+
align-items: center;
|
|
1706
|
+
gap: 12px;
|
|
1707
|
+
flex: 0 0 auto;
|
|
1708
|
+
}
|
|
1621
1709
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
height: 16px;
|
|
1628
|
-
font-size: 10px;
|
|
1629
|
-
font-weight: 700;
|
|
1630
|
-
border-radius: 3px;
|
|
1631
|
-
text-transform: uppercase;
|
|
1632
|
-
flex: 0 0 auto;
|
|
1633
|
-
}
|
|
1710
|
+
.logo-icon {
|
|
1711
|
+
font-size: 20px;
|
|
1712
|
+
color: var(--blue);
|
|
1713
|
+
font-weight: 700;
|
|
1714
|
+
}
|
|
1634
1715
|
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1716
|
+
.logo-info {
|
|
1717
|
+
display: flex;
|
|
1718
|
+
flex-direction: column;
|
|
1719
|
+
}
|
|
1639
1720
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1721
|
+
.logo-title {
|
|
1722
|
+
font-size: 13px;
|
|
1723
|
+
font-weight: 600;
|
|
1724
|
+
line-height: 1;
|
|
1725
|
+
color: var(--text-primary);
|
|
1726
|
+
}
|
|
1644
1727
|
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1728
|
+
.logo-version {
|
|
1729
|
+
font-size: 11px;
|
|
1730
|
+
color: var(--text-secondary);
|
|
1731
|
+
margin-top: 2px;
|
|
1732
|
+
}
|
|
1649
1733
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1734
|
+
.status-bar-center {
|
|
1735
|
+
flex: 1;
|
|
1736
|
+
display: flex;
|
|
1737
|
+
justify-content: center;
|
|
1738
|
+
}
|
|
1654
1739
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1740
|
+
.sovereignty-badge {
|
|
1741
|
+
display: flex;
|
|
1742
|
+
align-items: center;
|
|
1743
|
+
gap: 8px;
|
|
1744
|
+
padding: 8px 16px;
|
|
1745
|
+
background-color: rgba(63, 185, 80, 0.1);
|
|
1746
|
+
border: 1px solid rgba(63, 185, 80, 0.3);
|
|
1747
|
+
border-radius: 6px;
|
|
1748
|
+
font-size: 13px;
|
|
1749
|
+
font-weight: 500;
|
|
1750
|
+
}
|
|
1658
1751
|
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1752
|
+
.sovereignty-badge.degraded {
|
|
1753
|
+
background-color: rgba(210, 153, 34, 0.1);
|
|
1754
|
+
border-color: rgba(210, 153, 34, 0.3);
|
|
1755
|
+
}
|
|
1662
1756
|
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
}
|
|
1757
|
+
.sovereignty-badge.inactive {
|
|
1758
|
+
background-color: rgba(248, 81, 73, 0.1);
|
|
1759
|
+
border-color: rgba(248, 81, 73, 0.3);
|
|
1760
|
+
}
|
|
1668
1761
|
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
background: rgba(88, 166, 255, 0.08);
|
|
1674
|
-
border-left: 2px solid var(--blue);
|
|
1675
|
-
border-radius: 4px;
|
|
1676
|
-
}
|
|
1762
|
+
.sovereignty-score {
|
|
1763
|
+
font-weight: 700;
|
|
1764
|
+
color: var(--green);
|
|
1765
|
+
}
|
|
1677
1766
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
align-items: center;
|
|
1682
|
-
justify-content: center;
|
|
1683
|
-
height: 100%;
|
|
1684
|
-
color: var(--text-secondary);
|
|
1685
|
-
}
|
|
1767
|
+
.sovereignty-badge.degraded .sovereignty-score {
|
|
1768
|
+
color: var(--amber);
|
|
1769
|
+
}
|
|
1686
1770
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
}
|
|
1771
|
+
.sovereignty-badge.inactive .sovereignty-score {
|
|
1772
|
+
color: var(--red);
|
|
1773
|
+
}
|
|
1691
1774
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1775
|
+
.status-bar-right {
|
|
1776
|
+
display: flex;
|
|
1777
|
+
align-items: center;
|
|
1778
|
+
gap: 16px;
|
|
1779
|
+
flex: 0 0 auto;
|
|
1780
|
+
}
|
|
1695
1781
|
|
|
1696
|
-
|
|
1782
|
+
.status-item {
|
|
1783
|
+
display: flex;
|
|
1784
|
+
align-items: center;
|
|
1785
|
+
gap: 6px;
|
|
1786
|
+
font-size: 12px;
|
|
1787
|
+
color: var(--text-secondary);
|
|
1788
|
+
}
|
|
1697
1789
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
background: rgba(22, 27, 34, 0.5);
|
|
1703
|
-
overflow: hidden;
|
|
1704
|
-
}
|
|
1790
|
+
.status-item strong {
|
|
1791
|
+
color: var(--text-primary);
|
|
1792
|
+
font-weight: 500;
|
|
1793
|
+
}
|
|
1705
1794
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
letter-spacing: 0.5px;
|
|
1713
|
-
color: var(--text-secondary);
|
|
1714
|
-
display: flex;
|
|
1715
|
-
align-items: center;
|
|
1716
|
-
gap: 8px;
|
|
1717
|
-
}
|
|
1795
|
+
.status-dot {
|
|
1796
|
+
width: 8px;
|
|
1797
|
+
height: 8px;
|
|
1798
|
+
border-radius: 50%;
|
|
1799
|
+
background-color: var(--green);
|
|
1800
|
+
}
|
|
1718
1801
|
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
padding: 16px 16px;
|
|
1723
|
-
display: grid;
|
|
1724
|
-
grid-template-columns: 1fr 1fr;
|
|
1725
|
-
gap: 12px;
|
|
1726
|
-
}
|
|
1802
|
+
.status-dot.disconnected {
|
|
1803
|
+
background-color: var(--red);
|
|
1804
|
+
}
|
|
1727
1805
|
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1806
|
+
.pending-badge {
|
|
1807
|
+
display: flex;
|
|
1808
|
+
align-items: center;
|
|
1809
|
+
gap: 6px;
|
|
1810
|
+
padding: 4px 8px;
|
|
1811
|
+
background-color: var(--blue);
|
|
1812
|
+
color: var(--bg);
|
|
1813
|
+
border-radius: 4px;
|
|
1814
|
+
font-size: 11px;
|
|
1815
|
+
font-weight: 600;
|
|
1816
|
+
cursor: pointer;
|
|
1817
|
+
}
|
|
1737
1818
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1819
|
+
/* Main Content */
|
|
1820
|
+
.main-content {
|
|
1821
|
+
flex: 1;
|
|
1822
|
+
margin-top: 56px;
|
|
1823
|
+
overflow-y: auto;
|
|
1824
|
+
padding: 24px;
|
|
1825
|
+
}
|
|
1741
1826
|
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
letter-spacing: 0.5px;
|
|
1747
|
-
color: var(--text-secondary);
|
|
1748
|
-
}
|
|
1827
|
+
.grid {
|
|
1828
|
+
display: grid;
|
|
1829
|
+
gap: 20px;
|
|
1830
|
+
}
|
|
1749
1831
|
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
}
|
|
1832
|
+
/* Row 1: Sovereignty Layers */
|
|
1833
|
+
.sovereignty-layers {
|
|
1834
|
+
display: grid;
|
|
1835
|
+
grid-template-columns: repeat(4, 1fr);
|
|
1836
|
+
gap: 16px;
|
|
1837
|
+
}
|
|
1757
1838
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1839
|
+
.layer-card {
|
|
1840
|
+
background-color: var(--surface);
|
|
1841
|
+
border: 1px solid var(--border);
|
|
1842
|
+
border-radius: 8px;
|
|
1843
|
+
padding: 20px;
|
|
1844
|
+
display: flex;
|
|
1845
|
+
flex-direction: column;
|
|
1846
|
+
gap: 12px;
|
|
1847
|
+
}
|
|
1761
1848
|
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1849
|
+
.layer-card.degraded {
|
|
1850
|
+
border-color: var(--amber);
|
|
1851
|
+
background-color: rgba(210, 153, 34, 0.05);
|
|
1852
|
+
}
|
|
1765
1853
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
margin-top: 4px;
|
|
1771
|
-
}
|
|
1854
|
+
.layer-card.inactive {
|
|
1855
|
+
border-color: var(--red);
|
|
1856
|
+
background-color: rgba(248, 81, 73, 0.05);
|
|
1857
|
+
}
|
|
1772
1858
|
|
|
1773
|
-
|
|
1859
|
+
.layer-name {
|
|
1860
|
+
font-size: 12px;
|
|
1861
|
+
font-weight: 600;
|
|
1862
|
+
color: var(--text-secondary);
|
|
1863
|
+
text-transform: uppercase;
|
|
1864
|
+
letter-spacing: 0.5px;
|
|
1865
|
+
}
|
|
1774
1866
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
width: 0;
|
|
1781
|
-
background: var(--surface);
|
|
1782
|
-
border-left: 1px solid var(--border);
|
|
1783
|
-
z-index: 999;
|
|
1784
|
-
overflow-y: auto;
|
|
1785
|
-
transition: width 0.3s ease-out;
|
|
1786
|
-
display: flex;
|
|
1787
|
-
flex-direction: column;
|
|
1788
|
-
}
|
|
1867
|
+
.layer-title {
|
|
1868
|
+
font-size: 14px;
|
|
1869
|
+
font-weight: 600;
|
|
1870
|
+
color: var(--text-primary);
|
|
1871
|
+
}
|
|
1789
1872
|
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1873
|
+
.layer-status {
|
|
1874
|
+
display: inline-flex;
|
|
1875
|
+
align-items: center;
|
|
1876
|
+
gap: 6px;
|
|
1877
|
+
padding: 4px 8px;
|
|
1878
|
+
background-color: rgba(63, 185, 80, 0.15);
|
|
1879
|
+
color: var(--green);
|
|
1880
|
+
border-radius: 4px;
|
|
1881
|
+
font-size: 11px;
|
|
1882
|
+
font-weight: 600;
|
|
1883
|
+
width: fit-content;
|
|
1884
|
+
}
|
|
1793
1885
|
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
right: auto;
|
|
1798
|
-
left: 0;
|
|
1886
|
+
.layer-card.degraded .layer-status {
|
|
1887
|
+
background-color: rgba(210, 153, 34, 0.15);
|
|
1888
|
+
color: var(--amber);
|
|
1799
1889
|
}
|
|
1800
|
-
}
|
|
1801
1890
|
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
align-items: center;
|
|
1807
|
-
justify-content: space-between;
|
|
1808
|
-
flex: 0 0 auto;
|
|
1809
|
-
}
|
|
1891
|
+
.layer-card.inactive .layer-status {
|
|
1892
|
+
background-color: rgba(248, 81, 73, 0.15);
|
|
1893
|
+
color: var(--red);
|
|
1894
|
+
}
|
|
1810
1895
|
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1896
|
+
.layer-detail {
|
|
1897
|
+
font-size: 12px;
|
|
1898
|
+
color: var(--text-secondary);
|
|
1899
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1900
|
+
padding: 8px;
|
|
1901
|
+
background-color: var(--bg);
|
|
1902
|
+
border-radius: 4px;
|
|
1903
|
+
border-left: 2px solid var(--blue);
|
|
1904
|
+
}
|
|
1818
1905
|
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
padding: 0;
|
|
1826
|
-
display: flex;
|
|
1827
|
-
align-items: center;
|
|
1828
|
-
justify-content: center;
|
|
1829
|
-
}
|
|
1906
|
+
/* Row 2: Info Cards */
|
|
1907
|
+
.info-cards {
|
|
1908
|
+
display: grid;
|
|
1909
|
+
grid-template-columns: repeat(3, 1fr);
|
|
1910
|
+
gap: 16px;
|
|
1911
|
+
}
|
|
1830
1912
|
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1913
|
+
.info-card {
|
|
1914
|
+
background-color: var(--surface);
|
|
1915
|
+
border: 1px solid var(--border);
|
|
1916
|
+
border-radius: 8px;
|
|
1917
|
+
padding: 20px;
|
|
1918
|
+
}
|
|
1834
1919
|
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1920
|
+
.card-header {
|
|
1921
|
+
font-size: 12px;
|
|
1922
|
+
font-weight: 600;
|
|
1923
|
+
color: var(--text-secondary);
|
|
1924
|
+
text-transform: uppercase;
|
|
1925
|
+
letter-spacing: 0.5px;
|
|
1926
|
+
margin-bottom: 16px;
|
|
1927
|
+
}
|
|
1839
1928
|
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1929
|
+
.card-row {
|
|
1930
|
+
display: flex;
|
|
1931
|
+
justify-content: space-between;
|
|
1932
|
+
align-items: center;
|
|
1933
|
+
margin-bottom: 12px;
|
|
1934
|
+
font-size: 13px;
|
|
1935
|
+
}
|
|
1847
1936
|
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
gap: 8px;
|
|
1852
|
-
}
|
|
1937
|
+
.card-row:last-child {
|
|
1938
|
+
margin-bottom: 0;
|
|
1939
|
+
}
|
|
1853
1940
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
font-weight: 600;
|
|
1858
|
-
color: var(--text-primary);
|
|
1859
|
-
flex: 1;
|
|
1860
|
-
}
|
|
1941
|
+
.card-label {
|
|
1942
|
+
color: var(--text-secondary);
|
|
1943
|
+
}
|
|
1861
1944
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
height: 20px;
|
|
1868
|
-
font-size: 9px;
|
|
1869
|
-
font-weight: 700;
|
|
1870
|
-
border-radius: 3px;
|
|
1871
|
-
text-transform: uppercase;
|
|
1872
|
-
color: white;
|
|
1873
|
-
}
|
|
1945
|
+
.card-value {
|
|
1946
|
+
color: var(--text-primary);
|
|
1947
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1948
|
+
font-weight: 500;
|
|
1949
|
+
}
|
|
1874
1950
|
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1951
|
+
.identity-badge {
|
|
1952
|
+
display: inline-flex;
|
|
1953
|
+
align-items: center;
|
|
1954
|
+
gap: 4px;
|
|
1955
|
+
padding: 2px 6px;
|
|
1956
|
+
background-color: rgba(88, 166, 255, 0.15);
|
|
1957
|
+
color: var(--blue);
|
|
1958
|
+
border-radius: 3px;
|
|
1959
|
+
font-size: 10px;
|
|
1960
|
+
font-weight: 600;
|
|
1961
|
+
text-transform: uppercase;
|
|
1962
|
+
}
|
|
1878
1963
|
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1964
|
+
.trust-tier-badge {
|
|
1965
|
+
display: inline-flex;
|
|
1966
|
+
align-items: center;
|
|
1967
|
+
gap: 4px;
|
|
1968
|
+
padding: 2px 6px;
|
|
1969
|
+
background-color: rgba(63, 185, 80, 0.15);
|
|
1970
|
+
color: var(--green);
|
|
1971
|
+
border-radius: 3px;
|
|
1972
|
+
font-size: 10px;
|
|
1973
|
+
font-weight: 600;
|
|
1974
|
+
}
|
|
1882
1975
|
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1976
|
+
.truncated {
|
|
1977
|
+
max-width: 200px;
|
|
1978
|
+
overflow: hidden;
|
|
1979
|
+
text-overflow: ellipsis;
|
|
1980
|
+
white-space: nowrap;
|
|
1981
|
+
}
|
|
1887
1982
|
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
}
|
|
1983
|
+
/* Row 3: SHR & Activity */
|
|
1984
|
+
.main-panels {
|
|
1985
|
+
display: grid;
|
|
1986
|
+
grid-template-columns: 1fr 1fr;
|
|
1987
|
+
gap: 16px;
|
|
1988
|
+
min-height: 400px;
|
|
1989
|
+
}
|
|
1896
1990
|
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1991
|
+
.panel {
|
|
1992
|
+
background-color: var(--surface);
|
|
1993
|
+
border: 1px solid var(--border);
|
|
1994
|
+
border-radius: 8px;
|
|
1995
|
+
display: flex;
|
|
1996
|
+
flex-direction: column;
|
|
1997
|
+
overflow: hidden;
|
|
1998
|
+
}
|
|
1904
1999
|
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
2000
|
+
.panel-header {
|
|
2001
|
+
padding: 16px 20px;
|
|
2002
|
+
border-bottom: 1px solid var(--border);
|
|
2003
|
+
display: flex;
|
|
2004
|
+
justify-content: space-between;
|
|
2005
|
+
align-items: center;
|
|
2006
|
+
}
|
|
1910
2007
|
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
2008
|
+
.panel-title {
|
|
2009
|
+
font-size: 14px;
|
|
2010
|
+
font-weight: 600;
|
|
2011
|
+
color: var(--text-primary);
|
|
2012
|
+
}
|
|
1914
2013
|
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
2014
|
+
.panel-action {
|
|
2015
|
+
background: none;
|
|
2016
|
+
border: none;
|
|
2017
|
+
color: var(--blue);
|
|
2018
|
+
cursor: pointer;
|
|
2019
|
+
font-size: 12px;
|
|
2020
|
+
padding: 0;
|
|
2021
|
+
font-weight: 500;
|
|
2022
|
+
transition: color 0.2s;
|
|
2023
|
+
}
|
|
1919
2024
|
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
border: none;
|
|
1924
|
-
border-radius: var(--radius);
|
|
1925
|
-
font-size: 12px;
|
|
1926
|
-
font-weight: 600;
|
|
1927
|
-
cursor: pointer;
|
|
1928
|
-
transition: all 0.15s;
|
|
1929
|
-
font-family: var(--sans);
|
|
1930
|
-
}
|
|
2025
|
+
.panel-action:hover {
|
|
2026
|
+
color: #79c0ff;
|
|
2027
|
+
}
|
|
1931
2028
|
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
2029
|
+
.panel-content {
|
|
2030
|
+
flex: 1;
|
|
2031
|
+
overflow-y: auto;
|
|
2032
|
+
padding: 20px;
|
|
2033
|
+
}
|
|
1936
2034
|
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
2035
|
+
/* SHR Viewer */
|
|
2036
|
+
.shr-json {
|
|
2037
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2038
|
+
font-size: 12px;
|
|
2039
|
+
line-height: 1.6;
|
|
2040
|
+
color: var(--text-secondary);
|
|
2041
|
+
}
|
|
1940
2042
|
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
}
|
|
2043
|
+
.shr-section {
|
|
2044
|
+
margin-bottom: 12px;
|
|
2045
|
+
}
|
|
1945
2046
|
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2047
|
+
.shr-section-header {
|
|
2048
|
+
display: flex;
|
|
2049
|
+
align-items: center;
|
|
2050
|
+
gap: 8px;
|
|
2051
|
+
cursor: pointer;
|
|
2052
|
+
font-weight: 600;
|
|
2053
|
+
color: var(--text-primary);
|
|
2054
|
+
padding: 8px;
|
|
2055
|
+
background-color: var(--bg);
|
|
2056
|
+
border-radius: 4px;
|
|
2057
|
+
user-select: none;
|
|
2058
|
+
}
|
|
1949
2059
|
|
|
1950
|
-
|
|
2060
|
+
.shr-section-header:hover {
|
|
2061
|
+
background-color: var(--muted);
|
|
2062
|
+
}
|
|
1951
2063
|
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
display: flex;
|
|
1962
|
-
flex-direction: column;
|
|
1963
|
-
transition: max-height 0.3s ease-out;
|
|
1964
|
-
}
|
|
2064
|
+
.shr-toggle {
|
|
2065
|
+
width: 16px;
|
|
2066
|
+
height: 16px;
|
|
2067
|
+
display: flex;
|
|
2068
|
+
align-items: center;
|
|
2069
|
+
justify-content: center;
|
|
2070
|
+
font-size: 10px;
|
|
2071
|
+
transition: transform 0.2s;
|
|
2072
|
+
}
|
|
1965
2073
|
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2074
|
+
.shr-section.collapsed .shr-toggle {
|
|
2075
|
+
transform: rotate(-90deg);
|
|
2076
|
+
}
|
|
1969
2077
|
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
font-size: 12px;
|
|
1977
|
-
font-weight: 600;
|
|
1978
|
-
text-transform: uppercase;
|
|
1979
|
-
letter-spacing: 0.5px;
|
|
1980
|
-
color: var(--text-secondary);
|
|
1981
|
-
flex: 0 0 auto;
|
|
1982
|
-
}
|
|
2078
|
+
.shr-section-content {
|
|
2079
|
+
padding: 8px 16px;
|
|
2080
|
+
background-color: rgba(0, 0, 0, 0.2);
|
|
2081
|
+
border-radius: 4px;
|
|
2082
|
+
margin-top: 4px;
|
|
2083
|
+
}
|
|
1983
2084
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
2085
|
+
.shr-section.collapsed .shr-section-content {
|
|
2086
|
+
display: none;
|
|
2087
|
+
}
|
|
1987
2088
|
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
2089
|
+
.shr-item {
|
|
2090
|
+
display: flex;
|
|
2091
|
+
margin-bottom: 4px;
|
|
2092
|
+
}
|
|
1991
2093
|
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
flex-direction: column;
|
|
1998
|
-
gap: 10px;
|
|
1999
|
-
}
|
|
2094
|
+
.shr-key {
|
|
2095
|
+
color: var(--blue);
|
|
2096
|
+
margin-right: 8px;
|
|
2097
|
+
min-width: 120px;
|
|
2098
|
+
}
|
|
2000
2099
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
border-radius: 4px;
|
|
2006
|
-
font-size: 11px;
|
|
2007
|
-
color: var(--text-secondary);
|
|
2008
|
-
}
|
|
2100
|
+
.shr-value {
|
|
2101
|
+
color: var(--green);
|
|
2102
|
+
word-break: break-all;
|
|
2103
|
+
}
|
|
2009
2104
|
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2105
|
+
/* Activity Feed */
|
|
2106
|
+
.activity-feed {
|
|
2107
|
+
display: flex;
|
|
2108
|
+
flex-direction: column;
|
|
2109
|
+
gap: 12px;
|
|
2110
|
+
}
|
|
2015
2111
|
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2112
|
+
.activity-item {
|
|
2113
|
+
padding: 12px;
|
|
2114
|
+
background-color: var(--bg);
|
|
2115
|
+
border-left: 2px solid var(--border);
|
|
2116
|
+
border-radius: 4px;
|
|
2117
|
+
font-size: 12px;
|
|
2118
|
+
}
|
|
2022
2119
|
|
|
2023
|
-
|
|
2120
|
+
.activity-item.tool-call {
|
|
2121
|
+
border-left-color: var(--blue);
|
|
2122
|
+
}
|
|
2024
2123
|
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2124
|
+
.activity-item.context-gate {
|
|
2125
|
+
border-left-color: var(--amber);
|
|
2126
|
+
}
|
|
2028
2127
|
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2128
|
+
.activity-item.injection {
|
|
2129
|
+
border-left-color: var(--red);
|
|
2130
|
+
}
|
|
2032
2131
|
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
}
|
|
2132
|
+
.activity-item.protection {
|
|
2133
|
+
border-left-color: var(--green);
|
|
2134
|
+
}
|
|
2037
2135
|
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2136
|
+
.activity-type {
|
|
2137
|
+
font-weight: 600;
|
|
2138
|
+
color: var(--text-primary);
|
|
2139
|
+
margin-bottom: 4px;
|
|
2140
|
+
text-transform: uppercase;
|
|
2141
|
+
font-size: 11px;
|
|
2142
|
+
letter-spacing: 0.5px;
|
|
2143
|
+
}
|
|
2041
2144
|
|
|
2042
|
-
|
|
2145
|
+
.activity-content {
|
|
2146
|
+
color: var(--text-secondary);
|
|
2147
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2148
|
+
margin-bottom: 4px;
|
|
2149
|
+
word-break: break-all;
|
|
2150
|
+
}
|
|
2043
2151
|
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2152
|
+
.activity-time {
|
|
2153
|
+
font-size: 11px;
|
|
2154
|
+
color: var(--text-secondary);
|
|
2047
2155
|
}
|
|
2048
2156
|
|
|
2049
|
-
.
|
|
2050
|
-
|
|
2157
|
+
.empty-state {
|
|
2158
|
+
display: flex;
|
|
2159
|
+
align-items: center;
|
|
2160
|
+
justify-content: center;
|
|
2161
|
+
height: 100%;
|
|
2162
|
+
color: var(--text-secondary);
|
|
2163
|
+
font-size: 13px;
|
|
2051
2164
|
}
|
|
2052
|
-
}
|
|
2053
2165
|
|
|
2054
|
-
|
|
2055
|
-
.
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2166
|
+
/* Row 4: Handshake History */
|
|
2167
|
+
.handshake-table {
|
|
2168
|
+
background-color: var(--surface);
|
|
2169
|
+
border: 1px solid var(--border);
|
|
2170
|
+
border-radius: 8px;
|
|
2171
|
+
overflow: hidden;
|
|
2059
2172
|
}
|
|
2060
2173
|
|
|
2061
|
-
.
|
|
2062
|
-
|
|
2174
|
+
.table-header {
|
|
2175
|
+
display: grid;
|
|
2176
|
+
grid-template-columns: 2fr 1fr 1fr 1fr 1.5fr 1.5fr;
|
|
2177
|
+
gap: 16px;
|
|
2178
|
+
padding: 16px 20px;
|
|
2179
|
+
border-bottom: 1px solid var(--border);
|
|
2180
|
+
background-color: var(--bg);
|
|
2181
|
+
font-size: 12px;
|
|
2182
|
+
font-weight: 600;
|
|
2183
|
+
color: var(--text-secondary);
|
|
2184
|
+
text-transform: uppercase;
|
|
2185
|
+
letter-spacing: 0.5px;
|
|
2063
2186
|
}
|
|
2064
2187
|
|
|
2065
|
-
.
|
|
2066
|
-
|
|
2188
|
+
.table-rows {
|
|
2189
|
+
max-height: 300px;
|
|
2190
|
+
overflow-y: auto;
|
|
2067
2191
|
}
|
|
2068
2192
|
|
|
2069
|
-
.
|
|
2070
|
-
|
|
2193
|
+
.table-row {
|
|
2194
|
+
display: grid;
|
|
2195
|
+
grid-template-columns: 2fr 1fr 1fr 1fr 1.5fr 1.5fr;
|
|
2196
|
+
gap: 16px;
|
|
2197
|
+
padding: 14px 20px;
|
|
2198
|
+
border-bottom: 1px solid var(--border);
|
|
2199
|
+
align-items: center;
|
|
2200
|
+
font-size: 12px;
|
|
2201
|
+
cursor: pointer;
|
|
2202
|
+
transition: background-color 0.2s;
|
|
2071
2203
|
}
|
|
2072
2204
|
|
|
2073
|
-
.
|
|
2074
|
-
|
|
2205
|
+
.table-row:hover {
|
|
2206
|
+
background-color: var(--bg);
|
|
2075
2207
|
}
|
|
2076
2208
|
|
|
2077
|
-
.
|
|
2078
|
-
|
|
2209
|
+
.table-row:last-child {
|
|
2210
|
+
border-bottom: none;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
.table-cell {
|
|
2214
|
+
color: var(--text-secondary);
|
|
2215
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
.table-cell.strong {
|
|
2219
|
+
color: var(--text-primary);
|
|
2220
|
+
font-weight: 500;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
.table-empty {
|
|
2224
|
+
padding: 40px 20px;
|
|
2225
|
+
text-align: center;
|
|
2226
|
+
color: var(--text-secondary);
|
|
2227
|
+
font-size: 13px;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
/* Pending Overlay */
|
|
2231
|
+
.pending-overlay {
|
|
2232
|
+
position: fixed;
|
|
2233
|
+
top: 0;
|
|
2234
|
+
right: -400px;
|
|
2235
|
+
width: 400px;
|
|
2236
|
+
height: 100vh;
|
|
2237
|
+
background-color: var(--surface);
|
|
2238
|
+
border-left: 1px solid var(--border);
|
|
2239
|
+
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3);
|
|
2240
|
+
z-index: 200;
|
|
2241
|
+
transition: right 0.3s ease;
|
|
2242
|
+
display: flex;
|
|
2243
|
+
flex-direction: column;
|
|
2244
|
+
overflow-y: auto;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
.pending-overlay.show {
|
|
2248
|
+
right: 0;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
.pending-header {
|
|
2252
|
+
padding: 16px 20px;
|
|
2253
|
+
border-bottom: 1px solid var(--border);
|
|
2254
|
+
font-weight: 600;
|
|
2255
|
+
color: var(--text-primary);
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
.pending-items {
|
|
2259
|
+
flex: 1;
|
|
2260
|
+
overflow-y: auto;
|
|
2261
|
+
padding: 16px;
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
.pending-item {
|
|
2265
|
+
background-color: var(--bg);
|
|
2266
|
+
border: 1px solid var(--border);
|
|
2267
|
+
border-radius: 6px;
|
|
2268
|
+
padding: 16px;
|
|
2269
|
+
margin-bottom: 12px;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
.pending-title {
|
|
2273
|
+
font-weight: 600;
|
|
2274
|
+
color: var(--text-primary);
|
|
2275
|
+
margin-bottom: 8px;
|
|
2276
|
+
word-break: break-word;
|
|
2079
2277
|
}
|
|
2080
2278
|
|
|
2279
|
+
.pending-countdown {
|
|
2280
|
+
font-size: 12px;
|
|
2281
|
+
color: var(--amber);
|
|
2282
|
+
margin-bottom: 12px;
|
|
2283
|
+
font-weight: 500;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
.pending-actions {
|
|
2287
|
+
display: flex;
|
|
2288
|
+
gap: 8px;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
.pending-btn {
|
|
2292
|
+
flex: 1;
|
|
2293
|
+
padding: 8px 12px;
|
|
2294
|
+
border: none;
|
|
2295
|
+
border-radius: 4px;
|
|
2296
|
+
font-size: 12px;
|
|
2297
|
+
font-weight: 600;
|
|
2298
|
+
cursor: pointer;
|
|
2299
|
+
transition: background-color 0.2s;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
.pending-approve {
|
|
2303
|
+
background-color: var(--green);
|
|
2304
|
+
color: var(--bg);
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
.pending-approve:hover {
|
|
2308
|
+
background-color: #3fa040;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
.pending-deny {
|
|
2312
|
+
background-color: var(--red);
|
|
2313
|
+
color: var(--bg);
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
.pending-deny:hover {
|
|
2317
|
+
background-color: #e03c3c;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
/* Threat Panel */
|
|
2081
2321
|
.threat-panel {
|
|
2082
|
-
|
|
2322
|
+
background-color: var(--surface);
|
|
2323
|
+
border: 1px solid var(--border);
|
|
2324
|
+
border-radius: 8px;
|
|
2325
|
+
margin-top: 20px;
|
|
2326
|
+
overflow: hidden;
|
|
2083
2327
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2328
|
+
|
|
2329
|
+
.threat-header {
|
|
2330
|
+
padding: 16px 20px;
|
|
2331
|
+
border-bottom: 1px solid var(--border);
|
|
2332
|
+
display: flex;
|
|
2333
|
+
justify-content: space-between;
|
|
2334
|
+
align-items: center;
|
|
2335
|
+
cursor: pointer;
|
|
2336
|
+
user-select: none;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
.threat-title {
|
|
2340
|
+
font-weight: 600;
|
|
2341
|
+
color: var(--text-primary);
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
.threat-toggle {
|
|
2345
|
+
font-size: 10px;
|
|
2346
|
+
color: var(--text-secondary);
|
|
2347
|
+
transition: transform 0.2s;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
.threat-panel.collapsed .threat-toggle {
|
|
2351
|
+
transform: rotate(-90deg);
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
.threat-content {
|
|
2355
|
+
padding: 16px 20px;
|
|
2356
|
+
max-height: 300px;
|
|
2357
|
+
overflow-y: auto;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
.threat-panel.collapsed .threat-content {
|
|
2361
|
+
display: none;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
.threat-alert {
|
|
2365
|
+
background-color: rgba(248, 81, 73, 0.1);
|
|
2366
|
+
border: 1px solid var(--red);
|
|
2367
|
+
border-radius: 4px;
|
|
2368
|
+
padding: 12px;
|
|
2369
|
+
margin-bottom: 8px;
|
|
2370
|
+
font-size: 12px;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
.threat-alert:last-child {
|
|
2374
|
+
margin-bottom: 0;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
.threat-type {
|
|
2378
|
+
font-weight: 600;
|
|
2379
|
+
color: var(--red);
|
|
2380
|
+
margin-bottom: 4px;
|
|
2381
|
+
text-transform: uppercase;
|
|
2382
|
+
font-size: 10px;
|
|
2383
|
+
letter-spacing: 0.5px;
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
.threat-message {
|
|
2387
|
+
color: var(--text-secondary);
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
/* Scrollbar */
|
|
2391
|
+
::-webkit-scrollbar {
|
|
2392
|
+
width: 8px;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
::-webkit-scrollbar-track {
|
|
2396
|
+
background-color: transparent;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
::-webkit-scrollbar-thumb {
|
|
2400
|
+
background-color: var(--border);
|
|
2401
|
+
border-radius: 4px;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
::-webkit-scrollbar-thumb:hover {
|
|
2405
|
+
background-color: var(--text-secondary);
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
/* Responsive */
|
|
2409
|
+
@media (max-width: 1400px) {
|
|
2410
|
+
.sovereignty-layers {
|
|
2411
|
+
grid-template-columns: repeat(2, 1fr);
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
.main-panels {
|
|
2415
|
+
grid-template-columns: 1fr;
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
.pending-overlay {
|
|
2419
|
+
width: 100%;
|
|
2420
|
+
right: -100%;
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
@media (max-width: 768px) {
|
|
2425
|
+
.status-bar {
|
|
2426
|
+
flex-wrap: wrap;
|
|
2427
|
+
height: auto;
|
|
2428
|
+
padding: 12px;
|
|
2429
|
+
gap: 12px;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
.status-bar-center {
|
|
2433
|
+
order: 3;
|
|
2434
|
+
flex-basis: 100%;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
.main-content {
|
|
2438
|
+
margin-top: auto;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
.info-cards {
|
|
2442
|
+
grid-template-columns: 1fr;
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
.table-header,
|
|
2446
|
+
.table-row {
|
|
2447
|
+
grid-template-columns: 1fr;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
</style>
|
|
2086
2451
|
</head>
|
|
2087
2452
|
<body>
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
<div class="status-bar">
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
<div class="status-bar-center">
|
|
2096
|
-
<div class="sovereignty-badge">
|
|
2097
|
-
<div class="sovereignty-score" id="sovereigntyScore">85</div>
|
|
2098
|
-
<span>Sovereignty Health</span>
|
|
2099
|
-
</div>
|
|
2100
|
-
</div>
|
|
2101
|
-
<div class="status-bar-right">
|
|
2102
|
-
<div class="protections-indicator">
|
|
2103
|
-
<span class="count" id="activeProtections">6</span>/6 protections
|
|
2104
|
-
</div>
|
|
2105
|
-
<div class="uptime">
|
|
2106
|
-
<span id="uptimeText">\u2014</span>
|
|
2107
|
-
</div>
|
|
2108
|
-
<div class="status-dot" id="statusDot"></div>
|
|
2109
|
-
<div class="pending-badge hidden" id="pendingBadge">0</div>
|
|
2110
|
-
</div>
|
|
2111
|
-
</div>
|
|
2112
|
-
|
|
2113
|
-
<!-- Main Layout -->
|
|
2114
|
-
<div class="main-container">
|
|
2115
|
-
<!-- Activity Feed -->
|
|
2116
|
-
<div class="activity-feed">
|
|
2117
|
-
<div class="feed-header">
|
|
2118
|
-
<div class="feed-header-dot"></div>
|
|
2119
|
-
Live Activity
|
|
2120
|
-
</div>
|
|
2121
|
-
<div class="activity-list" id="activityList">
|
|
2122
|
-
<div class="activity-empty">
|
|
2123
|
-
<div class="activity-empty-icon">\u2192</div>
|
|
2124
|
-
<div class="activity-empty-text">Waiting for activity...</div>
|
|
2453
|
+
<!-- Status Bar -->
|
|
2454
|
+
<div class="status-bar">
|
|
2455
|
+
<div class="status-bar-left">
|
|
2456
|
+
<div class="logo-icon">\u25C6</div>
|
|
2457
|
+
<div class="logo-info">
|
|
2458
|
+
<div class="logo-title">SANCTUARY</div>
|
|
2459
|
+
<div class="logo-version">v${options.serverVersion}</div>
|
|
2125
2460
|
</div>
|
|
2126
2461
|
</div>
|
|
2127
|
-
</div>
|
|
2128
2462
|
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2463
|
+
<div class="status-bar-center">
|
|
2464
|
+
<div id="sovereignty-badge" class="sovereignty-badge">
|
|
2465
|
+
<span>Sovereignty Health:</span>
|
|
2466
|
+
<span class="sovereignty-score" id="sovereignty-score">\u2014</span>
|
|
2467
|
+
<span>/ 100</span>
|
|
2468
|
+
</div>
|
|
2133
2469
|
</div>
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
<
|
|
2138
|
-
<
|
|
2139
|
-
|
|
2470
|
+
|
|
2471
|
+
<div class="status-bar-right">
|
|
2472
|
+
<div class="status-item">
|
|
2473
|
+
<strong id="protections-count">\u2014</strong>
|
|
2474
|
+
<span>Protections</span>
|
|
2475
|
+
</div>
|
|
2476
|
+
<div class="status-item">
|
|
2477
|
+
<strong id="uptime-value">\u2014</strong>
|
|
2478
|
+
<span>Uptime</span>
|
|
2479
|
+
</div>
|
|
2480
|
+
<div class="status-dot" id="connection-status"></div>
|
|
2481
|
+
<div id="pending-item-badge" class="pending-badge" style="display: none;">
|
|
2482
|
+
<span>\u23F3</span>
|
|
2483
|
+
<span id="pending-count">0</span>
|
|
2140
2484
|
</div>
|
|
2485
|
+
</div>
|
|
2486
|
+
</div>
|
|
2141
2487
|
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2488
|
+
<!-- Main Content -->
|
|
2489
|
+
<div class="main-content">
|
|
2490
|
+
<div class="grid">
|
|
2491
|
+
<!-- Row 1: Sovereignty Layers -->
|
|
2492
|
+
<div class="sovereignty-layers" id="sovereignty-layers">
|
|
2493
|
+
<div class="layer-card" data-layer="l1">
|
|
2494
|
+
<div class="layer-name">Layer 1</div>
|
|
2495
|
+
<div class="layer-title">Cognitive Sovereignty</div>
|
|
2496
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l1-status">\u2014</span></div>
|
|
2497
|
+
<div class="layer-detail" id="l1-detail">Loading...</div>
|
|
2498
|
+
</div>
|
|
2499
|
+
<div class="layer-card" data-layer="l2">
|
|
2500
|
+
<div class="layer-name">Layer 2</div>
|
|
2501
|
+
<div class="layer-title">Operational Isolation</div>
|
|
2502
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l2-status">\u2014</span></div>
|
|
2503
|
+
<div class="layer-detail" id="l2-detail">Loading...</div>
|
|
2504
|
+
</div>
|
|
2505
|
+
<div class="layer-card" data-layer="l3">
|
|
2506
|
+
<div class="layer-name">Layer 3</div>
|
|
2507
|
+
<div class="layer-title">Selective Disclosure</div>
|
|
2508
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l3-status">\u2014</span></div>
|
|
2509
|
+
<div class="layer-detail" id="l3-detail">Loading...</div>
|
|
2510
|
+
</div>
|
|
2511
|
+
<div class="layer-card" data-layer="l4">
|
|
2512
|
+
<div class="layer-name">Layer 4</div>
|
|
2513
|
+
<div class="layer-title">Verifiable Reputation</div>
|
|
2514
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l4-status">\u2014</span></div>
|
|
2515
|
+
<div class="layer-detail" id="l4-detail">Loading...</div>
|
|
2516
|
+
</div>
|
|
2147
2517
|
</div>
|
|
2148
2518
|
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
<div class="
|
|
2152
|
-
|
|
2153
|
-
|
|
2519
|
+
<!-- Row 2: Info Cards -->
|
|
2520
|
+
<div class="info-cards">
|
|
2521
|
+
<div class="info-card">
|
|
2522
|
+
<div class="card-header">Identity</div>
|
|
2523
|
+
<div class="card-row">
|
|
2524
|
+
<span class="card-label">Primary</span>
|
|
2525
|
+
<span class="card-value" id="identity-label">\u2014</span>
|
|
2526
|
+
</div>
|
|
2527
|
+
<div class="card-row">
|
|
2528
|
+
<span class="card-label">DID</span>
|
|
2529
|
+
<span class="card-value truncated" id="identity-did" title="">\u2014</span>
|
|
2530
|
+
</div>
|
|
2531
|
+
<div class="card-row">
|
|
2532
|
+
<span class="card-label">Public Key</span>
|
|
2533
|
+
<span class="card-value truncated" id="identity-pubkey" title="">\u2014</span>
|
|
2534
|
+
</div>
|
|
2535
|
+
<div class="card-row">
|
|
2536
|
+
<span class="card-label">Type</span>
|
|
2537
|
+
<span class="identity-badge">Ed25519</span>
|
|
2538
|
+
</div>
|
|
2539
|
+
<div class="card-row">
|
|
2540
|
+
<span class="card-label">Created</span>
|
|
2541
|
+
<span class="card-value" id="identity-created">\u2014</span>
|
|
2542
|
+
</div>
|
|
2543
|
+
<div class="card-row">
|
|
2544
|
+
<span class="card-label">Identities</span>
|
|
2545
|
+
<span class="card-value" id="identity-count">\u2014</span>
|
|
2546
|
+
</div>
|
|
2547
|
+
</div>
|
|
2548
|
+
|
|
2549
|
+
<div class="info-card">
|
|
2550
|
+
<div class="card-header">Handshakes</div>
|
|
2551
|
+
<div class="card-row">
|
|
2552
|
+
<span class="card-label">Total</span>
|
|
2553
|
+
<span class="card-value" id="handshake-count">\u2014</span>
|
|
2554
|
+
</div>
|
|
2555
|
+
<div class="card-row">
|
|
2556
|
+
<span class="card-label">Latest Peer</span>
|
|
2557
|
+
<span class="card-value truncated" id="handshake-latest">\u2014</span>
|
|
2558
|
+
</div>
|
|
2559
|
+
<div class="card-row">
|
|
2560
|
+
<span class="card-label">Trust Tier</span>
|
|
2561
|
+
<span class="trust-tier-badge" id="handshake-tier">Unverified</span>
|
|
2562
|
+
</div>
|
|
2563
|
+
<div class="card-row">
|
|
2564
|
+
<span class="card-label">Timestamp</span>
|
|
2565
|
+
<span class="card-value" id="handshake-time">\u2014</span>
|
|
2566
|
+
</div>
|
|
2567
|
+
</div>
|
|
2568
|
+
|
|
2569
|
+
<div class="info-card">
|
|
2570
|
+
<div class="card-header">Reputation</div>
|
|
2571
|
+
<div class="card-row">
|
|
2572
|
+
<span class="card-label">Weighted Score</span>
|
|
2573
|
+
<span class="card-value" id="reputation-score">\u2014</span>
|
|
2574
|
+
</div>
|
|
2575
|
+
<div class="card-row">
|
|
2576
|
+
<span class="card-label">Attestations</span>
|
|
2577
|
+
<span class="card-value" id="reputation-attestations">\u2014</span>
|
|
2578
|
+
</div>
|
|
2579
|
+
<div class="card-row">
|
|
2580
|
+
<span class="card-label">Verified Sovereign</span>
|
|
2581
|
+
<span class="card-value" id="reputation-verified">\u2014</span>
|
|
2582
|
+
</div>
|
|
2583
|
+
<div class="card-row">
|
|
2584
|
+
<span class="card-label">Verified Degraded</span>
|
|
2585
|
+
<span class="card-value" id="reputation-degraded">\u2014</span>
|
|
2586
|
+
</div>
|
|
2587
|
+
<div class="card-row">
|
|
2588
|
+
<span class="card-label">Unverified</span>
|
|
2589
|
+
<span class="card-value" id="reputation-unverified">\u2014</span>
|
|
2590
|
+
</div>
|
|
2591
|
+
</div>
|
|
2154
2592
|
</div>
|
|
2155
2593
|
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
<div class="
|
|
2159
|
-
|
|
2160
|
-
|
|
2594
|
+
<!-- Row 3: SHR & Activity -->
|
|
2595
|
+
<div class="main-panels">
|
|
2596
|
+
<div class="panel">
|
|
2597
|
+
<div class="panel-header">
|
|
2598
|
+
<div class="panel-title">Sovereignty Health Report</div>
|
|
2599
|
+
<button class="panel-action" id="copy-shr-btn">Copy JSON</button>
|
|
2600
|
+
</div>
|
|
2601
|
+
<div class="panel-content">
|
|
2602
|
+
<div class="shr-json" id="shr-viewer">
|
|
2603
|
+
<div class="empty-state">Loading SHR...</div>
|
|
2604
|
+
</div>
|
|
2605
|
+
</div>
|
|
2606
|
+
</div>
|
|
2607
|
+
|
|
2608
|
+
<div class="panel">
|
|
2609
|
+
<div class="panel-header">
|
|
2610
|
+
<div class="panel-title">Activity Feed</div>
|
|
2611
|
+
</div>
|
|
2612
|
+
<div class="panel-content">
|
|
2613
|
+
<div id="activity-feed" class="activity-feed">
|
|
2614
|
+
<div class="empty-state">Waiting for activity...</div>
|
|
2615
|
+
</div>
|
|
2616
|
+
</div>
|
|
2617
|
+
</div>
|
|
2161
2618
|
</div>
|
|
2162
2619
|
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
<div class="
|
|
2166
|
-
|
|
2167
|
-
|
|
2620
|
+
<!-- Row 4: Handshake History -->
|
|
2621
|
+
<div class="handshake-table">
|
|
2622
|
+
<div class="table-header">
|
|
2623
|
+
<div>Counterparty</div>
|
|
2624
|
+
<div>Trust Tier</div>
|
|
2625
|
+
<div>Sovereignty</div>
|
|
2626
|
+
<div>Verified</div>
|
|
2627
|
+
<div>Completed</div>
|
|
2628
|
+
<div>Expires</div>
|
|
2629
|
+
</div>
|
|
2630
|
+
<div class="table-rows" id="handshake-table">
|
|
2631
|
+
<div class="table-empty">No handshakes completed yet</div>
|
|
2632
|
+
</div>
|
|
2168
2633
|
</div>
|
|
2169
2634
|
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
<div class="
|
|
2173
|
-
|
|
2174
|
-
|
|
2635
|
+
<!-- Threat Panel -->
|
|
2636
|
+
<div class="threat-panel collapsed">
|
|
2637
|
+
<div class="threat-header">
|
|
2638
|
+
<div class="threat-title">Security Threats</div>
|
|
2639
|
+
<div class="threat-toggle">\u25B6</div>
|
|
2640
|
+
</div>
|
|
2641
|
+
<div class="threat-content" id="threat-alerts">
|
|
2642
|
+
<div class="empty-state">No threats detected</div>
|
|
2643
|
+
</div>
|
|
2175
2644
|
</div>
|
|
2176
2645
|
</div>
|
|
2177
2646
|
</div>
|
|
2178
|
-
</div>
|
|
2179
2647
|
|
|
2180
|
-
<!-- Pending
|
|
2181
|
-
<div class="pending-overlay" id="
|
|
2182
|
-
|
|
2183
|
-
<div class="pending-
|
|
2184
|
-
<button class="pending-overlay-close" onclick="closePendingOverlay()">\xD7</button>
|
|
2185
|
-
</div>
|
|
2186
|
-
<div class="pending-list" id="pendingList"></div>
|
|
2187
|
-
</div>
|
|
2188
|
-
|
|
2189
|
-
<!-- Threat Panel (collapsible footer) -->
|
|
2190
|
-
<div class="threat-panel collapsed" id="threatPanel">
|
|
2191
|
-
<div class="threat-header" onclick="toggleThreatPanel()">
|
|
2192
|
-
<span class="threat-icon">\u26A0</span>
|
|
2193
|
-
Recent Threats
|
|
2194
|
-
<span id="threatCount" style="margin-left: auto; color: var(--red); font-weight: 700;">0</span>
|
|
2648
|
+
<!-- Pending Overlay -->
|
|
2649
|
+
<div class="pending-overlay" id="pending-overlay">
|
|
2650
|
+
<div class="pending-header">Pending Approvals</div>
|
|
2651
|
+
<div class="pending-items" id="pending-items"></div>
|
|
2195
2652
|
</div>
|
|
2196
|
-
<div class="threat-content" id="threatContent">
|
|
2197
|
-
<div class="threat-empty">No threats detected</div>
|
|
2198
|
-
</div>
|
|
2199
|
-
</div>
|
|
2200
2653
|
|
|
2201
|
-
<script>
|
|
2202
|
-
|
|
2203
|
-
|
|
2654
|
+
<script>
|
|
2655
|
+
// Constants
|
|
2656
|
+
const AUTH_TOKEN = '${options.authToken || ""}' || sessionStorage.getItem('authToken') || '';
|
|
2657
|
+
const TIMEOUT_SECONDS = ${options.timeoutSeconds};
|
|
2658
|
+
const API_BASE = '';
|
|
2659
|
+
|
|
2660
|
+
// State
|
|
2661
|
+
let apiState = {
|
|
2662
|
+
sovereignty: null,
|
|
2663
|
+
identity: null,
|
|
2664
|
+
handshakes: [],
|
|
2665
|
+
shr: null,
|
|
2666
|
+
status: null,
|
|
2667
|
+
};
|
|
2668
|
+
|
|
2669
|
+
let pendingRequests = new Map();
|
|
2670
|
+
let activityLog = [];
|
|
2671
|
+
const maxActivityItems = 50;
|
|
2204
2672
|
|
|
2205
|
-
|
|
2673
|
+
// Helpers
|
|
2674
|
+
function esc(text) {
|
|
2675
|
+
if (!text) return '';
|
|
2676
|
+
const div = document.createElement('div');
|
|
2677
|
+
div.textContent = text;
|
|
2678
|
+
return div.innerHTML;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
function formatTime(isoString) {
|
|
2682
|
+
if (!isoString) return '\u2014';
|
|
2683
|
+
const date = new Date(isoString);
|
|
2684
|
+
return date.toLocaleString('en-US', {
|
|
2685
|
+
month: 'short',
|
|
2686
|
+
day: 'numeric',
|
|
2687
|
+
hour: '2-digit',
|
|
2688
|
+
minute: '2-digit',
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2206
2691
|
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
const MAX_THREAT_ITEMS = 20;
|
|
2692
|
+
function truncate(str, len = 16) {
|
|
2693
|
+
if (!str) return '\u2014';
|
|
2694
|
+
if (str.length <= len) return str;
|
|
2695
|
+
return str.slice(0, len) + '...';
|
|
2696
|
+
}
|
|
2213
2697
|
|
|
2214
|
-
|
|
2698
|
+
function calculateSovereigntyScore(shr) {
|
|
2699
|
+
if (!shr || !shr.layers) return 0;
|
|
2700
|
+
const layers = shr.layers;
|
|
2701
|
+
let score = 100;
|
|
2215
2702
|
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
let sovereigntyScore = 85;
|
|
2225
|
-
let sessionRenewalTimer = null;
|
|
2703
|
+
if (layers.l1?.status === 'degraded') score -= 20;
|
|
2704
|
+
if (layers.l1?.status === 'inactive') score -= 35;
|
|
2705
|
+
if (layers.l2?.status === 'degraded') score -= 15;
|
|
2706
|
+
if (layers.l2?.status === 'inactive') score -= 25;
|
|
2707
|
+
if (layers.l3?.status === 'degraded') score -= 15;
|
|
2708
|
+
if (layers.l3?.status === 'inactive') score -= 25;
|
|
2709
|
+
if (layers.l4?.status === 'degraded') score -= 10;
|
|
2710
|
+
if (layers.l4?.status === 'inactive') score -= 20;
|
|
2226
2711
|
|
|
2227
|
-
|
|
2712
|
+
return Math.max(0, Math.min(100, score));
|
|
2713
|
+
}
|
|
2228
2714
|
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2715
|
+
async function fetchAPI(endpoint) {
|
|
2716
|
+
try {
|
|
2717
|
+
const response = await fetch(API_BASE + endpoint, {
|
|
2718
|
+
headers: {
|
|
2719
|
+
'Authorization': 'Bearer ' + AUTH_TOKEN,
|
|
2720
|
+
},
|
|
2721
|
+
});
|
|
2234
2722
|
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
}
|
|
2723
|
+
if (response.status === 401) {
|
|
2724
|
+
redirectToLogin();
|
|
2725
|
+
return null;
|
|
2726
|
+
}
|
|
2240
2727
|
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2728
|
+
if (!response.ok) {
|
|
2729
|
+
console.error('API Error:', response.status);
|
|
2730
|
+
return null;
|
|
2731
|
+
}
|
|
2245
2732
|
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
const data = await resp.json();
|
|
2252
|
-
SESSION_ID = data.session_id;
|
|
2253
|
-
var ttl = data.expires_in_seconds || 300;
|
|
2254
|
-
// Update cookie with new session
|
|
2255
|
-
setCookie(SESSION_ID, ttl);
|
|
2256
|
-
// Schedule renewal at 80% of TTL
|
|
2257
|
-
if (sessionRenewalTimer) clearTimeout(sessionRenewalTimer);
|
|
2258
|
-
sessionRenewalTimer = setTimeout(function() {
|
|
2259
|
-
exchangeSession().then(function() { reconnectSSE(); });
|
|
2260
|
-
}, ttl * 800);
|
|
2261
|
-
} else if (resp.status === 401) {
|
|
2262
|
-
// Token invalid or expired \u2014 show non-destructive re-login overlay
|
|
2263
|
-
showSessionExpired();
|
|
2264
|
-
}
|
|
2265
|
-
} catch (e) {
|
|
2266
|
-
// Network error \u2014 retry in 30s
|
|
2267
|
-
if (sessionRenewalTimer) clearTimeout(sessionRenewalTimer);
|
|
2268
|
-
sessionRenewalTimer = setTimeout(function() {
|
|
2269
|
-
exchangeSession().then(function() { reconnectSSE(); });
|
|
2270
|
-
}, 30000);
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
|
-
function showSessionExpired() {
|
|
2275
|
-
// Clear stored token
|
|
2276
|
-
try { sessionStorage.removeItem('sanctuary_token'); } catch(_) {}
|
|
2277
|
-
// Redirect to login page
|
|
2278
|
-
document.cookie = 'sanctuary_session=; path=/; max-age=0';
|
|
2279
|
-
window.location.reload();
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
// \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
|
|
2283
|
-
|
|
2284
|
-
function esc(s) {
|
|
2285
|
-
const d = document.createElement('div');
|
|
2286
|
-
d.textContent = String(s || '');
|
|
2287
|
-
return d.innerHTML;
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
function closePendingOverlay() {
|
|
2291
|
-
document.getElementById('pendingOverlay').classList.remove('active');
|
|
2292
|
-
}
|
|
2293
|
-
|
|
2294
|
-
function toggleThreatPanel() {
|
|
2295
|
-
document.getElementById('threatPanel').classList.toggle('collapsed');
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
|
-
function updateUptime() {
|
|
2299
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
2300
|
-
const hours = Math.floor(elapsed / 3600);
|
|
2301
|
-
const mins = Math.floor((elapsed % 3600) / 60);
|
|
2302
|
-
const secs = elapsed % 60;
|
|
2303
|
-
let uptimeStr = '';
|
|
2304
|
-
if (hours > 0) uptimeStr += hours + 'h ';
|
|
2305
|
-
if (mins > 0) uptimeStr += mins + 'm ';
|
|
2306
|
-
uptimeStr += secs + 's';
|
|
2307
|
-
document.getElementById('uptimeText').textContent = uptimeStr;
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
|
-
// \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
|
|
2311
|
-
|
|
2312
|
-
function updateSovereigntyScore(score) {
|
|
2313
|
-
sovereigntyScore = Math.min(100, Math.max(0, score || 85));
|
|
2314
|
-
const badge = document.getElementById('sovereigntyScore');
|
|
2315
|
-
badge.textContent = sovereigntyScore;
|
|
2316
|
-
badge.className = 'sovereignty-score';
|
|
2317
|
-
if (sovereigntyScore >= 80) {
|
|
2318
|
-
badge.classList.add('high');
|
|
2319
|
-
} else if (sovereigntyScore >= 50) {
|
|
2320
|
-
badge.classList.add('medium');
|
|
2321
|
-
} else {
|
|
2322
|
-
badge.classList.add('low');
|
|
2733
|
+
return await response.json();
|
|
2734
|
+
} catch (err) {
|
|
2735
|
+
console.error('Fetch error:', err);
|
|
2736
|
+
return null;
|
|
2737
|
+
}
|
|
2323
2738
|
}
|
|
2324
|
-
}
|
|
2325
2739
|
|
|
2326
|
-
|
|
2740
|
+
function redirectToLogin() {
|
|
2741
|
+
sessionStorage.removeItem('authToken');
|
|
2742
|
+
window.location.href = '/';
|
|
2743
|
+
}
|
|
2327
2744
|
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
tool,
|
|
2333
|
-
outcome,
|
|
2334
|
-
detail,
|
|
2335
|
-
hasInjection,
|
|
2336
|
-
isContextGated
|
|
2337
|
-
} = data;
|
|
2338
|
-
|
|
2339
|
-
const item = {
|
|
2340
|
-
id: 'activity-' + activityCount++,
|
|
2341
|
-
timestamp: timestamp || new Date().toISOString(),
|
|
2342
|
-
tier: tier || 1,
|
|
2343
|
-
tool: tool || 'unknown_tool',
|
|
2344
|
-
outcome: outcome || 'executed',
|
|
2345
|
-
detail: detail || '',
|
|
2346
|
-
hasInjection: !!hasInjection,
|
|
2347
|
-
isContextGated: !!isContextGated
|
|
2348
|
-
};
|
|
2745
|
+
// API Updates
|
|
2746
|
+
async function updateSovereignty() {
|
|
2747
|
+
const data = await fetchAPI('/api/sovereignty');
|
|
2748
|
+
if (!data) return;
|
|
2349
2749
|
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2750
|
+
apiState.sovereignty = data;
|
|
2751
|
+
|
|
2752
|
+
const score = calculateSovereigntyScore(data.shr);
|
|
2753
|
+
const badge = document.getElementById('sovereignty-badge');
|
|
2754
|
+
const scoreEl = document.getElementById('sovereignty-score');
|
|
2755
|
+
|
|
2756
|
+
scoreEl.textContent = score;
|
|
2757
|
+
|
|
2758
|
+
badge.classList.remove('degraded', 'inactive');
|
|
2759
|
+
if (score < 70) badge.classList.add('degraded');
|
|
2760
|
+
if (score < 40) badge.classList.add('inactive');
|
|
2761
|
+
|
|
2762
|
+
updateLayerCards(data.shr);
|
|
2353
2763
|
}
|
|
2354
2764
|
|
|
2355
|
-
|
|
2356
|
-
|
|
2765
|
+
function updateLayerCards(shr) {
|
|
2766
|
+
if (!shr || !shr.layers) return;
|
|
2357
2767
|
|
|
2358
|
-
|
|
2359
|
-
const list = document.getElementById('activityList');
|
|
2768
|
+
const layers = shr.layers;
|
|
2360
2769
|
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2770
|
+
updateLayerCard('l1', layers.l1, layers.l1?.encryption || 'AES-256-GCM');
|
|
2771
|
+
updateLayerCard('l2', layers.l2, layers.l2?.isolation_type || 'Process-level');
|
|
2772
|
+
updateLayerCard('l3', layers.l3, layers.l3?.proof_system || 'Schnorr-Pedersen');
|
|
2773
|
+
updateLayerCard('l4', layers.l4, layers.l4?.reputation_mode || 'Weighted');
|
|
2364
2774
|
}
|
|
2365
2775
|
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
const
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
const tierClass = 't' + item.tier;
|
|
2376
|
-
const outcomeClass = item.outcome === 'denied' ? 'outcome denied' : 'outcome';
|
|
2377
|
-
|
|
2378
|
-
let icon = '\u25CF';
|
|
2379
|
-
if (item.isContextGated) icon = '\u{1F3AF}';
|
|
2380
|
-
else if (item.hasInjection) icon = '\u26A0';
|
|
2381
|
-
else if (item.outcome === 'denied') icon = '\u2717';
|
|
2382
|
-
else icon = '\u2713';
|
|
2383
|
-
|
|
2384
|
-
tr.innerHTML =
|
|
2385
|
-
'<div class="activity-item-icon">' + esc(icon) + '</div>' +
|
|
2386
|
-
'<div class="activity-item-content">' +
|
|
2387
|
-
'<div class="activity-time">' + esc(timeStr) + '</div>' +
|
|
2388
|
-
'<div class="activity-main">' +
|
|
2389
|
-
'<span class="activity-tier ' + tierClass + '">T' + item.tier + '</span>' +
|
|
2390
|
-
'<span class="activity-tool">' + esc(item.tool) + '</span>' +
|
|
2391
|
-
'<span class="activity-outcome ' + (outcomeClass === 'outcome denied' ? 'denied' : '') + '">' + (item.outcome === 'denied' ? '\u2717 denied' : '\u2713 allowed') + '</span>' +
|
|
2392
|
-
'</div>' +
|
|
2393
|
-
'<div class="activity-detail">' + esc(item.detail) + '</div>' +
|
|
2394
|
-
'</div>' +
|
|
2395
|
-
'';
|
|
2396
|
-
|
|
2397
|
-
tr.addEventListener('click', () => {
|
|
2398
|
-
tr.classList.toggle('expanded');
|
|
2399
|
-
});
|
|
2776
|
+
function updateLayerCard(layer, layerData, detail) {
|
|
2777
|
+
if (!layerData) return;
|
|
2778
|
+
|
|
2779
|
+
const card = document.querySelector(\`[data-layer="\${layer}"]\`);
|
|
2780
|
+
if (!card) return;
|
|
2781
|
+
|
|
2782
|
+
const status = layerData.status || 'inactive';
|
|
2783
|
+
card.classList.remove('degraded', 'inactive');
|
|
2400
2784
|
|
|
2401
|
-
|
|
2785
|
+
if (status === 'degraded') {
|
|
2786
|
+
card.classList.add('degraded');
|
|
2787
|
+
} else if (status === 'inactive') {
|
|
2788
|
+
card.classList.add('inactive');
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
document.getElementById(\`\${layer}-status\`).textContent = status.toUpperCase();
|
|
2792
|
+
document.getElementById(\`\${layer}-detail\`).textContent = detail;
|
|
2402
2793
|
}
|
|
2403
|
-
}
|
|
2404
2794
|
|
|
2405
|
-
|
|
2795
|
+
async function updateIdentity() {
|
|
2796
|
+
const data = await fetchAPI('/api/identity');
|
|
2797
|
+
if (!data) return;
|
|
2406
2798
|
|
|
2407
|
-
|
|
2408
|
-
const {
|
|
2409
|
-
request_id,
|
|
2410
|
-
operation,
|
|
2411
|
-
tier,
|
|
2412
|
-
reason,
|
|
2413
|
-
context,
|
|
2414
|
-
timestamp
|
|
2415
|
-
} = data;
|
|
2416
|
-
|
|
2417
|
-
const pending = {
|
|
2418
|
-
id: request_id,
|
|
2419
|
-
operation: operation || 'unknown',
|
|
2420
|
-
tier: tier || 1,
|
|
2421
|
-
reason: reason || '',
|
|
2422
|
-
context: context || {},
|
|
2423
|
-
timestamp: timestamp || new Date().toISOString(),
|
|
2424
|
-
remaining: TIMEOUT_SECONDS
|
|
2425
|
-
};
|
|
2799
|
+
apiState.identity = data;
|
|
2426
2800
|
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2801
|
+
const primary = data.primary || {};
|
|
2802
|
+
document.getElementById('identity-label').textContent = primary.label || '\u2014';
|
|
2803
|
+
document.getElementById('identity-did').textContent = truncate(primary.did, 24);
|
|
2804
|
+
document.getElementById('identity-did').title = primary.did || '';
|
|
2805
|
+
document.getElementById('identity-pubkey').textContent = truncate(primary.publicKey, 24);
|
|
2806
|
+
document.getElementById('identity-pubkey').title = primary.publicKey || '';
|
|
2807
|
+
document.getElementById('identity-created').textContent = formatTime(primary.createdAt);
|
|
2808
|
+
document.getElementById('identity-count').textContent = data.identities?.length || '\u2014';
|
|
2809
|
+
}
|
|
2430
2810
|
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
}
|
|
2811
|
+
async function updateHandshakes() {
|
|
2812
|
+
const data = await fetchAPI('/api/handshakes');
|
|
2813
|
+
if (!data) return;
|
|
2435
2814
|
|
|
2436
|
-
|
|
2437
|
-
const count = pendingRequests.size;
|
|
2438
|
-
const badge = document.getElementById('pendingBadge');
|
|
2815
|
+
apiState.handshakes = data.handshakes || [];
|
|
2439
2816
|
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2817
|
+
document.getElementById('handshake-count').textContent = data.handshakes?.length || '0';
|
|
2818
|
+
|
|
2819
|
+
if (data.handshakes && data.handshakes.length > 0) {
|
|
2820
|
+
const latest = data.handshakes[0];
|
|
2821
|
+
document.getElementById('handshake-latest').textContent = truncate(latest.counterpartyId, 20);
|
|
2822
|
+
document.getElementById('handshake-latest').title = latest.counterpartyId || '';
|
|
2823
|
+
document.getElementById('handshake-tier').textContent = (latest.trustTier || 'Unverified').toUpperCase();
|
|
2824
|
+
document.getElementById('handshake-time').textContent = formatTime(latest.completedAt);
|
|
2825
|
+
} else {
|
|
2826
|
+
document.getElementById('handshake-latest').textContent = '\u2014';
|
|
2827
|
+
document.getElementById('handshake-tier').textContent = 'Unverified';
|
|
2828
|
+
document.getElementById('handshake-time').textContent = '\u2014';
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
updateHandshakeTable(data.handshakes || []);
|
|
2447
2832
|
}
|
|
2448
2833
|
|
|
2449
|
-
|
|
2450
|
-
|
|
2834
|
+
function updateHandshakeTable(handshakes) {
|
|
2835
|
+
const table = document.getElementById('handshake-table');
|
|
2836
|
+
|
|
2837
|
+
if (!handshakes || handshakes.length === 0) {
|
|
2838
|
+
table.innerHTML = '<div class="table-empty">No handshakes completed yet</div>';
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
table.innerHTML = handshakes
|
|
2843
|
+
.map(
|
|
2844
|
+
(hs) => \`
|
|
2845
|
+
<div class="table-row">
|
|
2846
|
+
<div class="table-cell strong">\${esc(truncate(hs.counterpartyId, 24))}</div>
|
|
2847
|
+
<div class="table-cell">\${esc(hs.trustTier || 'Unverified')}</div>
|
|
2848
|
+
<div class="table-cell">\${esc(hs.sovereigntyLevel || '\u2014')}</div>
|
|
2849
|
+
<div class="table-cell">\${hs.verified ? 'Yes' : 'No'}</div>
|
|
2850
|
+
<div class="table-cell">\${formatTime(hs.completedAt)}</div>
|
|
2851
|
+
<div class="table-cell">\${formatTime(hs.expiresAt)}</div>
|
|
2852
|
+
</div>
|
|
2853
|
+
\`
|
|
2854
|
+
)
|
|
2855
|
+
.join('');
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
async function updateSHR() {
|
|
2859
|
+
const data = await fetchAPI('/api/shr');
|
|
2860
|
+
if (!data) return;
|
|
2451
2861
|
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2862
|
+
apiState.shr = data;
|
|
2863
|
+
renderSHRViewer(data);
|
|
2864
|
+
}
|
|
2455
2865
|
|
|
2456
|
-
|
|
2457
|
-
const
|
|
2458
|
-
item.className = 'pending-item';
|
|
2866
|
+
function renderSHRViewer(shr) {
|
|
2867
|
+
const viewer = document.getElementById('shr-viewer');
|
|
2459
2868
|
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2869
|
+
if (!shr) {
|
|
2870
|
+
viewer.innerHTML = '<div class="empty-state">No SHR available</div>';
|
|
2871
|
+
return;
|
|
2872
|
+
}
|
|
2464
2873
|
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2874
|
+
let html = '';
|
|
2875
|
+
|
|
2876
|
+
// Implementation
|
|
2877
|
+
html += \`
|
|
2878
|
+
<div class="shr-section">
|
|
2879
|
+
<div class="shr-section-header">
|
|
2880
|
+
<div class="shr-toggle">\u25BC</div>
|
|
2881
|
+
<div>Implementation</div>
|
|
2882
|
+
</div>
|
|
2883
|
+
<div class="shr-section-content">
|
|
2884
|
+
<div class="shr-item">
|
|
2885
|
+
<div class="shr-key">sanctuary_version:</div>
|
|
2886
|
+
<div class="shr-value">\${esc(shr.implementation?.sanctuary_version || '\u2014')}</div>
|
|
2887
|
+
</div>
|
|
2888
|
+
<div class="shr-item">
|
|
2889
|
+
<div class="shr-key">node_version:</div>
|
|
2890
|
+
<div class="shr-value">\${esc(shr.implementation?.node_version || '\u2014')}</div>
|
|
2891
|
+
</div>
|
|
2892
|
+
<div class="shr-item">
|
|
2893
|
+
<div class="shr-key">generated_by:</div>
|
|
2894
|
+
<div class="shr-value">\${esc(shr.implementation?.generated_by || '\u2014')}</div>
|
|
2895
|
+
</div>
|
|
2896
|
+
</div>
|
|
2897
|
+
</div>
|
|
2898
|
+
\`;
|
|
2899
|
+
|
|
2900
|
+
// Metadata
|
|
2901
|
+
html += \`
|
|
2902
|
+
<div class="shr-section">
|
|
2903
|
+
<div class="shr-section-header">
|
|
2904
|
+
<div class="shr-toggle">\u25BC</div>
|
|
2905
|
+
<div>Metadata</div>
|
|
2906
|
+
</div>
|
|
2907
|
+
<div class="shr-section-content">
|
|
2908
|
+
<div class="shr-item">
|
|
2909
|
+
<div class="shr-key">instance_id:</div>
|
|
2910
|
+
<div class="shr-value">\${esc(truncate(shr.instance_id, 20))}</div>
|
|
2911
|
+
</div>
|
|
2912
|
+
<div class="shr-item">
|
|
2913
|
+
<div class="shr-key">generated_at:</div>
|
|
2914
|
+
<div class="shr-value">\${formatTime(shr.generated_at)}</div>
|
|
2915
|
+
</div>
|
|
2916
|
+
<div class="shr-item">
|
|
2917
|
+
<div class="shr-key">expires_at:</div>
|
|
2918
|
+
<div class="shr-value">\${formatTime(shr.expires_at)}</div>
|
|
2919
|
+
</div>
|
|
2920
|
+
</div>
|
|
2921
|
+
</div>
|
|
2922
|
+
\`;
|
|
2923
|
+
|
|
2924
|
+
// Layers
|
|
2925
|
+
if (shr.layers) {
|
|
2926
|
+
html += \`<div class="shr-section">
|
|
2927
|
+
<div class="shr-section-header">
|
|
2928
|
+
<div class="shr-toggle">\u25BC</div>
|
|
2929
|
+
<div>Layers</div>
|
|
2930
|
+
</div>
|
|
2931
|
+
<div class="shr-section-content">
|
|
2932
|
+
\`;
|
|
2933
|
+
|
|
2934
|
+
for (const [key, layer] of Object.entries(shr.layers)) {
|
|
2935
|
+
html += \`
|
|
2936
|
+
<div style="margin-bottom: 12px;">
|
|
2937
|
+
<div style="color: var(--blue); font-weight: 600; margin-bottom: 4px;">\${esc(key)}</div>
|
|
2938
|
+
<div style="padding-left: 12px;">
|
|
2939
|
+
\`;
|
|
2940
|
+
|
|
2941
|
+
for (const [lkey, lvalue] of Object.entries(layer || {})) {
|
|
2942
|
+
const displayValue =
|
|
2943
|
+
typeof lvalue === 'boolean'
|
|
2944
|
+
? lvalue
|
|
2945
|
+
? 'true'
|
|
2946
|
+
: 'false'
|
|
2947
|
+
: esc(String(lvalue));
|
|
2948
|
+
html += \`
|
|
2949
|
+
<div class="shr-item">
|
|
2950
|
+
<div class="shr-key">\${esc(lkey)}:</div>
|
|
2951
|
+
<div class="shr-value">\${displayValue}</div>
|
|
2952
|
+
</div>
|
|
2953
|
+
\`;
|
|
2954
|
+
}
|
|
2482
2955
|
|
|
2483
|
-
|
|
2956
|
+
html += \`
|
|
2957
|
+
</div>
|
|
2958
|
+
</div>
|
|
2959
|
+
\`;
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
html += \`
|
|
2963
|
+
</div>
|
|
2964
|
+
</div>
|
|
2965
|
+
\`;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
// Capabilities
|
|
2969
|
+
if (shr.capabilities) {
|
|
2970
|
+
html += \`
|
|
2971
|
+
<div class="shr-section">
|
|
2972
|
+
<div class="shr-section-header">
|
|
2973
|
+
<div class="shr-toggle">\u25BC</div>
|
|
2974
|
+
<div>Capabilities</div>
|
|
2975
|
+
</div>
|
|
2976
|
+
<div class="shr-section-content">
|
|
2977
|
+
\`;
|
|
2978
|
+
|
|
2979
|
+
for (const [key, value] of Object.entries(shr.capabilities)) {
|
|
2980
|
+
const displayValue = value ? 'true' : 'false';
|
|
2981
|
+
html += \`
|
|
2982
|
+
<div class="shr-item">
|
|
2983
|
+
<div class="shr-key">\${esc(key)}:</div>
|
|
2984
|
+
<div class="shr-value">\${displayValue}</div>
|
|
2985
|
+
</div>
|
|
2986
|
+
\`;
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
html += \`
|
|
2990
|
+
</div>
|
|
2991
|
+
</div>
|
|
2992
|
+
\`;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
// Signature
|
|
2996
|
+
html += \`
|
|
2997
|
+
<div class="shr-section">
|
|
2998
|
+
<div class="shr-section-header">
|
|
2999
|
+
<div class="shr-toggle">\u25BC</div>
|
|
3000
|
+
<div>Signature</div>
|
|
3001
|
+
</div>
|
|
3002
|
+
<div class="shr-section-content">
|
|
3003
|
+
<div class="shr-item">
|
|
3004
|
+
<div class="shr-key">signed_by:</div>
|
|
3005
|
+
<div class="shr-value">\${esc(truncate(shr.signed_by, 20))}</div>
|
|
3006
|
+
</div>
|
|
3007
|
+
<div class="shr-item">
|
|
3008
|
+
<div class="shr-key">signature:</div>
|
|
3009
|
+
<div class="shr-value">\${esc(truncate(shr.signature, 32))}</div>
|
|
3010
|
+
</div>
|
|
3011
|
+
</div>
|
|
3012
|
+
</div>
|
|
3013
|
+
\`;
|
|
3014
|
+
|
|
3015
|
+
viewer.innerHTML = html;
|
|
3016
|
+
|
|
3017
|
+
// Add collapse functionality
|
|
3018
|
+
document.querySelectorAll('.shr-section-header').forEach((header) => {
|
|
3019
|
+
header.addEventListener('click', () => {
|
|
3020
|
+
header.closest('.shr-section').classList.toggle('collapsed');
|
|
3021
|
+
});
|
|
3022
|
+
});
|
|
2484
3023
|
}
|
|
2485
|
-
}
|
|
2486
3024
|
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
}).catch(() => {});
|
|
2491
|
-
};
|
|
3025
|
+
async function updateStatus() {
|
|
3026
|
+
const data = await fetchAPI('/api/status');
|
|
3027
|
+
if (!data) return;
|
|
2492
3028
|
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
3029
|
+
apiState.status = data;
|
|
3030
|
+
|
|
3031
|
+
document.getElementById('protections-count').textContent = data.protectionsCount || '0';
|
|
3032
|
+
document.getElementById('uptime-value').textContent = formatUptime(data.uptime);
|
|
3033
|
+
|
|
3034
|
+
const connectionStatus = document.getElementById('connection-status');
|
|
3035
|
+
connectionStatus.classList.toggle('disconnected', !data.connected);
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
function formatUptime(seconds) {
|
|
3039
|
+
if (!seconds) return '\u2014';
|
|
3040
|
+
const hours = Math.floor(seconds / 3600);
|
|
3041
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
3042
|
+
if (hours > 0) return \`\${hours}h \${minutes}m\`;
|
|
3043
|
+
return \`\${minutes}m\`;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
// SSE Setup
|
|
3047
|
+
function setupSSE() {
|
|
3048
|
+
const eventSource = new EventSource(API_BASE + '/api/events', {
|
|
3049
|
+
headers: {
|
|
3050
|
+
'Authorization': 'Bearer ' + AUTH_TOKEN,
|
|
3051
|
+
},
|
|
3052
|
+
});
|
|
3053
|
+
|
|
3054
|
+
eventSource.addEventListener('init', (e) => {
|
|
3055
|
+
console.log('Connected to SSE');
|
|
3056
|
+
});
|
|
3057
|
+
|
|
3058
|
+
eventSource.addEventListener('sovereignty-update', () => {
|
|
3059
|
+
updateSovereignty();
|
|
3060
|
+
});
|
|
3061
|
+
|
|
3062
|
+
eventSource.addEventListener('handshake-update', () => {
|
|
3063
|
+
updateHandshakes();
|
|
3064
|
+
});
|
|
3065
|
+
|
|
3066
|
+
eventSource.addEventListener('tool-call', (e) => {
|
|
3067
|
+
const data = JSON.parse(e.data);
|
|
3068
|
+
addActivityItem({
|
|
3069
|
+
type: 'tool-call',
|
|
3070
|
+
title: 'Tool Call',
|
|
3071
|
+
content: data.toolName,
|
|
3072
|
+
timestamp: new Date().toISOString(),
|
|
3073
|
+
});
|
|
3074
|
+
});
|
|
3075
|
+
|
|
3076
|
+
eventSource.addEventListener('context-gate-decision', (e) => {
|
|
3077
|
+
const data = JSON.parse(e.data);
|
|
3078
|
+
addActivityItem({
|
|
3079
|
+
type: 'context-gate',
|
|
3080
|
+
title: 'Context Gate',
|
|
3081
|
+
content: data.decision,
|
|
3082
|
+
timestamp: new Date().toISOString(),
|
|
3083
|
+
});
|
|
3084
|
+
});
|
|
3085
|
+
|
|
3086
|
+
eventSource.addEventListener('injection-alert', (e) => {
|
|
3087
|
+
const data = JSON.parse(e.data);
|
|
3088
|
+
addActivityItem({
|
|
3089
|
+
type: 'injection',
|
|
3090
|
+
title: 'Injection Alert',
|
|
3091
|
+
content: data.pattern,
|
|
3092
|
+
timestamp: new Date().toISOString(),
|
|
3093
|
+
});
|
|
3094
|
+
addThreatAlert(data);
|
|
3095
|
+
});
|
|
3096
|
+
|
|
3097
|
+
eventSource.addEventListener('pending-request', (e) => {
|
|
3098
|
+
const data = JSON.parse(e.data);
|
|
3099
|
+
addPendingRequest(data);
|
|
3100
|
+
});
|
|
2498
3101
|
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
timestamp,
|
|
2504
|
-
severity,
|
|
2505
|
-
type,
|
|
2506
|
-
details
|
|
2507
|
-
} = data;
|
|
2508
|
-
|
|
2509
|
-
const threat = {
|
|
2510
|
-
id: 'threat-' + threatCount++,
|
|
2511
|
-
timestamp: timestamp || new Date().toISOString(),
|
|
2512
|
-
severity: severity || 'medium',
|
|
2513
|
-
type: type || 'unknown',
|
|
2514
|
-
details: details || ''
|
|
2515
|
-
};
|
|
3102
|
+
eventSource.addEventListener('request-resolved', (e) => {
|
|
3103
|
+
const data = JSON.parse(e.data);
|
|
3104
|
+
removePendingRequest(data.requestId);
|
|
3105
|
+
});
|
|
2516
3106
|
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
3107
|
+
eventSource.onerror = () => {
|
|
3108
|
+
console.error('SSE error');
|
|
3109
|
+
setTimeout(setupSSE, 5000);
|
|
3110
|
+
};
|
|
2520
3111
|
}
|
|
2521
3112
|
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
3113
|
+
// Activity Feed
|
|
3114
|
+
function addActivityItem(item) {
|
|
3115
|
+
activityLog.unshift(item);
|
|
3116
|
+
if (activityLog.length > maxActivityItems) {
|
|
3117
|
+
activityLog.pop();
|
|
3118
|
+
}
|
|
2525
3119
|
|
|
2526
|
-
|
|
2527
|
-
|
|
3120
|
+
const feed = document.getElementById('activity-feed');
|
|
3121
|
+
const html = \`
|
|
3122
|
+
<div class="activity-item \${item.type}">
|
|
3123
|
+
<div class="activity-type">\${esc(item.title)}</div>
|
|
3124
|
+
<div class="activity-content">\${esc(item.content)}</div>
|
|
3125
|
+
<div class="activity-time">\${formatTime(item.timestamp)}</div>
|
|
3126
|
+
</div>
|
|
3127
|
+
\`;
|
|
3128
|
+
|
|
3129
|
+
if (feed.querySelector('.empty-state')) {
|
|
3130
|
+
feed.innerHTML = '';
|
|
3131
|
+
}
|
|
2528
3132
|
|
|
2529
|
-
|
|
2530
|
-
const content = document.getElementById('threatContent');
|
|
2531
|
-
const badge = document.getElementById('threatCount');
|
|
3133
|
+
feed.insertAdjacentHTML('afterbegin', html);
|
|
2532
3134
|
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
return;
|
|
3135
|
+
if (feed.children.length > maxActivityItems) {
|
|
3136
|
+
feed.lastChild.remove();
|
|
3137
|
+
}
|
|
2537
3138
|
}
|
|
2538
3139
|
|
|
2539
|
-
|
|
2540
|
-
|
|
3140
|
+
// Pending Requests
|
|
3141
|
+
function addPendingRequest(request) {
|
|
3142
|
+
pendingRequests.set(request.requestId, {
|
|
3143
|
+
id: request.requestId,
|
|
3144
|
+
title: request.title,
|
|
3145
|
+
details: request.details,
|
|
3146
|
+
expiresAt: new Date(Date.now() + TIMEOUT_SECONDS * 1000),
|
|
3147
|
+
});
|
|
2541
3148
|
|
|
2542
|
-
|
|
2543
|
-
const div = document.createElement('div');
|
|
2544
|
-
div.className = 'threat-item';
|
|
2545
|
-
const time = new Date(threat.timestamp).toLocaleTimeString();
|
|
2546
|
-
div.innerHTML =
|
|
2547
|
-
'<div style="margin-bottom: 3px;">' +
|
|
2548
|
-
'<span class="threat-item-type">' + esc(threat.type) + '</span>' +
|
|
2549
|
-
'<span style="font-size: 10px; color: var(--text-secondary); margin-left: 6px;">' + esc(time) + '</span>' +
|
|
2550
|
-
'</div>' +
|
|
2551
|
-
'<div>' + esc(threat.details) + '</div>' +
|
|
2552
|
-
'';
|
|
2553
|
-
content.appendChild(div);
|
|
3149
|
+
updatePendingDisplay();
|
|
2554
3150
|
}
|
|
2555
|
-
}
|
|
2556
3151
|
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
connect();
|
|
2562
|
-
}
|
|
3152
|
+
function removePendingRequest(requestId) {
|
|
3153
|
+
pendingRequests.delete(requestId);
|
|
3154
|
+
updatePendingDisplay();
|
|
3155
|
+
}
|
|
2563
3156
|
|
|
2564
|
-
|
|
2565
|
-
|
|
3157
|
+
function updatePendingDisplay() {
|
|
3158
|
+
const badge = document.getElementById('pending-item-badge');
|
|
3159
|
+
const count = pendingRequests.size;
|
|
2566
3160
|
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
3161
|
+
if (count > 0) {
|
|
3162
|
+
document.getElementById('pending-count').textContent = count;
|
|
3163
|
+
badge.style.display = 'flex';
|
|
3164
|
+
} else {
|
|
3165
|
+
badge.style.display = 'none';
|
|
3166
|
+
}
|
|
2570
3167
|
|
|
2571
|
-
|
|
2572
|
-
document.getElementById('
|
|
2573
|
-
};
|
|
3168
|
+
const overlay = document.getElementById('pending-overlay');
|
|
3169
|
+
const items = document.getElementById('pending-items');
|
|
2574
3170
|
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
}
|
|
2580
|
-
if (data.policy) {
|
|
2581
|
-
updatePolicy(data.policy);
|
|
2582
|
-
}
|
|
2583
|
-
if (data.pending) {
|
|
2584
|
-
data.pending.forEach(addPendingRequest);
|
|
3171
|
+
if (count === 0) {
|
|
3172
|
+
items.innerHTML = '';
|
|
3173
|
+
overlay.classList.remove('show');
|
|
3174
|
+
return;
|
|
2585
3175
|
}
|
|
2586
|
-
});
|
|
2587
3176
|
|
|
2588
|
-
|
|
2589
|
-
const
|
|
2590
|
-
|
|
2591
|
-
|
|
3177
|
+
let html = '';
|
|
3178
|
+
for (const req of pendingRequests.values()) {
|
|
3179
|
+
const remaining = Math.max(0, Math.floor((req.expiresAt - Date.now()) / 1000));
|
|
3180
|
+
html += \`
|
|
3181
|
+
<div class="pending-item">
|
|
3182
|
+
<div class="pending-title">\${esc(req.title)}</div>
|
|
3183
|
+
<div class="pending-countdown">Expires in \${remaining}s</div>
|
|
3184
|
+
<div class="pending-actions">
|
|
3185
|
+
<button class="pending-btn pending-approve" data-id="\${req.id}">Approve</button>
|
|
3186
|
+
<button class="pending-btn pending-deny" data-id="\${req.id}">Deny</button>
|
|
3187
|
+
</div>
|
|
3188
|
+
</div>
|
|
3189
|
+
\`;
|
|
3190
|
+
}
|
|
2592
3191
|
|
|
2593
|
-
|
|
2594
|
-
const data = JSON.parse(e.data);
|
|
2595
|
-
removePendingRequest(data.request_id);
|
|
2596
|
-
});
|
|
3192
|
+
items.innerHTML = html;
|
|
2597
3193
|
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
tool: data.tool || 'unknown',
|
|
2604
|
-
outcome: data.outcome || 'executed',
|
|
2605
|
-
detail: data.detail || ''
|
|
3194
|
+
document.querySelectorAll('.pending-approve').forEach((btn) => {
|
|
3195
|
+
btn.addEventListener('click', async () => {
|
|
3196
|
+
const id = btn.getAttribute('data-id');
|
|
3197
|
+
await fetchAPI(\`/api/approve/\${id}\`);
|
|
3198
|
+
});
|
|
2606
3199
|
});
|
|
2607
|
-
});
|
|
2608
3200
|
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
tool: data.tool || 'unknown',
|
|
2615
|
-
outcome: data.outcome || 'gated',
|
|
2616
|
-
detail: data.fields_filtered ? 'Filtered ' + data.fields_filtered + ' fields' : data.reason || '',
|
|
2617
|
-
isContextGated: true
|
|
3201
|
+
document.querySelectorAll('.pending-deny').forEach((btn) => {
|
|
3202
|
+
btn.addEventListener('click', async () => {
|
|
3203
|
+
const id = btn.getAttribute('data-id');
|
|
3204
|
+
await fetchAPI(\`/api/deny/\${id}\`);
|
|
3205
|
+
});
|
|
2618
3206
|
});
|
|
2619
|
-
}
|
|
3207
|
+
}
|
|
2620
3208
|
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
tier: data.tier || 2,
|
|
2626
|
-
tool: data.tool || 'unknown',
|
|
2627
|
-
outcome: data.allowed ? 'allowed' : 'denied',
|
|
2628
|
-
detail: data.signal || 'Injection detected',
|
|
2629
|
-
hasInjection: true
|
|
2630
|
-
});
|
|
2631
|
-
addThreat({
|
|
2632
|
-
timestamp: data.timestamp,
|
|
2633
|
-
severity: data.severity || 'medium',
|
|
2634
|
-
type: 'Injection Alert',
|
|
2635
|
-
details: data.signal || 'Suspicious pattern detected'
|
|
2636
|
-
});
|
|
2637
|
-
});
|
|
3209
|
+
// Threat Panel
|
|
3210
|
+
function addThreatAlert(alert) {
|
|
3211
|
+
const panel = document.querySelector('.threat-panel');
|
|
3212
|
+
const content = document.getElementById('threat-alerts');
|
|
2638
3213
|
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
});
|
|
3214
|
+
if (content.querySelector('.empty-state')) {
|
|
3215
|
+
content.innerHTML = '';
|
|
3216
|
+
}
|
|
2643
3217
|
|
|
2644
|
-
|
|
2645
|
-
const data = JSON.parse(e.data);
|
|
2646
|
-
// Audit entries don't show in activity by default, but we could add them
|
|
2647
|
-
});
|
|
3218
|
+
panel.classList.remove('collapsed');
|
|
2648
3219
|
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
3220
|
+
const html = \`
|
|
3221
|
+
<div class="threat-alert">
|
|
3222
|
+
<div class="threat-type">\${esc(alert.type || 'Injection Alert')}</div>
|
|
3223
|
+
<div class="threat-message">\${esc(alert.message || alert.pattern || '\u2014')}</div>
|
|
3224
|
+
</div>
|
|
3225
|
+
\`;
|
|
2654
3226
|
|
|
2655
|
-
|
|
2656
|
-
if (!baseline) return;
|
|
2657
|
-
// Update baseline-derived stats if needed
|
|
2658
|
-
}
|
|
3227
|
+
content.insertAdjacentHTML('afterbegin', html);
|
|
2659
3228
|
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
// Policy info updated
|
|
3229
|
+
const alerts = content.querySelectorAll('.threat-alert');
|
|
3230
|
+
if (alerts.length > 10) {
|
|
3231
|
+
alerts[alerts.length - 1].remove();
|
|
3232
|
+
}
|
|
2665
3233
|
}
|
|
2666
|
-
}
|
|
2667
3234
|
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
}
|
|
2672
|
-
if (status.active_protections !== undefined) {
|
|
2673
|
-
document.getElementById('activeProtections').textContent = status.active_protections;
|
|
2674
|
-
}
|
|
2675
|
-
// Update individual protection cards
|
|
2676
|
-
if (status.encryption !== undefined) {
|
|
2677
|
-
const el = document.getElementById('encryptionStatus');
|
|
2678
|
-
el.className = 'protection-card-status ' + (status.encryption ? 'active' : 'inactive');
|
|
2679
|
-
el.textContent = status.encryption ? '\u2713 Active' : '\u2717 Inactive';
|
|
2680
|
-
}
|
|
2681
|
-
if (status.approval_gate !== undefined) {
|
|
2682
|
-
const el = document.getElementById('approvalStatus');
|
|
2683
|
-
el.className = 'protection-card-status ' + (status.approval_gate ? 'active' : 'inactive');
|
|
2684
|
-
el.textContent = status.approval_gate ? '\u2713 Active' : '\u2717 Inactive';
|
|
2685
|
-
}
|
|
2686
|
-
if (status.context_gating !== undefined) {
|
|
2687
|
-
const el = document.getElementById('contextStatus');
|
|
2688
|
-
el.className = 'protection-card-status ' + (status.context_gating ? 'active' : 'inactive');
|
|
2689
|
-
el.textContent = status.context_gating ? '\u2713 Active' : '\u2717 Inactive';
|
|
2690
|
-
}
|
|
2691
|
-
if (status.injection_detection !== undefined) {
|
|
2692
|
-
const el = document.getElementById('injectionStatus');
|
|
2693
|
-
el.className = 'protection-card-status ' + (status.injection_detection ? 'active' : 'inactive');
|
|
2694
|
-
el.textContent = status.injection_detection ? '\u2713 Active' : '\u2717 Inactive';
|
|
2695
|
-
}
|
|
2696
|
-
if (status.baseline !== undefined) {
|
|
2697
|
-
const el = document.getElementById('baselineStatus');
|
|
2698
|
-
el.className = 'protection-card-status ' + (status.baseline ? 'active' : 'inactive');
|
|
2699
|
-
el.textContent = status.baseline ? '\u2713 Active' : '\u2717 Inactive';
|
|
2700
|
-
}
|
|
2701
|
-
if (status.audit_trail !== undefined) {
|
|
2702
|
-
const el = document.getElementById('auditStatus');
|
|
2703
|
-
el.className = 'protection-card-status ' + (status.audit_trail ? 'active' : 'inactive');
|
|
2704
|
-
el.textContent = status.audit_trail ? '\u2713 Active' : '\u2717 Inactive';
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
3235
|
+
// Threat Panel Toggle
|
|
3236
|
+
document.querySelector('.threat-header').addEventListener('click', () => {
|
|
3237
|
+
document.querySelector('.threat-panel').classList.toggle('collapsed');
|
|
3238
|
+
});
|
|
2707
3239
|
|
|
2708
|
-
|
|
3240
|
+
// SHR Copy Button
|
|
3241
|
+
document.getElementById('copy-shr-btn').addEventListener('click', async () => {
|
|
3242
|
+
if (!apiState.shr) return;
|
|
2709
3243
|
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
3244
|
+
const json = JSON.stringify(apiState.shr, null, 2);
|
|
3245
|
+
try {
|
|
3246
|
+
await navigator.clipboard.writeText(json);
|
|
3247
|
+
const btn = document.getElementById('copy-shr-btn');
|
|
3248
|
+
const original = btn.textContent;
|
|
3249
|
+
btn.textContent = 'Copied!';
|
|
3250
|
+
setTimeout(() => {
|
|
3251
|
+
btn.textContent = original;
|
|
3252
|
+
}, 2000);
|
|
3253
|
+
} catch (err) {
|
|
3254
|
+
console.error('Copy failed:', err);
|
|
3255
|
+
}
|
|
3256
|
+
});
|
|
2717
3257
|
|
|
2718
|
-
//
|
|
2719
|
-
|
|
2720
|
-
|
|
3258
|
+
// Pending Overlay Toggle
|
|
3259
|
+
document.getElementById('pending-item-badge').addEventListener('click', () => {
|
|
3260
|
+
document.getElementById('pending-overlay').classList.toggle('show');
|
|
3261
|
+
});
|
|
2721
3262
|
|
|
2722
|
-
//
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
if (el) {
|
|
2728
|
-
el.textContent = req.remaining + 's';
|
|
2729
|
-
}
|
|
3263
|
+
// Initialize
|
|
3264
|
+
async function initialize() {
|
|
3265
|
+
if (!AUTH_TOKEN) {
|
|
3266
|
+
redirectToLogin();
|
|
3267
|
+
return;
|
|
2730
3268
|
}
|
|
2731
|
-
}, 1000);
|
|
2732
3269
|
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
} catch (e) {
|
|
2742
|
-
// Ignore
|
|
2743
|
-
}
|
|
2744
|
-
})();
|
|
3270
|
+
// Initial data fetch
|
|
3271
|
+
await Promise.all([
|
|
3272
|
+
updateSovereignty(),
|
|
3273
|
+
updateIdentity(),
|
|
3274
|
+
updateHandshakes(),
|
|
3275
|
+
updateSHR(),
|
|
3276
|
+
updateStatus(),
|
|
3277
|
+
]);
|
|
2745
3278
|
|
|
2746
|
-
|
|
2747
|
-
|
|
3279
|
+
// Setup SSE for real-time updates
|
|
3280
|
+
setupSSE();
|
|
2748
3281
|
|
|
3282
|
+
// Refresh status periodically
|
|
3283
|
+
setInterval(updateStatus, 30000);
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
// Start
|
|
3287
|
+
initialize();
|
|
3288
|
+
</script>
|
|
2749
3289
|
</body>
|
|
2750
3290
|
</html>`;
|
|
2751
3291
|
}
|
|
@@ -2757,6 +3297,7 @@ var SESSION_TTL_REMOTE_MS, SESSION_TTL_LOCAL_MS, MAX_SESSIONS, RATE_LIMIT_WINDOW
|
|
|
2757
3297
|
var init_dashboard = __esm({
|
|
2758
3298
|
"src/principal-policy/dashboard.ts"() {
|
|
2759
3299
|
init_config();
|
|
3300
|
+
init_generator();
|
|
2760
3301
|
init_dashboard_html();
|
|
2761
3302
|
SESSION_TTL_REMOTE_MS = 5 * 60 * 1e3;
|
|
2762
3303
|
SESSION_TTL_LOCAL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -2773,6 +3314,10 @@ var init_dashboard = __esm({
|
|
|
2773
3314
|
policy = null;
|
|
2774
3315
|
baseline = null;
|
|
2775
3316
|
auditLog = null;
|
|
3317
|
+
identityManager = null;
|
|
3318
|
+
handshakeResults = null;
|
|
3319
|
+
shrOpts = null;
|
|
3320
|
+
_sanctuaryConfig = null;
|
|
2776
3321
|
dashboardHTML;
|
|
2777
3322
|
loginHTML;
|
|
2778
3323
|
authToken;
|
|
@@ -2806,6 +3351,10 @@ var init_dashboard = __esm({
|
|
|
2806
3351
|
this.policy = deps.policy;
|
|
2807
3352
|
this.baseline = deps.baseline;
|
|
2808
3353
|
this.auditLog = deps.auditLog;
|
|
3354
|
+
if (deps.identityManager) this.identityManager = deps.identityManager;
|
|
3355
|
+
if (deps.handshakeResults) this.handshakeResults = deps.handshakeResults;
|
|
3356
|
+
if (deps.shrOpts) this.shrOpts = deps.shrOpts;
|
|
3357
|
+
if (deps.sanctuaryConfig) this._sanctuaryConfig = deps.sanctuaryConfig;
|
|
2809
3358
|
}
|
|
2810
3359
|
/**
|
|
2811
3360
|
* Start the HTTP(S) server for the dashboard.
|
|
@@ -3138,6 +3687,14 @@ var init_dashboard = __esm({
|
|
|
3138
3687
|
this.handlePendingList(res);
|
|
3139
3688
|
} else if (method === "GET" && url.pathname === "/api/audit-log") {
|
|
3140
3689
|
this.handleAuditLog(url, res);
|
|
3690
|
+
} else if (method === "GET" && url.pathname === "/api/sovereignty") {
|
|
3691
|
+
this.handleSovereignty(res);
|
|
3692
|
+
} else if (method === "GET" && url.pathname === "/api/identity") {
|
|
3693
|
+
this.handleIdentity(res);
|
|
3694
|
+
} else if (method === "GET" && url.pathname === "/api/handshakes") {
|
|
3695
|
+
this.handleHandshakes(res);
|
|
3696
|
+
} else if (method === "GET" && url.pathname === "/api/shr") {
|
|
3697
|
+
this.handleSHR(res);
|
|
3141
3698
|
} else if (method === "POST" && url.pathname.startsWith("/api/approve/")) {
|
|
3142
3699
|
if (!this.checkRateLimit(req, res, "decisions")) return;
|
|
3143
3700
|
const id = url.pathname.slice("/api/approve/".length);
|
|
@@ -3327,6 +3884,107 @@ data: ${JSON.stringify(initData)}
|
|
|
3327
3884
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3328
3885
|
res.end(JSON.stringify({ success: true, decision }));
|
|
3329
3886
|
}
|
|
3887
|
+
// ── Sovereignty Data Routes ─────────────────────────────────────────
|
|
3888
|
+
handleSovereignty(res) {
|
|
3889
|
+
if (!this.shrOpts) {
|
|
3890
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3891
|
+
res.end(JSON.stringify({ error: "SHR generator not available" }));
|
|
3892
|
+
return;
|
|
3893
|
+
}
|
|
3894
|
+
const shr = generateSHR(void 0, this.shrOpts);
|
|
3895
|
+
if (typeof shr === "string") {
|
|
3896
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3897
|
+
res.end(JSON.stringify({ error: shr }));
|
|
3898
|
+
return;
|
|
3899
|
+
}
|
|
3900
|
+
const layers = shr.body.layers;
|
|
3901
|
+
let score = 0;
|
|
3902
|
+
for (const layer of [layers.l1, layers.l2, layers.l3, layers.l4]) {
|
|
3903
|
+
if (layer.status === "active") score += 25;
|
|
3904
|
+
else if (layer.status === "degraded") score += 15;
|
|
3905
|
+
}
|
|
3906
|
+
const overallLevel = score === 100 ? "full" : score >= 65 ? "degraded" : score >= 25 ? "minimal" : "unverified";
|
|
3907
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3908
|
+
res.end(JSON.stringify({
|
|
3909
|
+
score,
|
|
3910
|
+
overall_level: overallLevel,
|
|
3911
|
+
layers: {
|
|
3912
|
+
l1: { status: layers.l1.status, detail: layers.l1.encryption, key_custody: layers.l1.key_custody },
|
|
3913
|
+
l2: { status: layers.l2.status, detail: layers.l2.isolation_type, attestation: layers.l2.attestation_available },
|
|
3914
|
+
l3: { status: layers.l3.status, detail: layers.l3.proof_system, selective_disclosure: layers.l3.selective_disclosure },
|
|
3915
|
+
l4: { status: layers.l4.status, detail: layers.l4.attestation_format, reputation_portable: layers.l4.reputation_portable }
|
|
3916
|
+
},
|
|
3917
|
+
degradations: shr.body.degradations,
|
|
3918
|
+
capabilities: shr.body.capabilities,
|
|
3919
|
+
config_loaded: this._sanctuaryConfig != null
|
|
3920
|
+
}));
|
|
3921
|
+
}
|
|
3922
|
+
handleIdentity(res) {
|
|
3923
|
+
if (!this.identityManager) {
|
|
3924
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3925
|
+
res.end(JSON.stringify({ identities: [], count: 0 }));
|
|
3926
|
+
return;
|
|
3927
|
+
}
|
|
3928
|
+
const identities = this.identityManager.list().map((id) => ({
|
|
3929
|
+
identity_id: id.identity_id,
|
|
3930
|
+
label: id.label,
|
|
3931
|
+
public_key: id.public_key,
|
|
3932
|
+
did: id.did,
|
|
3933
|
+
created_at: id.created_at,
|
|
3934
|
+
key_type: id.key_type,
|
|
3935
|
+
key_protection: id.key_protection,
|
|
3936
|
+
rotation_count: id.rotation_history?.length ?? 0
|
|
3937
|
+
}));
|
|
3938
|
+
const primary = this.identityManager.getDefault();
|
|
3939
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3940
|
+
res.end(JSON.stringify({
|
|
3941
|
+
identities,
|
|
3942
|
+
count: identities.length,
|
|
3943
|
+
primary_id: primary?.identity_id ?? null
|
|
3944
|
+
}));
|
|
3945
|
+
}
|
|
3946
|
+
handleHandshakes(res) {
|
|
3947
|
+
if (!this.handshakeResults) {
|
|
3948
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3949
|
+
res.end(JSON.stringify({ handshakes: [], count: 0 }));
|
|
3950
|
+
return;
|
|
3951
|
+
}
|
|
3952
|
+
const handshakes = Array.from(this.handshakeResults.values()).map((h) => ({
|
|
3953
|
+
counterparty_id: h.counterparty_id,
|
|
3954
|
+
verified: h.verified,
|
|
3955
|
+
sovereignty_level: h.sovereignty_level,
|
|
3956
|
+
trust_tier: h.trust_tier,
|
|
3957
|
+
completed_at: h.completed_at,
|
|
3958
|
+
expires_at: h.expires_at,
|
|
3959
|
+
errors: h.errors
|
|
3960
|
+
}));
|
|
3961
|
+
handshakes.sort((a, b) => new Date(b.completed_at).getTime() - new Date(a.completed_at).getTime());
|
|
3962
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3963
|
+
res.end(JSON.stringify({
|
|
3964
|
+
handshakes,
|
|
3965
|
+
count: handshakes.length,
|
|
3966
|
+
tier_distribution: {
|
|
3967
|
+
verified_sovereign: handshakes.filter((h) => h.trust_tier === "verified-sovereign").length,
|
|
3968
|
+
verified_degraded: handshakes.filter((h) => h.trust_tier === "verified-degraded").length,
|
|
3969
|
+
unverified: handshakes.filter((h) => h.trust_tier === "unverified").length
|
|
3970
|
+
}
|
|
3971
|
+
}));
|
|
3972
|
+
}
|
|
3973
|
+
handleSHR(res) {
|
|
3974
|
+
if (!this.shrOpts) {
|
|
3975
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3976
|
+
res.end(JSON.stringify({ error: "SHR generator not available" }));
|
|
3977
|
+
return;
|
|
3978
|
+
}
|
|
3979
|
+
const shr = generateSHR(void 0, this.shrOpts);
|
|
3980
|
+
if (typeof shr === "string") {
|
|
3981
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3982
|
+
res.end(JSON.stringify({ error: shr }));
|
|
3983
|
+
return;
|
|
3984
|
+
}
|
|
3985
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3986
|
+
res.end(JSON.stringify(shr));
|
|
3987
|
+
}
|
|
3330
3988
|
// ── SSE Broadcasting ────────────────────────────────────────────────
|
|
3331
3989
|
broadcastSSE(event, data) {
|
|
3332
3990
|
const message = `event: ${event}
|
|
@@ -3517,162 +4175,58 @@ async function startStandaloneDashboard(options = {}) {
|
|
|
3517
4175
|
const auditLog = new AuditLog(storage, masterKey);
|
|
3518
4176
|
const policy = await loadPrincipalPolicy(config.storage_path);
|
|
3519
4177
|
const baseline = new BaselineTracker(storage, masterKey);
|
|
3520
|
-
await baseline.load();
|
|
3521
|
-
const dashboardPort = options.port ?? config.dashboard.port;
|
|
3522
|
-
const dashboardHost = options.host ?? config.dashboard.host;
|
|
3523
|
-
let authToken = config.dashboard.auth_token;
|
|
3524
|
-
if (authToken === "auto") {
|
|
3525
|
-
const { randomBytes: randomBytes4 } = await import('crypto');
|
|
3526
|
-
authToken = randomBytes4(32).toString("hex");
|
|
3527
|
-
}
|
|
3528
|
-
const dashboard = new DashboardApprovalChannel({
|
|
3529
|
-
port: dashboardPort,
|
|
3530
|
-
host: dashboardHost,
|
|
3531
|
-
timeout_seconds: policy.approval_channel.timeout_seconds,
|
|
3532
|
-
auth_token: authToken,
|
|
3533
|
-
tls: config.dashboard.tls,
|
|
3534
|
-
auto_open: config.dashboard.auto_open ?? true
|
|
3535
|
-
// Default to auto-open in standalone mode
|
|
3536
|
-
});
|
|
3537
|
-
dashboard.setDependencies({ policy, baseline, auditLog });
|
|
3538
|
-
await dashboard.start();
|
|
3539
|
-
console.error(`Sanctuary Dashboard v${SANCTUARY_VERSION} (standalone mode)`);
|
|
3540
|
-
console.error(`Storage: ${config.storage_path}`);
|
|
3541
|
-
console.error(`Listening: http://${dashboardHost}:${dashboardPort}`);
|
|
3542
|
-
const saveBaseline = () => {
|
|
3543
|
-
baseline.save().catch(() => {
|
|
3544
|
-
});
|
|
3545
|
-
};
|
|
3546
|
-
process.on("SIGINT", saveBaseline);
|
|
3547
|
-
process.on("SIGTERM", saveBaseline);
|
|
3548
|
-
return dashboard;
|
|
3549
|
-
}
|
|
3550
|
-
var init_dashboard_standalone = __esm({
|
|
3551
|
-
"src/dashboard-standalone.ts"() {
|
|
3552
|
-
init_config();
|
|
3553
|
-
init_filesystem();
|
|
3554
|
-
init_audit_log();
|
|
3555
|
-
init_loader();
|
|
3556
|
-
init_baseline();
|
|
3557
|
-
init_dashboard();
|
|
3558
|
-
init_key_derivation();
|
|
3559
|
-
init_random();
|
|
3560
|
-
init_encoding();
|
|
3561
|
-
}
|
|
3562
|
-
});
|
|
3563
|
-
|
|
3564
|
-
// src/index.ts
|
|
3565
|
-
init_config();
|
|
3566
|
-
init_filesystem();
|
|
3567
|
-
|
|
3568
|
-
// src/l1-cognitive/state-store.ts
|
|
3569
|
-
init_encryption();
|
|
3570
|
-
init_hashing();
|
|
3571
|
-
|
|
3572
|
-
// src/core/identity.ts
|
|
3573
|
-
init_encoding();
|
|
3574
|
-
init_encryption();
|
|
3575
|
-
init_hashing();
|
|
3576
|
-
init_random();
|
|
3577
|
-
function generateKeypair() {
|
|
3578
|
-
const privateKey = randomBytes(32);
|
|
3579
|
-
const publicKey = ed25519.ed25519.getPublicKey(privateKey);
|
|
3580
|
-
return { publicKey, privateKey };
|
|
3581
|
-
}
|
|
3582
|
-
function publicKeyToDid(publicKey) {
|
|
3583
|
-
const multicodec = new Uint8Array([237, 1, ...publicKey]);
|
|
3584
|
-
return `did:key:z${toBase64url(multicodec)}`;
|
|
3585
|
-
}
|
|
3586
|
-
function generateIdentityId(publicKey) {
|
|
3587
|
-
const keyHash = hash(publicKey);
|
|
3588
|
-
return Array.from(keyHash.slice(0, 16)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3589
|
-
}
|
|
3590
|
-
function createIdentity(label, encryptionKey, keyProtection) {
|
|
3591
|
-
const { publicKey, privateKey } = generateKeypair();
|
|
3592
|
-
const identityId = generateIdentityId(publicKey);
|
|
3593
|
-
const did = publicKeyToDid(publicKey);
|
|
3594
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3595
|
-
const encryptedPrivateKey = encrypt(privateKey, encryptionKey);
|
|
3596
|
-
privateKey.fill(0);
|
|
3597
|
-
const publicIdentity = {
|
|
3598
|
-
identity_id: identityId,
|
|
3599
|
-
label,
|
|
3600
|
-
public_key: toBase64url(publicKey),
|
|
3601
|
-
did,
|
|
3602
|
-
created_at: now,
|
|
3603
|
-
key_type: "ed25519",
|
|
3604
|
-
key_protection: keyProtection
|
|
3605
|
-
};
|
|
3606
|
-
const storedIdentity = {
|
|
3607
|
-
...publicIdentity,
|
|
3608
|
-
encrypted_private_key: encryptedPrivateKey,
|
|
3609
|
-
rotation_history: []
|
|
3610
|
-
};
|
|
3611
|
-
return { publicIdentity, storedIdentity };
|
|
3612
|
-
}
|
|
3613
|
-
function sign(payload, encryptedPrivateKey, encryptionKey) {
|
|
3614
|
-
const privateKey = decrypt(encryptedPrivateKey, encryptionKey);
|
|
3615
|
-
try {
|
|
3616
|
-
return ed25519.ed25519.sign(payload, privateKey);
|
|
3617
|
-
} finally {
|
|
3618
|
-
privateKey.fill(0);
|
|
3619
|
-
}
|
|
3620
|
-
}
|
|
3621
|
-
function verify(payload, signature, publicKey) {
|
|
3622
|
-
try {
|
|
3623
|
-
return ed25519.ed25519.verify(signature, payload, publicKey);
|
|
3624
|
-
} catch {
|
|
3625
|
-
return false;
|
|
3626
|
-
}
|
|
3627
|
-
}
|
|
3628
|
-
function rotateKeys(storedIdentity, encryptionKey, reason) {
|
|
3629
|
-
const { publicKey: newPublicKey, privateKey: newPrivateKey } = generateKeypair();
|
|
3630
|
-
const newIdentityDid = publicKeyToDid(newPublicKey);
|
|
3631
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3632
|
-
const eventData = JSON.stringify({
|
|
3633
|
-
old_public_key: storedIdentity.public_key,
|
|
3634
|
-
new_public_key: toBase64url(newPublicKey),
|
|
3635
|
-
identity_id: storedIdentity.identity_id,
|
|
3636
|
-
reason,
|
|
3637
|
-
rotated_at: now
|
|
3638
|
-
});
|
|
3639
|
-
const eventBytes = new TextEncoder().encode(eventData);
|
|
3640
|
-
const signature = sign(
|
|
3641
|
-
eventBytes,
|
|
3642
|
-
storedIdentity.encrypted_private_key,
|
|
3643
|
-
encryptionKey
|
|
3644
|
-
);
|
|
3645
|
-
const rotationEvent = {
|
|
3646
|
-
old_public_key: storedIdentity.public_key,
|
|
3647
|
-
new_public_key: toBase64url(newPublicKey),
|
|
3648
|
-
identity_id: storedIdentity.identity_id,
|
|
3649
|
-
reason,
|
|
3650
|
-
rotated_at: now,
|
|
3651
|
-
signature: toBase64url(signature)
|
|
3652
|
-
};
|
|
3653
|
-
const encryptedNewPrivateKey = encrypt(newPrivateKey, encryptionKey);
|
|
3654
|
-
newPrivateKey.fill(0);
|
|
3655
|
-
const updatedIdentity = {
|
|
3656
|
-
...storedIdentity,
|
|
3657
|
-
public_key: toBase64url(newPublicKey),
|
|
3658
|
-
did: newIdentityDid,
|
|
3659
|
-
encrypted_private_key: encryptedNewPrivateKey,
|
|
3660
|
-
rotation_history: [
|
|
3661
|
-
...storedIdentity.rotation_history,
|
|
3662
|
-
{
|
|
3663
|
-
old_public_key: storedIdentity.public_key,
|
|
3664
|
-
new_public_key: toBase64url(newPublicKey),
|
|
3665
|
-
rotation_event: toBase64url(
|
|
3666
|
-
new TextEncoder().encode(JSON.stringify(rotationEvent))
|
|
3667
|
-
),
|
|
3668
|
-
rotated_at: now
|
|
3669
|
-
}
|
|
3670
|
-
]
|
|
4178
|
+
await baseline.load();
|
|
4179
|
+
const dashboardPort = options.port ?? config.dashboard.port;
|
|
4180
|
+
const dashboardHost = options.host ?? config.dashboard.host;
|
|
4181
|
+
let authToken = config.dashboard.auth_token;
|
|
4182
|
+
if (authToken === "auto") {
|
|
4183
|
+
const { randomBytes: randomBytes4 } = await import('crypto');
|
|
4184
|
+
authToken = randomBytes4(32).toString("hex");
|
|
4185
|
+
}
|
|
4186
|
+
const dashboard = new DashboardApprovalChannel({
|
|
4187
|
+
port: dashboardPort,
|
|
4188
|
+
host: dashboardHost,
|
|
4189
|
+
timeout_seconds: policy.approval_channel.timeout_seconds,
|
|
4190
|
+
auth_token: authToken,
|
|
4191
|
+
tls: config.dashboard.tls,
|
|
4192
|
+
auto_open: config.dashboard.auto_open ?? true
|
|
4193
|
+
// Default to auto-open in standalone mode
|
|
4194
|
+
});
|
|
4195
|
+
dashboard.setDependencies({ policy, baseline, auditLog });
|
|
4196
|
+
await dashboard.start();
|
|
4197
|
+
console.error(`Sanctuary Dashboard v${SANCTUARY_VERSION} (standalone mode)`);
|
|
4198
|
+
console.error(`Storage: ${config.storage_path}`);
|
|
4199
|
+
console.error(`Listening: http://${dashboardHost}:${dashboardPort}`);
|
|
4200
|
+
const saveBaseline = () => {
|
|
4201
|
+
baseline.save().catch(() => {
|
|
4202
|
+
});
|
|
3671
4203
|
};
|
|
3672
|
-
|
|
4204
|
+
process.on("SIGINT", saveBaseline);
|
|
4205
|
+
process.on("SIGTERM", saveBaseline);
|
|
4206
|
+
return dashboard;
|
|
3673
4207
|
}
|
|
4208
|
+
var init_dashboard_standalone = __esm({
|
|
4209
|
+
"src/dashboard-standalone.ts"() {
|
|
4210
|
+
init_config();
|
|
4211
|
+
init_filesystem();
|
|
4212
|
+
init_audit_log();
|
|
4213
|
+
init_loader();
|
|
4214
|
+
init_baseline();
|
|
4215
|
+
init_dashboard();
|
|
4216
|
+
init_key_derivation();
|
|
4217
|
+
init_random();
|
|
4218
|
+
init_encoding();
|
|
4219
|
+
}
|
|
4220
|
+
});
|
|
4221
|
+
|
|
4222
|
+
// src/index.ts
|
|
4223
|
+
init_config();
|
|
4224
|
+
init_filesystem();
|
|
3674
4225
|
|
|
3675
4226
|
// src/l1-cognitive/state-store.ts
|
|
4227
|
+
init_encryption();
|
|
4228
|
+
init_hashing();
|
|
4229
|
+
init_identity();
|
|
3676
4230
|
init_key_derivation();
|
|
3677
4231
|
init_encoding();
|
|
3678
4232
|
var RESERVED_NAMESPACE_PREFIXES = [
|
|
@@ -4212,6 +4766,7 @@ function toolResult(data) {
|
|
|
4212
4766
|
}
|
|
4213
4767
|
|
|
4214
4768
|
// src/l1-cognitive/tools.ts
|
|
4769
|
+
init_identity();
|
|
4215
4770
|
init_key_derivation();
|
|
4216
4771
|
init_encoding();
|
|
4217
4772
|
init_encryption();
|
|
@@ -5621,6 +6176,7 @@ init_encryption();
|
|
|
5621
6176
|
init_key_derivation();
|
|
5622
6177
|
init_encoding();
|
|
5623
6178
|
init_random();
|
|
6179
|
+
init_identity();
|
|
5624
6180
|
function computeMedian(values) {
|
|
5625
6181
|
if (values.length === 0) return 0;
|
|
5626
6182
|
const sorted = [...values].sort((a, b) => a - b);
|
|
@@ -7593,110 +8149,12 @@ function createPrincipalPolicyTools(policy, baseline, auditLog) {
|
|
|
7593
8149
|
];
|
|
7594
8150
|
}
|
|
7595
8151
|
|
|
7596
|
-
// src/shr/
|
|
7597
|
-
|
|
7598
|
-
if (obj === null || typeof obj !== "object") return obj;
|
|
7599
|
-
if (Array.isArray(obj)) return obj.map(deepSortKeys);
|
|
7600
|
-
const sorted = {};
|
|
7601
|
-
for (const key of Object.keys(obj).sort()) {
|
|
7602
|
-
sorted[key] = deepSortKeys(obj[key]);
|
|
7603
|
-
}
|
|
7604
|
-
return sorted;
|
|
7605
|
-
}
|
|
7606
|
-
function canonicalizeForSigning(body) {
|
|
7607
|
-
return JSON.stringify(deepSortKeys(body));
|
|
7608
|
-
}
|
|
7609
|
-
|
|
7610
|
-
// src/shr/generator.ts
|
|
7611
|
-
init_encoding();
|
|
7612
|
-
init_key_derivation();
|
|
7613
|
-
var DEFAULT_VALIDITY_MS = 60 * 60 * 1e3;
|
|
7614
|
-
function generateSHR(identityId, opts) {
|
|
7615
|
-
const { config, identityManager, masterKey, validityMs } = opts;
|
|
7616
|
-
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
7617
|
-
if (!identity) {
|
|
7618
|
-
return "No identity available for signing. Create an identity first.";
|
|
7619
|
-
}
|
|
7620
|
-
const now = /* @__PURE__ */ new Date();
|
|
7621
|
-
const expiresAt = new Date(now.getTime() + (validityMs ?? DEFAULT_VALIDITY_MS));
|
|
7622
|
-
const degradations = [];
|
|
7623
|
-
if (config.execution.environment === "local-process") {
|
|
7624
|
-
degradations.push({
|
|
7625
|
-
layer: "l2",
|
|
7626
|
-
code: "PROCESS_ISOLATION_ONLY",
|
|
7627
|
-
severity: "warning",
|
|
7628
|
-
description: "Process-level isolation only (no TEE)",
|
|
7629
|
-
mitigation: "TEE support planned for a future release"
|
|
7630
|
-
});
|
|
7631
|
-
degradations.push({
|
|
7632
|
-
layer: "l2",
|
|
7633
|
-
code: "SELF_REPORTED_ATTESTATION",
|
|
7634
|
-
severity: "warning",
|
|
7635
|
-
description: "Attestation is self-reported (no hardware root of trust)",
|
|
7636
|
-
mitigation: "TEE attestation planned for a future release"
|
|
7637
|
-
});
|
|
7638
|
-
}
|
|
7639
|
-
const body = {
|
|
7640
|
-
shr_version: "1.0",
|
|
7641
|
-
implementation: {
|
|
7642
|
-
sanctuary_version: config.version,
|
|
7643
|
-
node_version: process.versions.node,
|
|
7644
|
-
generated_by: "sanctuary-mcp-server"
|
|
7645
|
-
},
|
|
7646
|
-
instance_id: identity.identity_id,
|
|
7647
|
-
generated_at: now.toISOString(),
|
|
7648
|
-
expires_at: expiresAt.toISOString(),
|
|
7649
|
-
layers: {
|
|
7650
|
-
l1: {
|
|
7651
|
-
status: "active",
|
|
7652
|
-
encryption: config.state.encryption,
|
|
7653
|
-
key_custody: "self",
|
|
7654
|
-
integrity: config.state.integrity,
|
|
7655
|
-
identity_type: config.state.identity_provider,
|
|
7656
|
-
state_portable: true
|
|
7657
|
-
},
|
|
7658
|
-
l2: {
|
|
7659
|
-
status: config.execution.environment === "local-process" ? "degraded" : "active",
|
|
7660
|
-
isolation_type: config.execution.environment,
|
|
7661
|
-
attestation_available: config.execution.attestation
|
|
7662
|
-
},
|
|
7663
|
-
l3: {
|
|
7664
|
-
status: "active",
|
|
7665
|
-
proof_system: config.disclosure.proof_system,
|
|
7666
|
-
selective_disclosure: true
|
|
7667
|
-
},
|
|
7668
|
-
l4: {
|
|
7669
|
-
status: "active",
|
|
7670
|
-
reputation_mode: config.reputation.mode,
|
|
7671
|
-
attestation_format: config.reputation.attestation_format,
|
|
7672
|
-
reputation_portable: true
|
|
7673
|
-
}
|
|
7674
|
-
},
|
|
7675
|
-
capabilities: {
|
|
7676
|
-
handshake: true,
|
|
7677
|
-
shr_exchange: true,
|
|
7678
|
-
reputation_verify: true,
|
|
7679
|
-
encrypted_channel: false
|
|
7680
|
-
// Not yet implemented
|
|
7681
|
-
},
|
|
7682
|
-
degradations
|
|
7683
|
-
};
|
|
7684
|
-
const canonical = canonicalizeForSigning(body);
|
|
7685
|
-
const payload = stringToBytes(canonical);
|
|
7686
|
-
const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
7687
|
-
const signatureBytes = sign(
|
|
7688
|
-
payload,
|
|
7689
|
-
identity.encrypted_private_key,
|
|
7690
|
-
encryptionKey
|
|
7691
|
-
);
|
|
7692
|
-
return {
|
|
7693
|
-
body,
|
|
7694
|
-
signed_by: identity.public_key,
|
|
7695
|
-
signature: toBase64url(signatureBytes)
|
|
7696
|
-
};
|
|
7697
|
-
}
|
|
8152
|
+
// src/shr/tools.ts
|
|
8153
|
+
init_generator();
|
|
7698
8154
|
|
|
7699
8155
|
// src/shr/verifier.ts
|
|
8156
|
+
init_types();
|
|
8157
|
+
init_identity();
|
|
7700
8158
|
init_encoding();
|
|
7701
8159
|
function verifySHR(shr, now) {
|
|
7702
8160
|
const errors = [];
|
|
@@ -8118,7 +8576,11 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
|
|
|
8118
8576
|
return { tools };
|
|
8119
8577
|
}
|
|
8120
8578
|
|
|
8579
|
+
// src/handshake/tools.ts
|
|
8580
|
+
init_generator();
|
|
8581
|
+
|
|
8121
8582
|
// src/handshake/protocol.ts
|
|
8583
|
+
init_identity();
|
|
8122
8584
|
init_encoding();
|
|
8123
8585
|
init_random();
|
|
8124
8586
|
init_key_derivation();
|
|
@@ -8287,6 +8749,158 @@ function deriveTrustTier(level) {
|
|
|
8287
8749
|
}
|
|
8288
8750
|
}
|
|
8289
8751
|
|
|
8752
|
+
// src/handshake/attestation.ts
|
|
8753
|
+
init_types();
|
|
8754
|
+
init_identity();
|
|
8755
|
+
init_encoding();
|
|
8756
|
+
init_key_derivation();
|
|
8757
|
+
init_identity();
|
|
8758
|
+
init_encoding();
|
|
8759
|
+
var ATTESTATION_VERSION = "1.0";
|
|
8760
|
+
function deriveTrustTier2(level) {
|
|
8761
|
+
switch (level) {
|
|
8762
|
+
case "full":
|
|
8763
|
+
return "verified-sovereign";
|
|
8764
|
+
case "degraded":
|
|
8765
|
+
return "verified-degraded";
|
|
8766
|
+
default:
|
|
8767
|
+
return "unverified";
|
|
8768
|
+
}
|
|
8769
|
+
}
|
|
8770
|
+
function generateAttestation(opts) {
|
|
8771
|
+
const {
|
|
8772
|
+
attesterSHR,
|
|
8773
|
+
subjectSHR,
|
|
8774
|
+
verificationResult,
|
|
8775
|
+
mutual = false,
|
|
8776
|
+
identityManager,
|
|
8777
|
+
masterKey,
|
|
8778
|
+
identityId
|
|
8779
|
+
} = opts;
|
|
8780
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
8781
|
+
if (!identity) {
|
|
8782
|
+
return { error: "No identity available for signing attestation" };
|
|
8783
|
+
}
|
|
8784
|
+
const now = /* @__PURE__ */ new Date();
|
|
8785
|
+
const attesterExpiry = new Date(attesterSHR.body.expires_at);
|
|
8786
|
+
const subjectExpiry = new Date(subjectSHR.body.expires_at);
|
|
8787
|
+
const earliestExpiry = attesterExpiry < subjectExpiry ? attesterExpiry : subjectExpiry;
|
|
8788
|
+
const sovereigntyLevel = verificationResult.valid ? verificationResult.sovereignty_level : "unverified";
|
|
8789
|
+
const body = {
|
|
8790
|
+
attestation_version: ATTESTATION_VERSION,
|
|
8791
|
+
attester_id: attesterSHR.body.instance_id,
|
|
8792
|
+
subject_id: subjectSHR.body.instance_id,
|
|
8793
|
+
attester_shr: attesterSHR,
|
|
8794
|
+
subject_shr: subjectSHR,
|
|
8795
|
+
verification: {
|
|
8796
|
+
subject_shr_valid: verificationResult.valid,
|
|
8797
|
+
subject_sovereignty_level: sovereigntyLevel,
|
|
8798
|
+
subject_trust_tier: deriveTrustTier2(sovereigntyLevel),
|
|
8799
|
+
mutual,
|
|
8800
|
+
errors: verificationResult.errors,
|
|
8801
|
+
warnings: verificationResult.warnings
|
|
8802
|
+
},
|
|
8803
|
+
attested_at: now.toISOString(),
|
|
8804
|
+
expires_at: earliestExpiry.toISOString()
|
|
8805
|
+
};
|
|
8806
|
+
const canonical = JSON.stringify(deepSortKeys(body));
|
|
8807
|
+
const payload = stringToBytes(canonical);
|
|
8808
|
+
const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
8809
|
+
const signatureBytes = sign(
|
|
8810
|
+
payload,
|
|
8811
|
+
identity.encrypted_private_key,
|
|
8812
|
+
encryptionKey
|
|
8813
|
+
);
|
|
8814
|
+
const summary = generateSummary(body);
|
|
8815
|
+
return {
|
|
8816
|
+
body,
|
|
8817
|
+
signed_by: identity.public_key,
|
|
8818
|
+
signature: toBase64url(signatureBytes),
|
|
8819
|
+
summary
|
|
8820
|
+
};
|
|
8821
|
+
}
|
|
8822
|
+
function layerLine(label, status) {
|
|
8823
|
+
const icon = status === "active" ? "\u2713" : status === "degraded" ? "~" : "x";
|
|
8824
|
+
return ` ${icon} ${label}: ${status}`;
|
|
8825
|
+
}
|
|
8826
|
+
function generateSummary(body) {
|
|
8827
|
+
const v = body.verification;
|
|
8828
|
+
const sLayers = body.subject_shr.body.layers;
|
|
8829
|
+
const aLayers = body.attester_shr.body.layers;
|
|
8830
|
+
const tierLabel = v.subject_trust_tier === "verified-sovereign" ? "Verified Sovereign" : v.subject_trust_tier === "verified-degraded" ? "Verified (Degraded)" : "Unverified";
|
|
8831
|
+
const lines = [
|
|
8832
|
+
`--- Sovereignty Attestation ---`,
|
|
8833
|
+
``,
|
|
8834
|
+
`Attester: ${body.attester_id.slice(0, 16)}...`,
|
|
8835
|
+
`Subject: ${body.subject_id.slice(0, 16)}...`,
|
|
8836
|
+
`Result: ${tierLabel}`,
|
|
8837
|
+
``,
|
|
8838
|
+
`Subject Sovereignty Posture:`,
|
|
8839
|
+
layerLine("L1 Cognitive Sovereignty", sLayers.l1.status),
|
|
8840
|
+
layerLine("L2 Operational Isolation", sLayers.l2.status),
|
|
8841
|
+
layerLine("L3 Selective Disclosure", sLayers.l3.status),
|
|
8842
|
+
layerLine("L4 Verifiable Reputation", sLayers.l4.status),
|
|
8843
|
+
``,
|
|
8844
|
+
`Attester Sovereignty Posture:`,
|
|
8845
|
+
layerLine("L1 Cognitive Sovereignty", aLayers.l1.status),
|
|
8846
|
+
layerLine("L2 Operational Isolation", aLayers.l2.status),
|
|
8847
|
+
layerLine("L3 Selective Disclosure", aLayers.l3.status),
|
|
8848
|
+
layerLine("L4 Verifiable Reputation", aLayers.l4.status),
|
|
8849
|
+
``,
|
|
8850
|
+
`Mutual: ${v.mutual ? "Yes" : "One-sided"}`,
|
|
8851
|
+
`Attested: ${body.attested_at}`,
|
|
8852
|
+
`Expires: ${body.expires_at}`,
|
|
8853
|
+
`Signature: ${body.attestation_version} / Ed25519`
|
|
8854
|
+
];
|
|
8855
|
+
if (v.warnings.length > 0) {
|
|
8856
|
+
lines.push(``, `Warnings: ${v.warnings.join("; ")}`);
|
|
8857
|
+
}
|
|
8858
|
+
if (v.errors.length > 0) {
|
|
8859
|
+
lines.push(``, `Errors: ${v.errors.join("; ")}`);
|
|
8860
|
+
}
|
|
8861
|
+
lines.push(``, `--- Verify: compare signed_by against attester's known public key ---`);
|
|
8862
|
+
return lines.join("\n");
|
|
8863
|
+
}
|
|
8864
|
+
function verifyAttestation(attestation, now) {
|
|
8865
|
+
const errors = [];
|
|
8866
|
+
const currentTime = /* @__PURE__ */ new Date();
|
|
8867
|
+
if (attestation.body.attestation_version !== ATTESTATION_VERSION) {
|
|
8868
|
+
errors.push(
|
|
8869
|
+
`Unsupported attestation version: ${attestation.body.attestation_version}`
|
|
8870
|
+
);
|
|
8871
|
+
}
|
|
8872
|
+
if (!attestation.body.attester_id || !attestation.body.subject_id) {
|
|
8873
|
+
errors.push("Missing attester_id or subject_id");
|
|
8874
|
+
}
|
|
8875
|
+
if (!attestation.body.attester_shr || !attestation.body.subject_shr) {
|
|
8876
|
+
errors.push("Missing attester or subject SHR");
|
|
8877
|
+
}
|
|
8878
|
+
const expired = new Date(attestation.body.expires_at) <= currentTime;
|
|
8879
|
+
if (expired) {
|
|
8880
|
+
errors.push("Attestation has expired");
|
|
8881
|
+
}
|
|
8882
|
+
try {
|
|
8883
|
+
const publicKey = fromBase64url(attestation.signed_by);
|
|
8884
|
+
const canonical = JSON.stringify(deepSortKeys(attestation.body));
|
|
8885
|
+
const payload = stringToBytes(canonical);
|
|
8886
|
+
const signatureBytes = fromBase64url(attestation.signature);
|
|
8887
|
+
const signatureValid = verify(payload, signatureBytes, publicKey);
|
|
8888
|
+
if (!signatureValid) {
|
|
8889
|
+
errors.push("Attestation signature is invalid");
|
|
8890
|
+
}
|
|
8891
|
+
} catch (e) {
|
|
8892
|
+
errors.push(`Signature verification error: ${e.message}`);
|
|
8893
|
+
}
|
|
8894
|
+
return {
|
|
8895
|
+
valid: errors.length === 0,
|
|
8896
|
+
errors,
|
|
8897
|
+
attester_id: attestation.body.attester_id ?? "unknown",
|
|
8898
|
+
subject_id: attestation.body.subject_id ?? "unknown",
|
|
8899
|
+
trust_tier: errors.length === 0 ? attestation.body.verification.subject_trust_tier : "unverified",
|
|
8900
|
+
expired
|
|
8901
|
+
};
|
|
8902
|
+
}
|
|
8903
|
+
|
|
8290
8904
|
// src/handshake/tools.ts
|
|
8291
8905
|
function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
8292
8906
|
const sessions = /* @__PURE__ */ new Map();
|
|
@@ -8472,6 +9086,103 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
|
8472
9086
|
result: session.result ?? null
|
|
8473
9087
|
});
|
|
8474
9088
|
}
|
|
9089
|
+
},
|
|
9090
|
+
// ─── Streamlined Exchange ─────────────────────────────────────────
|
|
9091
|
+
{
|
|
9092
|
+
name: "sanctuary/handshake_exchange",
|
|
9093
|
+
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).",
|
|
9094
|
+
inputSchema: {
|
|
9095
|
+
type: "object",
|
|
9096
|
+
properties: {
|
|
9097
|
+
counterparty_shr: {
|
|
9098
|
+
type: "object",
|
|
9099
|
+
description: "The counterparty's signed SHR (SignedSHR object with body, signed_by, signature)."
|
|
9100
|
+
},
|
|
9101
|
+
identity_id: {
|
|
9102
|
+
type: "string",
|
|
9103
|
+
description: "Identity to use for the exchange. Defaults to primary identity."
|
|
9104
|
+
}
|
|
9105
|
+
},
|
|
9106
|
+
required: ["counterparty_shr"]
|
|
9107
|
+
},
|
|
9108
|
+
handler: async (args) => {
|
|
9109
|
+
const counterpartySHR = args.counterparty_shr;
|
|
9110
|
+
const ourSHR = generateSHR(args.identity_id, shrOpts);
|
|
9111
|
+
if (typeof ourSHR === "string") {
|
|
9112
|
+
return toolResult({ error: ourSHR });
|
|
9113
|
+
}
|
|
9114
|
+
const verificationResult = verifySHR(counterpartySHR);
|
|
9115
|
+
const attestation = generateAttestation({
|
|
9116
|
+
attesterSHR: ourSHR,
|
|
9117
|
+
subjectSHR: counterpartySHR,
|
|
9118
|
+
verificationResult,
|
|
9119
|
+
mutual: false,
|
|
9120
|
+
identityManager,
|
|
9121
|
+
masterKey,
|
|
9122
|
+
identityId: args.identity_id
|
|
9123
|
+
});
|
|
9124
|
+
if ("error" in attestation) {
|
|
9125
|
+
auditLog.append("l4", "handshake_exchange", ourSHR.body.instance_id, void 0, "failure");
|
|
9126
|
+
return toolResult({ error: attestation.error });
|
|
9127
|
+
}
|
|
9128
|
+
if (verificationResult.valid) {
|
|
9129
|
+
const sovereigntyLevel = verificationResult.sovereignty_level;
|
|
9130
|
+
const trustTier = sovereigntyLevel === "full" ? "verified-sovereign" : sovereigntyLevel === "degraded" ? "verified-degraded" : "unverified";
|
|
9131
|
+
handshakeResults.set(verificationResult.counterparty_id, {
|
|
9132
|
+
counterparty_id: verificationResult.counterparty_id,
|
|
9133
|
+
counterparty_shr: counterpartySHR,
|
|
9134
|
+
verified: true,
|
|
9135
|
+
sovereignty_level: sovereigntyLevel,
|
|
9136
|
+
trust_tier: trustTier,
|
|
9137
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9138
|
+
expires_at: verificationResult.expires_at,
|
|
9139
|
+
errors: []
|
|
9140
|
+
});
|
|
9141
|
+
}
|
|
9142
|
+
auditLog.append("l4", "handshake_exchange", ourSHR.body.instance_id);
|
|
9143
|
+
return toolResult({
|
|
9144
|
+
attestation,
|
|
9145
|
+
our_shr: ourSHR,
|
|
9146
|
+
verification: {
|
|
9147
|
+
counterparty_valid: verificationResult.valid,
|
|
9148
|
+
counterparty_sovereignty: verificationResult.sovereignty_level,
|
|
9149
|
+
counterparty_id: verificationResult.counterparty_id,
|
|
9150
|
+
errors: verificationResult.errors,
|
|
9151
|
+
warnings: verificationResult.warnings
|
|
9152
|
+
},
|
|
9153
|
+
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.",
|
|
9154
|
+
_content_trust: "external"
|
|
9155
|
+
});
|
|
9156
|
+
}
|
|
9157
|
+
},
|
|
9158
|
+
{
|
|
9159
|
+
name: "sanctuary/handshake_verify_attestation",
|
|
9160
|
+
description: "Verify a signed attestation artifact from another agent. Checks the Ed25519 signature, temporal validity, and structural integrity.",
|
|
9161
|
+
inputSchema: {
|
|
9162
|
+
type: "object",
|
|
9163
|
+
properties: {
|
|
9164
|
+
attestation: {
|
|
9165
|
+
type: "object",
|
|
9166
|
+
description: "The SignedAttestation object to verify (body, signed_by, signature, summary)."
|
|
9167
|
+
}
|
|
9168
|
+
},
|
|
9169
|
+
required: ["attestation"]
|
|
9170
|
+
},
|
|
9171
|
+
handler: async (args) => {
|
|
9172
|
+
const attestation = args.attestation;
|
|
9173
|
+
const result = verifyAttestation(attestation);
|
|
9174
|
+
auditLog.append(
|
|
9175
|
+
"l4",
|
|
9176
|
+
"handshake_verify_attestation",
|
|
9177
|
+
result.attester_id,
|
|
9178
|
+
void 0,
|
|
9179
|
+
result.valid ? "success" : "failure"
|
|
9180
|
+
);
|
|
9181
|
+
return toolResult({
|
|
9182
|
+
...result,
|
|
9183
|
+
_content_trust: "external"
|
|
9184
|
+
});
|
|
9185
|
+
}
|
|
8475
9186
|
}
|
|
8476
9187
|
];
|
|
8477
9188
|
return { tools, handshakeResults };
|
|
@@ -8841,6 +9552,7 @@ init_encryption();
|
|
|
8841
9552
|
init_encoding();
|
|
8842
9553
|
|
|
8843
9554
|
// src/bridge/bridge.ts
|
|
9555
|
+
init_identity();
|
|
8844
9556
|
init_encoding();
|
|
8845
9557
|
init_random();
|
|
8846
9558
|
init_hashing();
|
|
@@ -11929,6 +12641,7 @@ init_filesystem();
|
|
|
11929
12641
|
init_baseline();
|
|
11930
12642
|
init_loader();
|
|
11931
12643
|
init_dashboard();
|
|
12644
|
+
init_generator();
|
|
11932
12645
|
async function createSanctuaryServer(options) {
|
|
11933
12646
|
const config = await loadConfig(options?.configPath);
|
|
11934
12647
|
await promises.mkdir(config.storage_path, { recursive: true, mode: 448 });
|
|
@@ -12282,7 +12995,15 @@ async function createSanctuaryServer(options) {
|
|
|
12282
12995
|
tls: config.dashboard.tls,
|
|
12283
12996
|
auto_open: config.dashboard.auto_open
|
|
12284
12997
|
});
|
|
12285
|
-
dashboard.setDependencies({
|
|
12998
|
+
dashboard.setDependencies({
|
|
12999
|
+
policy,
|
|
13000
|
+
baseline,
|
|
13001
|
+
auditLog,
|
|
13002
|
+
identityManager,
|
|
13003
|
+
handshakeResults,
|
|
13004
|
+
shrOpts: { config, identityManager, masterKey },
|
|
13005
|
+
sanctuaryConfig: config
|
|
13006
|
+
});
|
|
12286
13007
|
await dashboard.start();
|
|
12287
13008
|
approvalChannel = dashboard;
|
|
12288
13009
|
} else if (config.webhook.enabled && config.webhook.url && config.webhook.secret) {
|