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