@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/index.cjs
CHANGED
|
@@ -3638,6 +3638,8 @@ var DEFAULT_POLICY = {
|
|
|
3638
3638
|
"handshake_respond",
|
|
3639
3639
|
"handshake_complete",
|
|
3640
3640
|
"handshake_status",
|
|
3641
|
+
"handshake_exchange",
|
|
3642
|
+
"handshake_verify_attestation",
|
|
3641
3643
|
"reputation_query_weighted",
|
|
3642
3644
|
"federation_peers",
|
|
3643
3645
|
"federation_trust_evaluate",
|
|
@@ -3807,6 +3809,8 @@ tier3_always_allow:
|
|
|
3807
3809
|
- handshake_respond
|
|
3808
3810
|
- handshake_complete
|
|
3809
3811
|
- handshake_status
|
|
3812
|
+
- handshake_exchange
|
|
3813
|
+
- handshake_verify_attestation
|
|
3810
3814
|
- reputation_query_weighted
|
|
3811
3815
|
- federation_peers
|
|
3812
3816
|
- federation_trust_evaluate
|
|
@@ -4063,179 +4067,362 @@ var AutoApproveChannel = class {
|
|
|
4063
4067
|
}
|
|
4064
4068
|
};
|
|
4065
4069
|
|
|
4070
|
+
// src/shr/types.ts
|
|
4071
|
+
function deepSortKeys(obj) {
|
|
4072
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
4073
|
+
if (Array.isArray(obj)) return obj.map(deepSortKeys);
|
|
4074
|
+
const sorted = {};
|
|
4075
|
+
for (const key of Object.keys(obj).sort()) {
|
|
4076
|
+
sorted[key] = deepSortKeys(obj[key]);
|
|
4077
|
+
}
|
|
4078
|
+
return sorted;
|
|
4079
|
+
}
|
|
4080
|
+
function canonicalizeForSigning(body) {
|
|
4081
|
+
return JSON.stringify(deepSortKeys(body));
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4084
|
+
// src/shr/generator.ts
|
|
4085
|
+
init_encoding();
|
|
4086
|
+
var DEFAULT_VALIDITY_MS = 60 * 60 * 1e3;
|
|
4087
|
+
function generateSHR(identityId, opts) {
|
|
4088
|
+
const { config, identityManager, masterKey, validityMs } = opts;
|
|
4089
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
4090
|
+
if (!identity) {
|
|
4091
|
+
return "No identity available for signing. Create an identity first.";
|
|
4092
|
+
}
|
|
4093
|
+
const now = /* @__PURE__ */ new Date();
|
|
4094
|
+
const expiresAt = new Date(now.getTime() + (validityMs ?? DEFAULT_VALIDITY_MS));
|
|
4095
|
+
const degradations = [];
|
|
4096
|
+
if (config.execution.environment === "local-process") {
|
|
4097
|
+
degradations.push({
|
|
4098
|
+
layer: "l2",
|
|
4099
|
+
code: "PROCESS_ISOLATION_ONLY",
|
|
4100
|
+
severity: "warning",
|
|
4101
|
+
description: "Process-level isolation only (no TEE)",
|
|
4102
|
+
mitigation: "TEE support planned for a future release"
|
|
4103
|
+
});
|
|
4104
|
+
degradations.push({
|
|
4105
|
+
layer: "l2",
|
|
4106
|
+
code: "SELF_REPORTED_ATTESTATION",
|
|
4107
|
+
severity: "warning",
|
|
4108
|
+
description: "Attestation is self-reported (no hardware root of trust)",
|
|
4109
|
+
mitigation: "TEE attestation planned for a future release"
|
|
4110
|
+
});
|
|
4111
|
+
}
|
|
4112
|
+
const body = {
|
|
4113
|
+
shr_version: "1.0",
|
|
4114
|
+
implementation: {
|
|
4115
|
+
sanctuary_version: config.version,
|
|
4116
|
+
node_version: process.versions.node,
|
|
4117
|
+
generated_by: "sanctuary-mcp-server"
|
|
4118
|
+
},
|
|
4119
|
+
instance_id: identity.identity_id,
|
|
4120
|
+
generated_at: now.toISOString(),
|
|
4121
|
+
expires_at: expiresAt.toISOString(),
|
|
4122
|
+
layers: {
|
|
4123
|
+
l1: {
|
|
4124
|
+
status: "active",
|
|
4125
|
+
encryption: config.state.encryption,
|
|
4126
|
+
key_custody: "self",
|
|
4127
|
+
integrity: config.state.integrity,
|
|
4128
|
+
identity_type: config.state.identity_provider,
|
|
4129
|
+
state_portable: true
|
|
4130
|
+
},
|
|
4131
|
+
l2: {
|
|
4132
|
+
status: config.execution.environment === "local-process" ? "degraded" : "active",
|
|
4133
|
+
isolation_type: config.execution.environment,
|
|
4134
|
+
attestation_available: config.execution.attestation
|
|
4135
|
+
},
|
|
4136
|
+
l3: {
|
|
4137
|
+
status: "active",
|
|
4138
|
+
proof_system: config.disclosure.proof_system,
|
|
4139
|
+
selective_disclosure: true
|
|
4140
|
+
},
|
|
4141
|
+
l4: {
|
|
4142
|
+
status: "active",
|
|
4143
|
+
reputation_mode: config.reputation.mode,
|
|
4144
|
+
attestation_format: config.reputation.attestation_format,
|
|
4145
|
+
reputation_portable: true
|
|
4146
|
+
}
|
|
4147
|
+
},
|
|
4148
|
+
capabilities: {
|
|
4149
|
+
handshake: true,
|
|
4150
|
+
shr_exchange: true,
|
|
4151
|
+
reputation_verify: true,
|
|
4152
|
+
encrypted_channel: false
|
|
4153
|
+
// Not yet implemented
|
|
4154
|
+
},
|
|
4155
|
+
degradations
|
|
4156
|
+
};
|
|
4157
|
+
const canonical = canonicalizeForSigning(body);
|
|
4158
|
+
const payload = stringToBytes(canonical);
|
|
4159
|
+
const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
4160
|
+
const signatureBytes = sign(
|
|
4161
|
+
payload,
|
|
4162
|
+
identity.encrypted_private_key,
|
|
4163
|
+
encryptionKey
|
|
4164
|
+
);
|
|
4165
|
+
return {
|
|
4166
|
+
body,
|
|
4167
|
+
signed_by: identity.public_key,
|
|
4168
|
+
signature: toBase64url(signatureBytes)
|
|
4169
|
+
};
|
|
4170
|
+
}
|
|
4171
|
+
|
|
4066
4172
|
// src/principal-policy/dashboard-html.ts
|
|
4067
4173
|
function generateLoginHTML(options) {
|
|
4068
4174
|
return `<!DOCTYPE html>
|
|
4069
4175
|
<html lang="en">
|
|
4070
4176
|
<head>
|
|
4071
|
-
<meta charset="
|
|
4072
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
4073
|
-
<title>Sanctuary \u2014
|
|
4074
|
-
<
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4177
|
+
<meta charset="UTF-8">
|
|
4178
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4179
|
+
<title>Sanctuary \u2014 Principal Dashboard</title>
|
|
4180
|
+
<style>
|
|
4181
|
+
:root {
|
|
4182
|
+
--bg: #0d1117;
|
|
4183
|
+
--surface: #161b22;
|
|
4184
|
+
--border: #30363d;
|
|
4185
|
+
--text-primary: #e6edf3;
|
|
4186
|
+
--text-secondary: #8b949e;
|
|
4187
|
+
--green: #3fb950;
|
|
4188
|
+
--amber: #d29922;
|
|
4189
|
+
--red: #f85149;
|
|
4190
|
+
--blue: #58a6ff;
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
* {
|
|
4194
|
+
margin: 0;
|
|
4195
|
+
padding: 0;
|
|
4196
|
+
box-sizing: border-box;
|
|
4197
|
+
}
|
|
4198
|
+
|
|
4199
|
+
body {
|
|
4200
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
4201
|
+
background-color: var(--bg);
|
|
4202
|
+
color: var(--text-primary);
|
|
4203
|
+
min-height: 100vh;
|
|
4204
|
+
display: flex;
|
|
4205
|
+
align-items: center;
|
|
4206
|
+
justify-content: center;
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
.login-container {
|
|
4210
|
+
width: 100%;
|
|
4211
|
+
max-width: 400px;
|
|
4212
|
+
padding: 20px;
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
.login-card {
|
|
4216
|
+
background-color: var(--surface);
|
|
4217
|
+
border: 1px solid var(--border);
|
|
4218
|
+
border-radius: 8px;
|
|
4219
|
+
padding: 40px 32px;
|
|
4220
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
4221
|
+
}
|
|
4222
|
+
|
|
4223
|
+
.login-header {
|
|
4224
|
+
display: flex;
|
|
4225
|
+
align-items: center;
|
|
4226
|
+
gap: 12px;
|
|
4227
|
+
margin-bottom: 32px;
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
.logo {
|
|
4231
|
+
font-size: 24px;
|
|
4232
|
+
font-weight: 700;
|
|
4233
|
+
color: var(--blue);
|
|
4234
|
+
}
|
|
4235
|
+
|
|
4236
|
+
.logo-text {
|
|
4237
|
+
display: flex;
|
|
4238
|
+
flex-direction: column;
|
|
4239
|
+
}
|
|
4240
|
+
|
|
4241
|
+
.logo-text .title {
|
|
4242
|
+
font-size: 18px;
|
|
4243
|
+
font-weight: 600;
|
|
4244
|
+
letter-spacing: -0.5px;
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
.logo-text .version {
|
|
4248
|
+
font-size: 12px;
|
|
4249
|
+
color: var(--text-secondary);
|
|
4250
|
+
margin-top: 2px;
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4253
|
+
.form-group {
|
|
4254
|
+
margin-bottom: 24px;
|
|
4255
|
+
}
|
|
4256
|
+
|
|
4257
|
+
label {
|
|
4258
|
+
display: block;
|
|
4259
|
+
font-size: 14px;
|
|
4260
|
+
font-weight: 500;
|
|
4261
|
+
margin-bottom: 8px;
|
|
4262
|
+
color: var(--text-primary);
|
|
4263
|
+
}
|
|
4264
|
+
|
|
4265
|
+
input[type="text"],
|
|
4266
|
+
input[type="password"] {
|
|
4267
|
+
width: 100%;
|
|
4268
|
+
padding: 10px 12px;
|
|
4269
|
+
background-color: var(--bg);
|
|
4270
|
+
border: 1px solid var(--border);
|
|
4271
|
+
border-radius: 6px;
|
|
4272
|
+
color: var(--text-primary);
|
|
4273
|
+
font-size: 14px;
|
|
4274
|
+
font-family: 'JetBrains Mono', monospace;
|
|
4275
|
+
transition: border-color 0.2s;
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
input[type="text"]:focus,
|
|
4279
|
+
input[type="password"]:focus {
|
|
4280
|
+
outline: none;
|
|
4281
|
+
border-color: var(--blue);
|
|
4282
|
+
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.1);
|
|
4283
|
+
}
|
|
4284
|
+
|
|
4285
|
+
.error-message {
|
|
4286
|
+
display: none;
|
|
4287
|
+
background-color: rgba(248, 81, 73, 0.1);
|
|
4288
|
+
border: 1px solid var(--red);
|
|
4289
|
+
color: #ff9999;
|
|
4290
|
+
padding: 12px;
|
|
4291
|
+
border-radius: 6px;
|
|
4292
|
+
font-size: 13px;
|
|
4293
|
+
margin-bottom: 20px;
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4296
|
+
.error-message.show {
|
|
4297
|
+
display: block;
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
button {
|
|
4301
|
+
width: 100%;
|
|
4302
|
+
padding: 10px 16px;
|
|
4303
|
+
background-color: var(--blue);
|
|
4304
|
+
color: var(--bg);
|
|
4305
|
+
border: none;
|
|
4306
|
+
border-radius: 6px;
|
|
4307
|
+
font-size: 14px;
|
|
4308
|
+
font-weight: 600;
|
|
4309
|
+
cursor: pointer;
|
|
4310
|
+
transition: background-color 0.2s;
|
|
4311
|
+
}
|
|
4312
|
+
|
|
4313
|
+
button:hover {
|
|
4314
|
+
background-color: #79c0ff;
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
button:active {
|
|
4318
|
+
background-color: #4184e4;
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
button:disabled {
|
|
4322
|
+
background-color: var(--text-secondary);
|
|
4323
|
+
cursor: not-allowed;
|
|
4324
|
+
opacity: 0.5;
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
.info-text {
|
|
4328
|
+
font-size: 12px;
|
|
4329
|
+
color: var(--text-secondary);
|
|
4330
|
+
margin-top: 16px;
|
|
4331
|
+
text-align: center;
|
|
4332
|
+
}
|
|
4333
|
+
</style>
|
|
4185
4334
|
</head>
|
|
4186
4335
|
<body>
|
|
4187
|
-
<div class="login-container">
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4336
|
+
<div class="login-container">
|
|
4337
|
+
<div class="login-card">
|
|
4338
|
+
<div class="login-header">
|
|
4339
|
+
<div class="logo">\u25C6</div>
|
|
4340
|
+
<div class="logo-text">
|
|
4341
|
+
<div class="title">SANCTUARY</div>
|
|
4342
|
+
<div class="version">v${options.serverVersion}</div>
|
|
4343
|
+
</div>
|
|
4344
|
+
</div>
|
|
4345
|
+
|
|
4346
|
+
<div id="error-message" class="error-message"></div>
|
|
4347
|
+
|
|
4348
|
+
<form id="login-form">
|
|
4349
|
+
<div class="form-group">
|
|
4350
|
+
<label for="auth-token">Auth Token</label>
|
|
4351
|
+
<input
|
|
4352
|
+
type="text"
|
|
4353
|
+
id="auth-token"
|
|
4354
|
+
name="token"
|
|
4355
|
+
placeholder="Paste your session token..."
|
|
4356
|
+
autocomplete="off"
|
|
4357
|
+
spellcheck="false"
|
|
4358
|
+
required
|
|
4359
|
+
/>
|
|
4360
|
+
</div>
|
|
4361
|
+
|
|
4362
|
+
<button type="submit" id="login-button">Open Dashboard</button>
|
|
4363
|
+
</form>
|
|
4364
|
+
|
|
4365
|
+
<div class="info-text">
|
|
4366
|
+
Session tokens expire after 1 hour of inactivity
|
|
4367
|
+
</div>
|
|
4368
|
+
</div>
|
|
4200
4369
|
</div>
|
|
4201
|
-
|
|
4202
|
-
<script>
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4370
|
+
|
|
4371
|
+
<script>
|
|
4372
|
+
const loginForm = document.getElementById('login-form');
|
|
4373
|
+
const authTokenInput = document.getElementById('auth-token');
|
|
4374
|
+
const errorMessage = document.getElementById('error-message');
|
|
4375
|
+
const loginButton = document.getElementById('login-button');
|
|
4376
|
+
|
|
4377
|
+
loginForm.addEventListener('submit', async (e) => {
|
|
4378
|
+
e.preventDefault();
|
|
4379
|
+
const token = authTokenInput.value.trim();
|
|
4380
|
+
|
|
4381
|
+
if (!token) {
|
|
4382
|
+
showError('Token is required');
|
|
4383
|
+
return;
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
loginButton.disabled = true;
|
|
4387
|
+
loginButton.textContent = 'Verifying...';
|
|
4388
|
+
errorMessage.classList.remove('show');
|
|
4389
|
+
|
|
4390
|
+
try {
|
|
4391
|
+
const response = await fetch('/auth/session', {
|
|
4392
|
+
method: 'POST',
|
|
4393
|
+
headers: {
|
|
4394
|
+
'Content-Type': 'application/json',
|
|
4395
|
+
'Authorization': 'Bearer ' + token,
|
|
4396
|
+
},
|
|
4397
|
+
body: JSON.stringify({ token }),
|
|
4398
|
+
});
|
|
4399
|
+
|
|
4400
|
+
if (response.ok) {
|
|
4401
|
+
const data = await response.json();
|
|
4402
|
+
sessionStorage.setItem('authToken', token);
|
|
4403
|
+
window.location.href = '/dashboard';
|
|
4404
|
+
} else if (response.status === 401) {
|
|
4405
|
+
showError('Invalid token. Please check and try again.');
|
|
4406
|
+
} else {
|
|
4407
|
+
showError('Authentication failed. Please try again.');
|
|
4408
|
+
}
|
|
4409
|
+
} catch (err) {
|
|
4410
|
+
showError('Connection error. Please check your network.');
|
|
4411
|
+
} finally {
|
|
4412
|
+
loginButton.disabled = false;
|
|
4413
|
+
loginButton.textContent = 'Open Dashboard';
|
|
4414
|
+
}
|
|
4216
4415
|
});
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
'; path=/; SameSite=Strict; max-age=' + maxAge;
|
|
4228
|
-
// Reload to enter the dashboard
|
|
4229
|
-
window.location.reload();
|
|
4230
|
-
} catch (err) {
|
|
4231
|
-
errEl.textContent = err.message || 'Authentication failed. Check your token.';
|
|
4232
|
-
errEl.style.display = 'block';
|
|
4233
|
-
btn.disabled = false;
|
|
4234
|
-
btn.textContent = 'Open Dashboard';
|
|
4235
|
-
}
|
|
4236
|
-
return false;
|
|
4237
|
-
}
|
|
4238
|
-
</script>
|
|
4416
|
+
|
|
4417
|
+
function showError(message) {
|
|
4418
|
+
errorMessage.textContent = message;
|
|
4419
|
+
errorMessage.classList.add('show');
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
authTokenInput.addEventListener('input', () => {
|
|
4423
|
+
errorMessage.classList.remove('show');
|
|
4424
|
+
});
|
|
4425
|
+
</script>
|
|
4239
4426
|
</body>
|
|
4240
4427
|
</html>`;
|
|
4241
4428
|
}
|
|
@@ -4243,1412 +4430,1648 @@ function generateDashboardHTML(options) {
|
|
|
4243
4430
|
return `<!DOCTYPE html>
|
|
4244
4431
|
<html lang="en">
|
|
4245
4432
|
<head>
|
|
4246
|
-
<meta charset="
|
|
4247
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
4248
|
-
<title>Sanctuary \u2014 Principal Dashboard</title>
|
|
4249
|
-
<
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4433
|
+
<meta charset="UTF-8">
|
|
4434
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4435
|
+
<title>Sanctuary \u2014 Principal Dashboard</title>
|
|
4436
|
+
<style>
|
|
4437
|
+
:root {
|
|
4438
|
+
--bg: #0d1117;
|
|
4439
|
+
--surface: #161b22;
|
|
4440
|
+
--border: #30363d;
|
|
4441
|
+
--text-primary: #e6edf3;
|
|
4442
|
+
--text-secondary: #8b949e;
|
|
4443
|
+
--green: #3fb950;
|
|
4444
|
+
--amber: #d29922;
|
|
4445
|
+
--red: #f85149;
|
|
4446
|
+
--blue: #58a6ff;
|
|
4447
|
+
--success: #3fb950;
|
|
4448
|
+
--warning: #d29922;
|
|
4449
|
+
--error: #f85149;
|
|
4450
|
+
--muted: #21262d;
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4453
|
+
* {
|
|
4454
|
+
margin: 0;
|
|
4455
|
+
padding: 0;
|
|
4456
|
+
box-sizing: border-box;
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
html, body {
|
|
4460
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
4461
|
+
background-color: var(--bg);
|
|
4462
|
+
color: var(--text-primary);
|
|
4463
|
+
height: 100%;
|
|
4464
|
+
overflow: hidden;
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
body {
|
|
4468
|
+
display: flex;
|
|
4469
|
+
flex-direction: column;
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
/* Status Bar */
|
|
4473
|
+
.status-bar {
|
|
4474
|
+
position: fixed;
|
|
4475
|
+
top: 0;
|
|
4476
|
+
left: 0;
|
|
4477
|
+
right: 0;
|
|
4478
|
+
height: 56px;
|
|
4479
|
+
background-color: var(--surface);
|
|
4480
|
+
border-bottom: 1px solid var(--border);
|
|
4481
|
+
display: flex;
|
|
4482
|
+
align-items: center;
|
|
4483
|
+
padding: 0 24px;
|
|
4484
|
+
gap: 24px;
|
|
4485
|
+
z-index: 100;
|
|
4486
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
.status-bar-left {
|
|
4490
|
+
display: flex;
|
|
4491
|
+
align-items: center;
|
|
4492
|
+
gap: 12px;
|
|
4493
|
+
flex: 0 0 auto;
|
|
4494
|
+
}
|
|
4302
4495
|
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
}
|
|
4496
|
+
.logo-icon {
|
|
4497
|
+
font-size: 20px;
|
|
4498
|
+
color: var(--blue);
|
|
4499
|
+
font-weight: 700;
|
|
4500
|
+
}
|
|
4309
4501
|
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
color: var(--text-primary);
|
|
4315
|
-
}
|
|
4502
|
+
.logo-info {
|
|
4503
|
+
display: flex;
|
|
4504
|
+
flex-direction: column;
|
|
4505
|
+
}
|
|
4316
4506
|
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4507
|
+
.logo-title {
|
|
4508
|
+
font-size: 13px;
|
|
4509
|
+
font-weight: 600;
|
|
4510
|
+
line-height: 1;
|
|
4511
|
+
color: var(--text-primary);
|
|
4512
|
+
}
|
|
4320
4513
|
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4514
|
+
.logo-version {
|
|
4515
|
+
font-size: 11px;
|
|
4516
|
+
color: var(--text-secondary);
|
|
4517
|
+
margin-top: 2px;
|
|
4518
|
+
}
|
|
4326
4519
|
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
}
|
|
4333
|
-
|
|
4334
|
-
.sovereignty-badge {
|
|
4335
|
-
display: flex;
|
|
4336
|
-
align-items: center;
|
|
4337
|
-
gap: 8px;
|
|
4338
|
-
padding: 6px 12px;
|
|
4339
|
-
background: rgba(88, 166, 255, 0.1);
|
|
4340
|
-
border: 1px solid var(--blue);
|
|
4341
|
-
border-radius: 20px;
|
|
4342
|
-
font-size: 13px;
|
|
4343
|
-
font-weight: 600;
|
|
4344
|
-
}
|
|
4520
|
+
.status-bar-center {
|
|
4521
|
+
flex: 1;
|
|
4522
|
+
display: flex;
|
|
4523
|
+
justify-content: center;
|
|
4524
|
+
}
|
|
4345
4525
|
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
color: var(--bg);
|
|
4358
|
-
}
|
|
4526
|
+
.sovereignty-badge {
|
|
4527
|
+
display: flex;
|
|
4528
|
+
align-items: center;
|
|
4529
|
+
gap: 8px;
|
|
4530
|
+
padding: 8px 16px;
|
|
4531
|
+
background-color: rgba(63, 185, 80, 0.1);
|
|
4532
|
+
border: 1px solid rgba(63, 185, 80, 0.3);
|
|
4533
|
+
border-radius: 6px;
|
|
4534
|
+
font-size: 13px;
|
|
4535
|
+
font-weight: 500;
|
|
4536
|
+
}
|
|
4359
4537
|
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4538
|
+
.sovereignty-badge.degraded {
|
|
4539
|
+
background-color: rgba(210, 153, 34, 0.1);
|
|
4540
|
+
border-color: rgba(210, 153, 34, 0.3);
|
|
4541
|
+
}
|
|
4363
4542
|
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4543
|
+
.sovereignty-badge.inactive {
|
|
4544
|
+
background-color: rgba(248, 81, 73, 0.1);
|
|
4545
|
+
border-color: rgba(248, 81, 73, 0.3);
|
|
4546
|
+
}
|
|
4367
4547
|
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4548
|
+
.sovereignty-score {
|
|
4549
|
+
font-weight: 700;
|
|
4550
|
+
color: var(--green);
|
|
4551
|
+
}
|
|
4371
4552
|
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
gap: 16px;
|
|
4376
|
-
flex: 0 0 auto;
|
|
4377
|
-
}
|
|
4553
|
+
.sovereignty-badge.degraded .sovereignty-score {
|
|
4554
|
+
color: var(--amber);
|
|
4555
|
+
}
|
|
4378
4556
|
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
gap: 6px;
|
|
4383
|
-
font-size: 12px;
|
|
4384
|
-
color: var(--text-secondary);
|
|
4385
|
-
font-family: var(--mono);
|
|
4386
|
-
}
|
|
4557
|
+
.sovereignty-badge.inactive .sovereignty-score {
|
|
4558
|
+
color: var(--red);
|
|
4559
|
+
}
|
|
4387
4560
|
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4561
|
+
.status-bar-right {
|
|
4562
|
+
display: flex;
|
|
4563
|
+
align-items: center;
|
|
4564
|
+
gap: 16px;
|
|
4565
|
+
flex: 0 0 auto;
|
|
4566
|
+
}
|
|
4392
4567
|
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
}
|
|
4568
|
+
.status-item {
|
|
4569
|
+
display: flex;
|
|
4570
|
+
align-items: center;
|
|
4571
|
+
gap: 6px;
|
|
4572
|
+
font-size: 12px;
|
|
4573
|
+
color: var(--text-secondary);
|
|
4574
|
+
}
|
|
4401
4575
|
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
background: var(--green);
|
|
4407
|
-
animation: pulse 2s ease-in-out infinite;
|
|
4408
|
-
}
|
|
4576
|
+
.status-item strong {
|
|
4577
|
+
color: var(--text-primary);
|
|
4578
|
+
font-weight: 500;
|
|
4579
|
+
}
|
|
4409
4580
|
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4581
|
+
.status-dot {
|
|
4582
|
+
width: 8px;
|
|
4583
|
+
height: 8px;
|
|
4584
|
+
border-radius: 50%;
|
|
4585
|
+
background-color: var(--green);
|
|
4586
|
+
}
|
|
4414
4587
|
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
}
|
|
4588
|
+
.status-dot.disconnected {
|
|
4589
|
+
background-color: var(--red);
|
|
4590
|
+
}
|
|
4419
4591
|
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
animation: pulse 1s ease-in-out infinite;
|
|
4433
|
-
}
|
|
4592
|
+
.pending-badge {
|
|
4593
|
+
display: flex;
|
|
4594
|
+
align-items: center;
|
|
4595
|
+
gap: 6px;
|
|
4596
|
+
padding: 4px 8px;
|
|
4597
|
+
background-color: var(--blue);
|
|
4598
|
+
color: var(--bg);
|
|
4599
|
+
border-radius: 4px;
|
|
4600
|
+
font-size: 11px;
|
|
4601
|
+
font-weight: 600;
|
|
4602
|
+
cursor: pointer;
|
|
4603
|
+
}
|
|
4434
4604
|
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4605
|
+
/* Main Content */
|
|
4606
|
+
.main-content {
|
|
4607
|
+
flex: 1;
|
|
4608
|
+
margin-top: 56px;
|
|
4609
|
+
overflow-y: auto;
|
|
4610
|
+
padding: 24px;
|
|
4611
|
+
}
|
|
4438
4612
|
|
|
4439
|
-
|
|
4613
|
+
.grid {
|
|
4614
|
+
display: grid;
|
|
4615
|
+
gap: 20px;
|
|
4616
|
+
}
|
|
4440
4617
|
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4618
|
+
/* Row 1: Sovereignty Layers */
|
|
4619
|
+
.sovereignty-layers {
|
|
4620
|
+
display: grid;
|
|
4621
|
+
grid-template-columns: repeat(4, 1fr);
|
|
4622
|
+
gap: 16px;
|
|
4623
|
+
}
|
|
4447
4624
|
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4625
|
+
.layer-card {
|
|
4626
|
+
background-color: var(--surface);
|
|
4627
|
+
border: 1px solid var(--border);
|
|
4628
|
+
border-radius: 8px;
|
|
4629
|
+
padding: 20px;
|
|
4630
|
+
display: flex;
|
|
4631
|
+
flex-direction: column;
|
|
4632
|
+
gap: 12px;
|
|
4633
|
+
}
|
|
4455
4634
|
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
align-items: center;
|
|
4461
|
-
gap: 8px;
|
|
4462
|
-
font-size: 12px;
|
|
4463
|
-
font-weight: 600;
|
|
4464
|
-
text-transform: uppercase;
|
|
4465
|
-
letter-spacing: 0.5px;
|
|
4466
|
-
color: var(--text-secondary);
|
|
4467
|
-
}
|
|
4635
|
+
.layer-card.degraded {
|
|
4636
|
+
border-color: var(--amber);
|
|
4637
|
+
background-color: rgba(210, 153, 34, 0.05);
|
|
4638
|
+
}
|
|
4468
4639
|
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
background: var(--green);
|
|
4474
|
-
}
|
|
4640
|
+
.layer-card.inactive {
|
|
4641
|
+
border-color: var(--red);
|
|
4642
|
+
background-color: rgba(248, 81, 73, 0.05);
|
|
4643
|
+
}
|
|
4475
4644
|
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4645
|
+
.layer-name {
|
|
4646
|
+
font-size: 12px;
|
|
4647
|
+
font-weight: 600;
|
|
4648
|
+
color: var(--text-secondary);
|
|
4649
|
+
text-transform: uppercase;
|
|
4650
|
+
letter-spacing: 0.5px;
|
|
4651
|
+
}
|
|
4481
4652
|
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
cursor: pointer;
|
|
4488
|
-
transition: background 0.15s;
|
|
4489
|
-
display: flex;
|
|
4490
|
-
align-items: flex-start;
|
|
4491
|
-
gap: 10px;
|
|
4492
|
-
}
|
|
4653
|
+
.layer-title {
|
|
4654
|
+
font-size: 14px;
|
|
4655
|
+
font-weight: 600;
|
|
4656
|
+
color: var(--text-primary);
|
|
4657
|
+
}
|
|
4493
4658
|
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4659
|
+
.layer-status {
|
|
4660
|
+
display: inline-flex;
|
|
4661
|
+
align-items: center;
|
|
4662
|
+
gap: 6px;
|
|
4663
|
+
padding: 4px 8px;
|
|
4664
|
+
background-color: rgba(63, 185, 80, 0.15);
|
|
4665
|
+
color: var(--green);
|
|
4666
|
+
border-radius: 4px;
|
|
4667
|
+
font-size: 11px;
|
|
4668
|
+
font-weight: 600;
|
|
4669
|
+
width: fit-content;
|
|
4670
|
+
}
|
|
4497
4671
|
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
font-size: 12px;
|
|
4503
|
-
color: var(--text-secondary);
|
|
4504
|
-
margin-top: 1px;
|
|
4505
|
-
}
|
|
4672
|
+
.layer-card.degraded .layer-status {
|
|
4673
|
+
background-color: rgba(210, 153, 34, 0.15);
|
|
4674
|
+
color: var(--amber);
|
|
4675
|
+
}
|
|
4506
4676
|
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4677
|
+
.layer-card.inactive .layer-status {
|
|
4678
|
+
background-color: rgba(248, 81, 73, 0.15);
|
|
4679
|
+
color: var(--red);
|
|
4680
|
+
}
|
|
4511
4681
|
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4682
|
+
.layer-detail {
|
|
4683
|
+
font-size: 12px;
|
|
4684
|
+
color: var(--text-secondary);
|
|
4685
|
+
font-family: 'JetBrains Mono', monospace;
|
|
4686
|
+
padding: 8px;
|
|
4687
|
+
background-color: var(--bg);
|
|
4688
|
+
border-radius: 4px;
|
|
4689
|
+
border-left: 2px solid var(--blue);
|
|
4690
|
+
}
|
|
4517
4691
|
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4692
|
+
/* Row 2: Info Cards */
|
|
4693
|
+
.info-cards {
|
|
4694
|
+
display: grid;
|
|
4695
|
+
grid-template-columns: repeat(3, 1fr);
|
|
4696
|
+
gap: 16px;
|
|
4697
|
+
}
|
|
4524
4698
|
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
font-size: 10px;
|
|
4532
|
-
font-weight: 700;
|
|
4533
|
-
border-radius: 3px;
|
|
4534
|
-
text-transform: uppercase;
|
|
4535
|
-
flex: 0 0 auto;
|
|
4536
|
-
}
|
|
4699
|
+
.info-card {
|
|
4700
|
+
background-color: var(--surface);
|
|
4701
|
+
border: 1px solid var(--border);
|
|
4702
|
+
border-radius: 8px;
|
|
4703
|
+
padding: 20px;
|
|
4704
|
+
}
|
|
4537
4705
|
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4706
|
+
.card-header {
|
|
4707
|
+
font-size: 12px;
|
|
4708
|
+
font-weight: 600;
|
|
4709
|
+
color: var(--text-secondary);
|
|
4710
|
+
text-transform: uppercase;
|
|
4711
|
+
letter-spacing: 0.5px;
|
|
4712
|
+
margin-bottom: 16px;
|
|
4713
|
+
}
|
|
4542
4714
|
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4715
|
+
.card-row {
|
|
4716
|
+
display: flex;
|
|
4717
|
+
justify-content: space-between;
|
|
4718
|
+
align-items: center;
|
|
4719
|
+
margin-bottom: 12px;
|
|
4720
|
+
font-size: 13px;
|
|
4721
|
+
}
|
|
4547
4722
|
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
}
|
|
4723
|
+
.card-row:last-child {
|
|
4724
|
+
margin-bottom: 0;
|
|
4725
|
+
}
|
|
4552
4726
|
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
}
|
|
4727
|
+
.card-label {
|
|
4728
|
+
color: var(--text-secondary);
|
|
4729
|
+
}
|
|
4557
4730
|
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4731
|
+
.card-value {
|
|
4732
|
+
color: var(--text-primary);
|
|
4733
|
+
font-family: 'JetBrains Mono', monospace;
|
|
4734
|
+
font-weight: 500;
|
|
4735
|
+
}
|
|
4561
4736
|
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4737
|
+
.identity-badge {
|
|
4738
|
+
display: inline-flex;
|
|
4739
|
+
align-items: center;
|
|
4740
|
+
gap: 4px;
|
|
4741
|
+
padding: 2px 6px;
|
|
4742
|
+
background-color: rgba(88, 166, 255, 0.15);
|
|
4743
|
+
color: var(--blue);
|
|
4744
|
+
border-radius: 3px;
|
|
4745
|
+
font-size: 10px;
|
|
4746
|
+
font-weight: 600;
|
|
4747
|
+
text-transform: uppercase;
|
|
4748
|
+
}
|
|
4565
4749
|
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4750
|
+
.trust-tier-badge {
|
|
4751
|
+
display: inline-flex;
|
|
4752
|
+
align-items: center;
|
|
4753
|
+
gap: 4px;
|
|
4754
|
+
padding: 2px 6px;
|
|
4755
|
+
background-color: rgba(63, 185, 80, 0.15);
|
|
4756
|
+
color: var(--green);
|
|
4757
|
+
border-radius: 3px;
|
|
4758
|
+
font-size: 10px;
|
|
4759
|
+
font-weight: 600;
|
|
4760
|
+
}
|
|
4571
4761
|
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
border-radius: 4px;
|
|
4579
|
-
}
|
|
4762
|
+
.truncated {
|
|
4763
|
+
max-width: 200px;
|
|
4764
|
+
overflow: hidden;
|
|
4765
|
+
text-overflow: ellipsis;
|
|
4766
|
+
white-space: nowrap;
|
|
4767
|
+
}
|
|
4580
4768
|
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
}
|
|
4769
|
+
/* Row 3: SHR & Activity */
|
|
4770
|
+
.main-panels {
|
|
4771
|
+
display: grid;
|
|
4772
|
+
grid-template-columns: 1fr 1fr;
|
|
4773
|
+
gap: 16px;
|
|
4774
|
+
min-height: 400px;
|
|
4775
|
+
}
|
|
4589
4776
|
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4777
|
+
.panel {
|
|
4778
|
+
background-color: var(--surface);
|
|
4779
|
+
border: 1px solid var(--border);
|
|
4780
|
+
border-radius: 8px;
|
|
4781
|
+
display: flex;
|
|
4782
|
+
flex-direction: column;
|
|
4783
|
+
overflow: hidden;
|
|
4784
|
+
}
|
|
4594
4785
|
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4786
|
+
.panel-header {
|
|
4787
|
+
padding: 16px 20px;
|
|
4788
|
+
border-bottom: 1px solid var(--border);
|
|
4789
|
+
display: flex;
|
|
4790
|
+
justify-content: space-between;
|
|
4791
|
+
align-items: center;
|
|
4792
|
+
}
|
|
4598
4793
|
|
|
4599
|
-
|
|
4794
|
+
.panel-title {
|
|
4795
|
+
font-size: 14px;
|
|
4796
|
+
font-weight: 600;
|
|
4797
|
+
color: var(--text-primary);
|
|
4798
|
+
}
|
|
4600
4799
|
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4800
|
+
.panel-action {
|
|
4801
|
+
background: none;
|
|
4802
|
+
border: none;
|
|
4803
|
+
color: var(--blue);
|
|
4804
|
+
cursor: pointer;
|
|
4805
|
+
font-size: 12px;
|
|
4806
|
+
padding: 0;
|
|
4807
|
+
font-weight: 500;
|
|
4808
|
+
transition: color 0.2s;
|
|
4809
|
+
}
|
|
4608
4810
|
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
font-size: 12px;
|
|
4613
|
-
font-weight: 600;
|
|
4614
|
-
text-transform: uppercase;
|
|
4615
|
-
letter-spacing: 0.5px;
|
|
4616
|
-
color: var(--text-secondary);
|
|
4617
|
-
display: flex;
|
|
4618
|
-
align-items: center;
|
|
4619
|
-
gap: 8px;
|
|
4620
|
-
}
|
|
4811
|
+
.panel-action:hover {
|
|
4812
|
+
color: #79c0ff;
|
|
4813
|
+
}
|
|
4621
4814
|
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
grid-template-columns: 1fr 1fr;
|
|
4628
|
-
gap: 12px;
|
|
4629
|
-
}
|
|
4815
|
+
.panel-content {
|
|
4816
|
+
flex: 1;
|
|
4817
|
+
overflow-y: auto;
|
|
4818
|
+
padding: 20px;
|
|
4819
|
+
}
|
|
4630
4820
|
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
gap: 8px;
|
|
4639
|
-
}
|
|
4821
|
+
/* SHR Viewer */
|
|
4822
|
+
.shr-json {
|
|
4823
|
+
font-family: 'JetBrains Mono', monospace;
|
|
4824
|
+
font-size: 12px;
|
|
4825
|
+
line-height: 1.6;
|
|
4826
|
+
color: var(--text-secondary);
|
|
4827
|
+
}
|
|
4640
4828
|
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4829
|
+
.shr-section {
|
|
4830
|
+
margin-bottom: 12px;
|
|
4831
|
+
}
|
|
4644
4832
|
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4833
|
+
.shr-section-header {
|
|
4834
|
+
display: flex;
|
|
4835
|
+
align-items: center;
|
|
4836
|
+
gap: 8px;
|
|
4837
|
+
cursor: pointer;
|
|
4838
|
+
font-weight: 600;
|
|
4839
|
+
color: var(--text-primary);
|
|
4840
|
+
padding: 8px;
|
|
4841
|
+
background-color: var(--bg);
|
|
4842
|
+
border-radius: 4px;
|
|
4843
|
+
user-select: none;
|
|
4844
|
+
}
|
|
4652
4845
|
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
gap: 6px;
|
|
4657
|
-
font-size: 12px;
|
|
4658
|
-
font-weight: 600;
|
|
4659
|
-
}
|
|
4846
|
+
.shr-section-header:hover {
|
|
4847
|
+
background-color: var(--muted);
|
|
4848
|
+
}
|
|
4660
4849
|
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4850
|
+
.shr-toggle {
|
|
4851
|
+
width: 16px;
|
|
4852
|
+
height: 16px;
|
|
4853
|
+
display: flex;
|
|
4854
|
+
align-items: center;
|
|
4855
|
+
justify-content: center;
|
|
4856
|
+
font-size: 10px;
|
|
4857
|
+
transition: transform 0.2s;
|
|
4858
|
+
}
|
|
4664
4859
|
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4860
|
+
.shr-section.collapsed .shr-toggle {
|
|
4861
|
+
transform: rotate(-90deg);
|
|
4862
|
+
}
|
|
4668
4863
|
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4864
|
+
.shr-section-content {
|
|
4865
|
+
padding: 8px 16px;
|
|
4866
|
+
background-color: rgba(0, 0, 0, 0.2);
|
|
4867
|
+
border-radius: 4px;
|
|
4868
|
+
margin-top: 4px;
|
|
4869
|
+
}
|
|
4675
4870
|
|
|
4676
|
-
|
|
4871
|
+
.shr-section.collapsed .shr-section-content {
|
|
4872
|
+
display: none;
|
|
4873
|
+
}
|
|
4677
4874
|
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
bottom: 0;
|
|
4683
|
-
width: 0;
|
|
4684
|
-
background: var(--surface);
|
|
4685
|
-
border-left: 1px solid var(--border);
|
|
4686
|
-
z-index: 999;
|
|
4687
|
-
overflow-y: auto;
|
|
4688
|
-
transition: width 0.3s ease-out;
|
|
4689
|
-
display: flex;
|
|
4690
|
-
flex-direction: column;
|
|
4691
|
-
}
|
|
4875
|
+
.shr-item {
|
|
4876
|
+
display: flex;
|
|
4877
|
+
margin-bottom: 4px;
|
|
4878
|
+
}
|
|
4692
4879
|
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4880
|
+
.shr-key {
|
|
4881
|
+
color: var(--blue);
|
|
4882
|
+
margin-right: 8px;
|
|
4883
|
+
min-width: 120px;
|
|
4884
|
+
}
|
|
4696
4885
|
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
right: auto;
|
|
4701
|
-
left: 0;
|
|
4886
|
+
.shr-value {
|
|
4887
|
+
color: var(--green);
|
|
4888
|
+
word-break: break-all;
|
|
4702
4889
|
}
|
|
4703
|
-
}
|
|
4704
4890
|
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
flex: 0 0 auto;
|
|
4712
|
-
}
|
|
4891
|
+
/* Activity Feed */
|
|
4892
|
+
.activity-feed {
|
|
4893
|
+
display: flex;
|
|
4894
|
+
flex-direction: column;
|
|
4895
|
+
gap: 12px;
|
|
4896
|
+
}
|
|
4713
4897
|
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4898
|
+
.activity-item {
|
|
4899
|
+
padding: 12px;
|
|
4900
|
+
background-color: var(--bg);
|
|
4901
|
+
border-left: 2px solid var(--border);
|
|
4902
|
+
border-radius: 4px;
|
|
4903
|
+
font-size: 12px;
|
|
4904
|
+
}
|
|
4721
4905
|
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
color: var(--text-secondary);
|
|
4726
|
-
cursor: pointer;
|
|
4727
|
-
font-size: 18px;
|
|
4728
|
-
padding: 0;
|
|
4729
|
-
display: flex;
|
|
4730
|
-
align-items: center;
|
|
4731
|
-
justify-content: center;
|
|
4732
|
-
}
|
|
4906
|
+
.activity-item.tool-call {
|
|
4907
|
+
border-left-color: var(--blue);
|
|
4908
|
+
}
|
|
4733
4909
|
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4910
|
+
.activity-item.context-gate {
|
|
4911
|
+
border-left-color: var(--amber);
|
|
4912
|
+
}
|
|
4737
4913
|
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
}
|
|
4914
|
+
.activity-item.injection {
|
|
4915
|
+
border-left-color: var(--red);
|
|
4916
|
+
}
|
|
4742
4917
|
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
display: flex;
|
|
4747
|
-
flex-direction: column;
|
|
4748
|
-
gap: 10px;
|
|
4749
|
-
}
|
|
4918
|
+
.activity-item.protection {
|
|
4919
|
+
border-left-color: var(--green);
|
|
4920
|
+
}
|
|
4750
4921
|
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4922
|
+
.activity-type {
|
|
4923
|
+
font-weight: 600;
|
|
4924
|
+
color: var(--text-primary);
|
|
4925
|
+
margin-bottom: 4px;
|
|
4926
|
+
text-transform: uppercase;
|
|
4927
|
+
font-size: 11px;
|
|
4928
|
+
letter-spacing: 0.5px;
|
|
4929
|
+
}
|
|
4756
4930
|
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
}
|
|
4931
|
+
.activity-content {
|
|
4932
|
+
color: var(--text-secondary);
|
|
4933
|
+
font-family: 'JetBrains Mono', monospace;
|
|
4934
|
+
margin-bottom: 4px;
|
|
4935
|
+
word-break: break-all;
|
|
4936
|
+
}
|
|
4764
4937
|
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
width: 28px;
|
|
4770
|
-
height: 20px;
|
|
4771
|
-
font-size: 9px;
|
|
4772
|
-
font-weight: 700;
|
|
4773
|
-
border-radius: 3px;
|
|
4774
|
-
text-transform: uppercase;
|
|
4775
|
-
color: white;
|
|
4776
|
-
}
|
|
4938
|
+
.activity-time {
|
|
4939
|
+
font-size: 11px;
|
|
4940
|
+
color: var(--text-secondary);
|
|
4941
|
+
}
|
|
4777
4942
|
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4943
|
+
.empty-state {
|
|
4944
|
+
display: flex;
|
|
4945
|
+
align-items: center;
|
|
4946
|
+
justify-content: center;
|
|
4947
|
+
height: 100%;
|
|
4948
|
+
color: var(--text-secondary);
|
|
4949
|
+
font-size: 13px;
|
|
4950
|
+
}
|
|
4781
4951
|
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4952
|
+
/* Row 4: Handshake History */
|
|
4953
|
+
.handshake-table {
|
|
4954
|
+
background-color: var(--surface);
|
|
4955
|
+
border: 1px solid var(--border);
|
|
4956
|
+
border-radius: 8px;
|
|
4957
|
+
overflow: hidden;
|
|
4958
|
+
}
|
|
4785
4959
|
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4960
|
+
.table-header {
|
|
4961
|
+
display: grid;
|
|
4962
|
+
grid-template-columns: 2fr 1fr 1fr 1fr 1.5fr 1.5fr;
|
|
4963
|
+
gap: 16px;
|
|
4964
|
+
padding: 16px 20px;
|
|
4965
|
+
border-bottom: 1px solid var(--border);
|
|
4966
|
+
background-color: var(--bg);
|
|
4967
|
+
font-size: 12px;
|
|
4968
|
+
font-weight: 600;
|
|
4969
|
+
color: var(--text-secondary);
|
|
4970
|
+
text-transform: uppercase;
|
|
4971
|
+
letter-spacing: 0.5px;
|
|
4972
|
+
}
|
|
4790
4973
|
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
font-size: 11px;
|
|
4796
|
-
font-family: var(--mono);
|
|
4797
|
-
color: var(--text-secondary);
|
|
4798
|
-
}
|
|
4974
|
+
.table-rows {
|
|
4975
|
+
max-height: 300px;
|
|
4976
|
+
overflow-y: auto;
|
|
4977
|
+
}
|
|
4799
4978
|
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4979
|
+
.table-row {
|
|
4980
|
+
display: grid;
|
|
4981
|
+
grid-template-columns: 2fr 1fr 1fr 1fr 1.5fr 1.5fr;
|
|
4982
|
+
gap: 16px;
|
|
4983
|
+
padding: 14px 20px;
|
|
4984
|
+
border-bottom: 1px solid var(--border);
|
|
4985
|
+
align-items: center;
|
|
4986
|
+
font-size: 12px;
|
|
4987
|
+
cursor: pointer;
|
|
4988
|
+
transition: background-color 0.2s;
|
|
4989
|
+
}
|
|
4807
4990
|
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
transition: width 0.1s linear;
|
|
4812
|
-
}
|
|
4991
|
+
.table-row:hover {
|
|
4992
|
+
background-color: var(--bg);
|
|
4993
|
+
}
|
|
4813
4994
|
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4995
|
+
.table-row:last-child {
|
|
4996
|
+
border-bottom: none;
|
|
4997
|
+
}
|
|
4817
4998
|
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4999
|
+
.table-cell {
|
|
5000
|
+
color: var(--text-secondary);
|
|
5001
|
+
font-family: 'JetBrains Mono', monospace;
|
|
5002
|
+
}
|
|
4822
5003
|
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
border-radius: var(--radius);
|
|
4828
|
-
font-size: 12px;
|
|
4829
|
-
font-weight: 600;
|
|
4830
|
-
cursor: pointer;
|
|
4831
|
-
transition: all 0.15s;
|
|
4832
|
-
font-family: var(--sans);
|
|
4833
|
-
}
|
|
5004
|
+
.table-cell.strong {
|
|
5005
|
+
color: var(--text-primary);
|
|
5006
|
+
font-weight: 500;
|
|
5007
|
+
}
|
|
4834
5008
|
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
5009
|
+
.table-empty {
|
|
5010
|
+
padding: 40px 20px;
|
|
5011
|
+
text-align: center;
|
|
5012
|
+
color: var(--text-secondary);
|
|
5013
|
+
font-size: 13px;
|
|
5014
|
+
}
|
|
4839
5015
|
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
5016
|
+
/* Pending Overlay */
|
|
5017
|
+
.pending-overlay {
|
|
5018
|
+
position: fixed;
|
|
5019
|
+
top: 0;
|
|
5020
|
+
right: -400px;
|
|
5021
|
+
width: 400px;
|
|
5022
|
+
height: 100vh;
|
|
5023
|
+
background-color: var(--surface);
|
|
5024
|
+
border-left: 1px solid var(--border);
|
|
5025
|
+
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3);
|
|
5026
|
+
z-index: 200;
|
|
5027
|
+
transition: right 0.3s ease;
|
|
5028
|
+
display: flex;
|
|
5029
|
+
flex-direction: column;
|
|
5030
|
+
overflow-y: auto;
|
|
5031
|
+
}
|
|
4843
5032
|
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
}
|
|
5033
|
+
.pending-overlay.show {
|
|
5034
|
+
right: 0;
|
|
5035
|
+
}
|
|
4848
5036
|
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
5037
|
+
.pending-header {
|
|
5038
|
+
padding: 16px 20px;
|
|
5039
|
+
border-bottom: 1px solid var(--border);
|
|
5040
|
+
font-weight: 600;
|
|
5041
|
+
color: var(--text-primary);
|
|
5042
|
+
}
|
|
4852
5043
|
|
|
4853
|
-
|
|
5044
|
+
.pending-items {
|
|
5045
|
+
flex: 1;
|
|
5046
|
+
overflow-y: auto;
|
|
5047
|
+
padding: 16px;
|
|
5048
|
+
}
|
|
4854
5049
|
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
max-height: 240px;
|
|
4863
|
-
z-index: 500;
|
|
4864
|
-
display: flex;
|
|
4865
|
-
flex-direction: column;
|
|
4866
|
-
transition: max-height 0.3s ease-out;
|
|
4867
|
-
}
|
|
5050
|
+
.pending-item {
|
|
5051
|
+
background-color: var(--bg);
|
|
5052
|
+
border: 1px solid var(--border);
|
|
5053
|
+
border-radius: 6px;
|
|
5054
|
+
padding: 16px;
|
|
5055
|
+
margin-bottom: 12px;
|
|
5056
|
+
}
|
|
4868
5057
|
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
5058
|
+
.pending-title {
|
|
5059
|
+
font-weight: 600;
|
|
5060
|
+
color: var(--text-primary);
|
|
5061
|
+
margin-bottom: 8px;
|
|
5062
|
+
word-break: break-word;
|
|
5063
|
+
}
|
|
4872
5064
|
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
font-size: 12px;
|
|
4880
|
-
font-weight: 600;
|
|
4881
|
-
text-transform: uppercase;
|
|
4882
|
-
letter-spacing: 0.5px;
|
|
4883
|
-
color: var(--text-secondary);
|
|
4884
|
-
flex: 0 0 auto;
|
|
4885
|
-
}
|
|
5065
|
+
.pending-countdown {
|
|
5066
|
+
font-size: 12px;
|
|
5067
|
+
color: var(--amber);
|
|
5068
|
+
margin-bottom: 12px;
|
|
5069
|
+
font-weight: 500;
|
|
5070
|
+
}
|
|
4886
5071
|
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
5072
|
+
.pending-actions {
|
|
5073
|
+
display: flex;
|
|
5074
|
+
gap: 8px;
|
|
5075
|
+
}
|
|
4890
5076
|
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
5077
|
+
.pending-btn {
|
|
5078
|
+
flex: 1;
|
|
5079
|
+
padding: 8px 12px;
|
|
5080
|
+
border: none;
|
|
5081
|
+
border-radius: 4px;
|
|
5082
|
+
font-size: 12px;
|
|
5083
|
+
font-weight: 600;
|
|
5084
|
+
cursor: pointer;
|
|
5085
|
+
transition: background-color 0.2s;
|
|
5086
|
+
}
|
|
4894
5087
|
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
display: flex;
|
|
4900
|
-
flex-direction: column;
|
|
4901
|
-
gap: 10px;
|
|
4902
|
-
}
|
|
5088
|
+
.pending-approve {
|
|
5089
|
+
background-color: var(--green);
|
|
5090
|
+
color: var(--bg);
|
|
5091
|
+
}
|
|
4903
5092
|
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
border-left: 2px solid var(--red);
|
|
4908
|
-
border-radius: 4px;
|
|
4909
|
-
font-size: 11px;
|
|
4910
|
-
color: var(--text-secondary);
|
|
4911
|
-
}
|
|
5093
|
+
.pending-approve:hover {
|
|
5094
|
+
background-color: #3fa040;
|
|
5095
|
+
}
|
|
4912
5096
|
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
}
|
|
5097
|
+
.pending-deny {
|
|
5098
|
+
background-color: var(--red);
|
|
5099
|
+
color: var(--bg);
|
|
5100
|
+
}
|
|
4918
5101
|
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
color: var(--text-secondary);
|
|
4923
|
-
font-size: 12px;
|
|
4924
|
-
}
|
|
5102
|
+
.pending-deny:hover {
|
|
5103
|
+
background-color: #e03c3c;
|
|
5104
|
+
}
|
|
4925
5105
|
|
|
4926
|
-
|
|
5106
|
+
/* Threat Panel */
|
|
5107
|
+
.threat-panel {
|
|
5108
|
+
background-color: var(--surface);
|
|
5109
|
+
border: 1px solid var(--border);
|
|
5110
|
+
border-radius: 8px;
|
|
5111
|
+
margin-top: 20px;
|
|
5112
|
+
overflow: hidden;
|
|
5113
|
+
}
|
|
4927
5114
|
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
5115
|
+
.threat-header {
|
|
5116
|
+
padding: 16px 20px;
|
|
5117
|
+
border-bottom: 1px solid var(--border);
|
|
5118
|
+
display: flex;
|
|
5119
|
+
justify-content: space-between;
|
|
5120
|
+
align-items: center;
|
|
5121
|
+
cursor: pointer;
|
|
5122
|
+
user-select: none;
|
|
5123
|
+
}
|
|
4931
5124
|
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
5125
|
+
.threat-title {
|
|
5126
|
+
font-weight: 600;
|
|
5127
|
+
color: var(--text-primary);
|
|
5128
|
+
}
|
|
4935
5129
|
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
5130
|
+
.threat-toggle {
|
|
5131
|
+
font-size: 10px;
|
|
5132
|
+
color: var(--text-secondary);
|
|
5133
|
+
transition: transform 0.2s;
|
|
5134
|
+
}
|
|
4940
5135
|
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
5136
|
+
.threat-panel.collapsed .threat-toggle {
|
|
5137
|
+
transform: rotate(-90deg);
|
|
5138
|
+
}
|
|
4944
5139
|
|
|
4945
|
-
|
|
5140
|
+
.threat-content {
|
|
5141
|
+
padding: 16px 20px;
|
|
5142
|
+
max-height: 300px;
|
|
5143
|
+
overflow-y: auto;
|
|
5144
|
+
}
|
|
4946
5145
|
|
|
4947
|
-
|
|
4948
|
-
.protection-sidebar {
|
|
5146
|
+
.threat-panel.collapsed .threat-content {
|
|
4949
5147
|
display: none;
|
|
4950
5148
|
}
|
|
4951
5149
|
|
|
4952
|
-
.
|
|
4953
|
-
|
|
5150
|
+
.threat-alert {
|
|
5151
|
+
background-color: rgba(248, 81, 73, 0.1);
|
|
5152
|
+
border: 1px solid var(--red);
|
|
5153
|
+
border-radius: 4px;
|
|
5154
|
+
padding: 12px;
|
|
5155
|
+
margin-bottom: 8px;
|
|
5156
|
+
font-size: 12px;
|
|
4954
5157
|
}
|
|
4955
|
-
}
|
|
4956
5158
|
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
padding: 0 12px;
|
|
4960
|
-
gap: 12px;
|
|
4961
|
-
height: 48px;
|
|
5159
|
+
.threat-alert:last-child {
|
|
5160
|
+
margin-bottom: 0;
|
|
4962
5161
|
}
|
|
4963
5162
|
|
|
4964
|
-
.
|
|
4965
|
-
font-
|
|
5163
|
+
.threat-type {
|
|
5164
|
+
font-weight: 600;
|
|
5165
|
+
color: var(--red);
|
|
5166
|
+
margin-bottom: 4px;
|
|
5167
|
+
text-transform: uppercase;
|
|
5168
|
+
font-size: 10px;
|
|
5169
|
+
letter-spacing: 0.5px;
|
|
4966
5170
|
}
|
|
4967
5171
|
|
|
4968
|
-
.
|
|
4969
|
-
|
|
5172
|
+
.threat-message {
|
|
5173
|
+
color: var(--text-secondary);
|
|
4970
5174
|
}
|
|
4971
5175
|
|
|
4972
|
-
|
|
4973
|
-
|
|
5176
|
+
/* Scrollbar */
|
|
5177
|
+
::-webkit-scrollbar {
|
|
5178
|
+
width: 8px;
|
|
4974
5179
|
}
|
|
4975
5180
|
|
|
4976
|
-
|
|
4977
|
-
|
|
5181
|
+
::-webkit-scrollbar-track {
|
|
5182
|
+
background-color: transparent;
|
|
4978
5183
|
}
|
|
4979
5184
|
|
|
4980
|
-
|
|
4981
|
-
|
|
5185
|
+
::-webkit-scrollbar-thumb {
|
|
5186
|
+
background-color: var(--border);
|
|
5187
|
+
border-radius: 4px;
|
|
4982
5188
|
}
|
|
4983
5189
|
|
|
4984
|
-
|
|
4985
|
-
|
|
5190
|
+
::-webkit-scrollbar-thumb:hover {
|
|
5191
|
+
background-color: var(--text-secondary);
|
|
4986
5192
|
}
|
|
4987
|
-
|
|
4988
|
-
|
|
5193
|
+
|
|
5194
|
+
/* Responsive */
|
|
5195
|
+
@media (max-width: 1400px) {
|
|
5196
|
+
.sovereignty-layers {
|
|
5197
|
+
grid-template-columns: repeat(2, 1fr);
|
|
5198
|
+
}
|
|
5199
|
+
|
|
5200
|
+
.main-panels {
|
|
5201
|
+
grid-template-columns: 1fr;
|
|
5202
|
+
}
|
|
5203
|
+
|
|
5204
|
+
.pending-overlay {
|
|
5205
|
+
width: 100%;
|
|
5206
|
+
right: -100%;
|
|
5207
|
+
}
|
|
5208
|
+
}
|
|
5209
|
+
|
|
5210
|
+
@media (max-width: 768px) {
|
|
5211
|
+
.status-bar {
|
|
5212
|
+
flex-wrap: wrap;
|
|
5213
|
+
height: auto;
|
|
5214
|
+
padding: 12px;
|
|
5215
|
+
gap: 12px;
|
|
5216
|
+
}
|
|
5217
|
+
|
|
5218
|
+
.status-bar-center {
|
|
5219
|
+
order: 3;
|
|
5220
|
+
flex-basis: 100%;
|
|
5221
|
+
}
|
|
5222
|
+
|
|
5223
|
+
.main-content {
|
|
5224
|
+
margin-top: auto;
|
|
5225
|
+
}
|
|
5226
|
+
|
|
5227
|
+
.info-cards {
|
|
5228
|
+
grid-template-columns: 1fr;
|
|
5229
|
+
}
|
|
5230
|
+
|
|
5231
|
+
.table-header,
|
|
5232
|
+
.table-row {
|
|
5233
|
+
grid-template-columns: 1fr;
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
</style>
|
|
4989
5237
|
</head>
|
|
4990
5238
|
<body>
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
<div class="status-bar">
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
<div class="status-bar-center">
|
|
4999
|
-
<div class="sovereignty-badge">
|
|
5000
|
-
<div class="sovereignty-score" id="sovereigntyScore">85</div>
|
|
5001
|
-
<span>Sovereignty Health</span>
|
|
5002
|
-
</div>
|
|
5003
|
-
</div>
|
|
5004
|
-
<div class="status-bar-right">
|
|
5005
|
-
<div class="protections-indicator">
|
|
5006
|
-
<span class="count" id="activeProtections">6</span>/6 protections
|
|
5007
|
-
</div>
|
|
5008
|
-
<div class="uptime">
|
|
5009
|
-
<span id="uptimeText">\u2014</span>
|
|
5010
|
-
</div>
|
|
5011
|
-
<div class="status-dot" id="statusDot"></div>
|
|
5012
|
-
<div class="pending-badge hidden" id="pendingBadge">0</div>
|
|
5013
|
-
</div>
|
|
5014
|
-
</div>
|
|
5015
|
-
|
|
5016
|
-
<!-- Main Layout -->
|
|
5017
|
-
<div class="main-container">
|
|
5018
|
-
<!-- Activity Feed -->
|
|
5019
|
-
<div class="activity-feed">
|
|
5020
|
-
<div class="feed-header">
|
|
5021
|
-
<div class="feed-header-dot"></div>
|
|
5022
|
-
Live Activity
|
|
5023
|
-
</div>
|
|
5024
|
-
<div class="activity-list" id="activityList">
|
|
5025
|
-
<div class="activity-empty">
|
|
5026
|
-
<div class="activity-empty-icon">\u2192</div>
|
|
5027
|
-
<div class="activity-empty-text">Waiting for activity...</div>
|
|
5239
|
+
<!-- Status Bar -->
|
|
5240
|
+
<div class="status-bar">
|
|
5241
|
+
<div class="status-bar-left">
|
|
5242
|
+
<div class="logo-icon">\u25C6</div>
|
|
5243
|
+
<div class="logo-info">
|
|
5244
|
+
<div class="logo-title">SANCTUARY</div>
|
|
5245
|
+
<div class="logo-version">v${options.serverVersion}</div>
|
|
5028
5246
|
</div>
|
|
5029
5247
|
</div>
|
|
5030
|
-
</div>
|
|
5031
5248
|
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5249
|
+
<div class="status-bar-center">
|
|
5250
|
+
<div id="sovereignty-badge" class="sovereignty-badge">
|
|
5251
|
+
<span>Sovereignty Health:</span>
|
|
5252
|
+
<span class="sovereignty-score" id="sovereignty-score">\u2014</span>
|
|
5253
|
+
<span>/ 100</span>
|
|
5254
|
+
</div>
|
|
5036
5255
|
</div>
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
<
|
|
5041
|
-
<
|
|
5042
|
-
|
|
5256
|
+
|
|
5257
|
+
<div class="status-bar-right">
|
|
5258
|
+
<div class="status-item">
|
|
5259
|
+
<strong id="protections-count">\u2014</strong>
|
|
5260
|
+
<span>Protections</span>
|
|
5261
|
+
</div>
|
|
5262
|
+
<div class="status-item">
|
|
5263
|
+
<strong id="uptime-value">\u2014</strong>
|
|
5264
|
+
<span>Uptime</span>
|
|
5043
5265
|
</div>
|
|
5266
|
+
<div class="status-dot" id="connection-status"></div>
|
|
5267
|
+
<div id="pending-item-badge" class="pending-badge" style="display: none;">
|
|
5268
|
+
<span>\u23F3</span>
|
|
5269
|
+
<span id="pending-count">0</span>
|
|
5270
|
+
</div>
|
|
5271
|
+
</div>
|
|
5272
|
+
</div>
|
|
5044
5273
|
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5274
|
+
<!-- Main Content -->
|
|
5275
|
+
<div class="main-content">
|
|
5276
|
+
<div class="grid">
|
|
5277
|
+
<!-- Row 1: Sovereignty Layers -->
|
|
5278
|
+
<div class="sovereignty-layers" id="sovereignty-layers">
|
|
5279
|
+
<div class="layer-card" data-layer="l1">
|
|
5280
|
+
<div class="layer-name">Layer 1</div>
|
|
5281
|
+
<div class="layer-title">Cognitive Sovereignty</div>
|
|
5282
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l1-status">\u2014</span></div>
|
|
5283
|
+
<div class="layer-detail" id="l1-detail">Loading...</div>
|
|
5284
|
+
</div>
|
|
5285
|
+
<div class="layer-card" data-layer="l2">
|
|
5286
|
+
<div class="layer-name">Layer 2</div>
|
|
5287
|
+
<div class="layer-title">Operational Isolation</div>
|
|
5288
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l2-status">\u2014</span></div>
|
|
5289
|
+
<div class="layer-detail" id="l2-detail">Loading...</div>
|
|
5290
|
+
</div>
|
|
5291
|
+
<div class="layer-card" data-layer="l3">
|
|
5292
|
+
<div class="layer-name">Layer 3</div>
|
|
5293
|
+
<div class="layer-title">Selective Disclosure</div>
|
|
5294
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l3-status">\u2014</span></div>
|
|
5295
|
+
<div class="layer-detail" id="l3-detail">Loading...</div>
|
|
5296
|
+
</div>
|
|
5297
|
+
<div class="layer-card" data-layer="l4">
|
|
5298
|
+
<div class="layer-name">Layer 4</div>
|
|
5299
|
+
<div class="layer-title">Verifiable Reputation</div>
|
|
5300
|
+
<div class="layer-status"><span>\u25CF</span> <span id="l4-status">\u2014</span></div>
|
|
5301
|
+
<div class="layer-detail" id="l4-detail">Loading...</div>
|
|
5302
|
+
</div>
|
|
5050
5303
|
</div>
|
|
5051
5304
|
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
<div class="
|
|
5055
|
-
|
|
5056
|
-
|
|
5305
|
+
<!-- Row 2: Info Cards -->
|
|
5306
|
+
<div class="info-cards">
|
|
5307
|
+
<div class="info-card">
|
|
5308
|
+
<div class="card-header">Identity</div>
|
|
5309
|
+
<div class="card-row">
|
|
5310
|
+
<span class="card-label">Primary</span>
|
|
5311
|
+
<span class="card-value" id="identity-label">\u2014</span>
|
|
5312
|
+
</div>
|
|
5313
|
+
<div class="card-row">
|
|
5314
|
+
<span class="card-label">DID</span>
|
|
5315
|
+
<span class="card-value truncated" id="identity-did" title="">\u2014</span>
|
|
5316
|
+
</div>
|
|
5317
|
+
<div class="card-row">
|
|
5318
|
+
<span class="card-label">Public Key</span>
|
|
5319
|
+
<span class="card-value truncated" id="identity-pubkey" title="">\u2014</span>
|
|
5320
|
+
</div>
|
|
5321
|
+
<div class="card-row">
|
|
5322
|
+
<span class="card-label">Type</span>
|
|
5323
|
+
<span class="identity-badge">Ed25519</span>
|
|
5324
|
+
</div>
|
|
5325
|
+
<div class="card-row">
|
|
5326
|
+
<span class="card-label">Created</span>
|
|
5327
|
+
<span class="card-value" id="identity-created">\u2014</span>
|
|
5328
|
+
</div>
|
|
5329
|
+
<div class="card-row">
|
|
5330
|
+
<span class="card-label">Identities</span>
|
|
5331
|
+
<span class="card-value" id="identity-count">\u2014</span>
|
|
5332
|
+
</div>
|
|
5333
|
+
</div>
|
|
5334
|
+
|
|
5335
|
+
<div class="info-card">
|
|
5336
|
+
<div class="card-header">Handshakes</div>
|
|
5337
|
+
<div class="card-row">
|
|
5338
|
+
<span class="card-label">Total</span>
|
|
5339
|
+
<span class="card-value" id="handshake-count">\u2014</span>
|
|
5340
|
+
</div>
|
|
5341
|
+
<div class="card-row">
|
|
5342
|
+
<span class="card-label">Latest Peer</span>
|
|
5343
|
+
<span class="card-value truncated" id="handshake-latest">\u2014</span>
|
|
5344
|
+
</div>
|
|
5345
|
+
<div class="card-row">
|
|
5346
|
+
<span class="card-label">Trust Tier</span>
|
|
5347
|
+
<span class="trust-tier-badge" id="handshake-tier">Unverified</span>
|
|
5348
|
+
</div>
|
|
5349
|
+
<div class="card-row">
|
|
5350
|
+
<span class="card-label">Timestamp</span>
|
|
5351
|
+
<span class="card-value" id="handshake-time">\u2014</span>
|
|
5352
|
+
</div>
|
|
5353
|
+
</div>
|
|
5354
|
+
|
|
5355
|
+
<div class="info-card">
|
|
5356
|
+
<div class="card-header">Reputation</div>
|
|
5357
|
+
<div class="card-row">
|
|
5358
|
+
<span class="card-label">Weighted Score</span>
|
|
5359
|
+
<span class="card-value" id="reputation-score">\u2014</span>
|
|
5360
|
+
</div>
|
|
5361
|
+
<div class="card-row">
|
|
5362
|
+
<span class="card-label">Attestations</span>
|
|
5363
|
+
<span class="card-value" id="reputation-attestations">\u2014</span>
|
|
5364
|
+
</div>
|
|
5365
|
+
<div class="card-row">
|
|
5366
|
+
<span class="card-label">Verified Sovereign</span>
|
|
5367
|
+
<span class="card-value" id="reputation-verified">\u2014</span>
|
|
5368
|
+
</div>
|
|
5369
|
+
<div class="card-row">
|
|
5370
|
+
<span class="card-label">Verified Degraded</span>
|
|
5371
|
+
<span class="card-value" id="reputation-degraded">\u2014</span>
|
|
5372
|
+
</div>
|
|
5373
|
+
<div class="card-row">
|
|
5374
|
+
<span class="card-label">Unverified</span>
|
|
5375
|
+
<span class="card-value" id="reputation-unverified">\u2014</span>
|
|
5376
|
+
</div>
|
|
5377
|
+
</div>
|
|
5057
5378
|
</div>
|
|
5058
5379
|
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
<div class="
|
|
5062
|
-
|
|
5063
|
-
|
|
5380
|
+
<!-- Row 3: SHR & Activity -->
|
|
5381
|
+
<div class="main-panels">
|
|
5382
|
+
<div class="panel">
|
|
5383
|
+
<div class="panel-header">
|
|
5384
|
+
<div class="panel-title">Sovereignty Health Report</div>
|
|
5385
|
+
<button class="panel-action" id="copy-shr-btn">Copy JSON</button>
|
|
5386
|
+
</div>
|
|
5387
|
+
<div class="panel-content">
|
|
5388
|
+
<div class="shr-json" id="shr-viewer">
|
|
5389
|
+
<div class="empty-state">Loading SHR...</div>
|
|
5390
|
+
</div>
|
|
5391
|
+
</div>
|
|
5392
|
+
</div>
|
|
5393
|
+
|
|
5394
|
+
<div class="panel">
|
|
5395
|
+
<div class="panel-header">
|
|
5396
|
+
<div class="panel-title">Activity Feed</div>
|
|
5397
|
+
</div>
|
|
5398
|
+
<div class="panel-content">
|
|
5399
|
+
<div id="activity-feed" class="activity-feed">
|
|
5400
|
+
<div class="empty-state">Waiting for activity...</div>
|
|
5401
|
+
</div>
|
|
5402
|
+
</div>
|
|
5403
|
+
</div>
|
|
5064
5404
|
</div>
|
|
5065
5405
|
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
<div class="
|
|
5069
|
-
|
|
5070
|
-
|
|
5406
|
+
<!-- Row 4: Handshake History -->
|
|
5407
|
+
<div class="handshake-table">
|
|
5408
|
+
<div class="table-header">
|
|
5409
|
+
<div>Counterparty</div>
|
|
5410
|
+
<div>Trust Tier</div>
|
|
5411
|
+
<div>Sovereignty</div>
|
|
5412
|
+
<div>Verified</div>
|
|
5413
|
+
<div>Completed</div>
|
|
5414
|
+
<div>Expires</div>
|
|
5415
|
+
</div>
|
|
5416
|
+
<div class="table-rows" id="handshake-table">
|
|
5417
|
+
<div class="table-empty">No handshakes completed yet</div>
|
|
5418
|
+
</div>
|
|
5071
5419
|
</div>
|
|
5072
5420
|
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
<div class="
|
|
5076
|
-
|
|
5077
|
-
|
|
5421
|
+
<!-- Threat Panel -->
|
|
5422
|
+
<div class="threat-panel collapsed">
|
|
5423
|
+
<div class="threat-header">
|
|
5424
|
+
<div class="threat-title">Security Threats</div>
|
|
5425
|
+
<div class="threat-toggle">\u25B6</div>
|
|
5426
|
+
</div>
|
|
5427
|
+
<div class="threat-content" id="threat-alerts">
|
|
5428
|
+
<div class="empty-state">No threats detected</div>
|
|
5429
|
+
</div>
|
|
5078
5430
|
</div>
|
|
5079
5431
|
</div>
|
|
5080
5432
|
</div>
|
|
5081
|
-
</div>
|
|
5082
5433
|
|
|
5083
|
-
<!-- Pending
|
|
5084
|
-
<div class="pending-overlay" id="
|
|
5085
|
-
|
|
5086
|
-
<div class="pending-
|
|
5087
|
-
<button class="pending-overlay-close" onclick="closePendingOverlay()">\xD7</button>
|
|
5088
|
-
</div>
|
|
5089
|
-
<div class="pending-list" id="pendingList"></div>
|
|
5090
|
-
</div>
|
|
5091
|
-
|
|
5092
|
-
<!-- Threat Panel (collapsible footer) -->
|
|
5093
|
-
<div class="threat-panel collapsed" id="threatPanel">
|
|
5094
|
-
<div class="threat-header" onclick="toggleThreatPanel()">
|
|
5095
|
-
<span class="threat-icon">\u26A0</span>
|
|
5096
|
-
Recent Threats
|
|
5097
|
-
<span id="threatCount" style="margin-left: auto; color: var(--red); font-weight: 700;">0</span>
|
|
5434
|
+
<!-- Pending Overlay -->
|
|
5435
|
+
<div class="pending-overlay" id="pending-overlay">
|
|
5436
|
+
<div class="pending-header">Pending Approvals</div>
|
|
5437
|
+
<div class="pending-items" id="pending-items"></div>
|
|
5098
5438
|
</div>
|
|
5099
|
-
<div class="threat-content" id="threatContent">
|
|
5100
|
-
<div class="threat-empty">No threats detected</div>
|
|
5101
|
-
</div>
|
|
5102
|
-
</div>
|
|
5103
5439
|
|
|
5104
|
-
<script>
|
|
5105
|
-
|
|
5106
|
-
|
|
5440
|
+
<script>
|
|
5441
|
+
// Constants
|
|
5442
|
+
const AUTH_TOKEN = '${options.authToken || ""}' || sessionStorage.getItem('authToken') || '';
|
|
5443
|
+
const TIMEOUT_SECONDS = ${options.timeoutSeconds};
|
|
5444
|
+
const API_BASE = '';
|
|
5445
|
+
|
|
5446
|
+
// State
|
|
5447
|
+
let apiState = {
|
|
5448
|
+
sovereignty: null,
|
|
5449
|
+
identity: null,
|
|
5450
|
+
handshakes: [],
|
|
5451
|
+
shr: null,
|
|
5452
|
+
status: null,
|
|
5453
|
+
};
|
|
5107
5454
|
|
|
5108
|
-
|
|
5455
|
+
let pendingRequests = new Map();
|
|
5456
|
+
let activityLog = [];
|
|
5457
|
+
const maxActivityItems = 50;
|
|
5109
5458
|
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5459
|
+
// Helpers
|
|
5460
|
+
function esc(text) {
|
|
5461
|
+
if (!text) return '';
|
|
5462
|
+
const div = document.createElement('div');
|
|
5463
|
+
div.textContent = text;
|
|
5464
|
+
return div.innerHTML;
|
|
5465
|
+
}
|
|
5466
|
+
|
|
5467
|
+
function formatTime(isoString) {
|
|
5468
|
+
if (!isoString) return '\u2014';
|
|
5469
|
+
const date = new Date(isoString);
|
|
5470
|
+
return date.toLocaleString('en-US', {
|
|
5471
|
+
month: 'short',
|
|
5472
|
+
day: 'numeric',
|
|
5473
|
+
hour: '2-digit',
|
|
5474
|
+
minute: '2-digit',
|
|
5475
|
+
});
|
|
5476
|
+
}
|
|
5116
5477
|
|
|
5117
|
-
|
|
5478
|
+
function truncate(str, len = 16) {
|
|
5479
|
+
if (!str) return '\u2014';
|
|
5480
|
+
if (str.length <= len) return str;
|
|
5481
|
+
return str.slice(0, len) + '...';
|
|
5482
|
+
}
|
|
5118
5483
|
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
let threatCount = 0;
|
|
5124
|
-
const pendingRequests = new Map();
|
|
5125
|
-
const activityItems = [];
|
|
5126
|
-
const threatItems = [];
|
|
5127
|
-
let sovereigntyScore = 85;
|
|
5128
|
-
let sessionRenewalTimer = null;
|
|
5484
|
+
function calculateSovereigntyScore(shr) {
|
|
5485
|
+
if (!shr || !shr.layers) return 0;
|
|
5486
|
+
const layers = shr.layers;
|
|
5487
|
+
let score = 100;
|
|
5129
5488
|
|
|
5130
|
-
|
|
5489
|
+
if (layers.l1?.status === 'degraded') score -= 20;
|
|
5490
|
+
if (layers.l1?.status === 'inactive') score -= 35;
|
|
5491
|
+
if (layers.l2?.status === 'degraded') score -= 15;
|
|
5492
|
+
if (layers.l2?.status === 'inactive') score -= 25;
|
|
5493
|
+
if (layers.l3?.status === 'degraded') score -= 15;
|
|
5494
|
+
if (layers.l3?.status === 'inactive') score -= 25;
|
|
5495
|
+
if (layers.l4?.status === 'degraded') score -= 10;
|
|
5496
|
+
if (layers.l4?.status === 'inactive') score -= 20;
|
|
5131
5497
|
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
if (AUTH_TOKEN) h['Authorization'] = 'Bearer ' + AUTH_TOKEN;
|
|
5135
|
-
return h;
|
|
5136
|
-
}
|
|
5498
|
+
return Math.max(0, Math.min(100, score));
|
|
5499
|
+
}
|
|
5137
5500
|
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5501
|
+
async function fetchAPI(endpoint) {
|
|
5502
|
+
try {
|
|
5503
|
+
const response = await fetch(API_BASE + endpoint, {
|
|
5504
|
+
headers: {
|
|
5505
|
+
'Authorization': 'Bearer ' + AUTH_TOKEN,
|
|
5506
|
+
},
|
|
5507
|
+
});
|
|
5143
5508
|
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5509
|
+
if (response.status === 401) {
|
|
5510
|
+
redirectToLogin();
|
|
5511
|
+
return null;
|
|
5512
|
+
}
|
|
5148
5513
|
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
// Schedule renewal at 80% of TTL
|
|
5160
|
-
if (sessionRenewalTimer) clearTimeout(sessionRenewalTimer);
|
|
5161
|
-
sessionRenewalTimer = setTimeout(function() {
|
|
5162
|
-
exchangeSession().then(function() { reconnectSSE(); });
|
|
5163
|
-
}, ttl * 800);
|
|
5164
|
-
} else if (resp.status === 401) {
|
|
5165
|
-
// Token invalid or expired \u2014 show non-destructive re-login overlay
|
|
5166
|
-
showSessionExpired();
|
|
5167
|
-
}
|
|
5168
|
-
} catch (e) {
|
|
5169
|
-
// Network error \u2014 retry in 30s
|
|
5170
|
-
if (sessionRenewalTimer) clearTimeout(sessionRenewalTimer);
|
|
5171
|
-
sessionRenewalTimer = setTimeout(function() {
|
|
5172
|
-
exchangeSession().then(function() { reconnectSSE(); });
|
|
5173
|
-
}, 30000);
|
|
5174
|
-
}
|
|
5175
|
-
}
|
|
5176
|
-
|
|
5177
|
-
function showSessionExpired() {
|
|
5178
|
-
// Clear stored token
|
|
5179
|
-
try { sessionStorage.removeItem('sanctuary_token'); } catch(_) {}
|
|
5180
|
-
// Redirect to login page
|
|
5181
|
-
document.cookie = 'sanctuary_session=; path=/; max-age=0';
|
|
5182
|
-
window.location.reload();
|
|
5183
|
-
}
|
|
5184
|
-
|
|
5185
|
-
// \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
|
|
5186
|
-
|
|
5187
|
-
function esc(s) {
|
|
5188
|
-
const d = document.createElement('div');
|
|
5189
|
-
d.textContent = String(s || '');
|
|
5190
|
-
return d.innerHTML;
|
|
5191
|
-
}
|
|
5192
|
-
|
|
5193
|
-
function closePendingOverlay() {
|
|
5194
|
-
document.getElementById('pendingOverlay').classList.remove('active');
|
|
5195
|
-
}
|
|
5196
|
-
|
|
5197
|
-
function toggleThreatPanel() {
|
|
5198
|
-
document.getElementById('threatPanel').classList.toggle('collapsed');
|
|
5199
|
-
}
|
|
5200
|
-
|
|
5201
|
-
function updateUptime() {
|
|
5202
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
5203
|
-
const hours = Math.floor(elapsed / 3600);
|
|
5204
|
-
const mins = Math.floor((elapsed % 3600) / 60);
|
|
5205
|
-
const secs = elapsed % 60;
|
|
5206
|
-
let uptimeStr = '';
|
|
5207
|
-
if (hours > 0) uptimeStr += hours + 'h ';
|
|
5208
|
-
if (mins > 0) uptimeStr += mins + 'm ';
|
|
5209
|
-
uptimeStr += secs + 's';
|
|
5210
|
-
document.getElementById('uptimeText').textContent = uptimeStr;
|
|
5211
|
-
}
|
|
5212
|
-
|
|
5213
|
-
// \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
|
|
5214
|
-
|
|
5215
|
-
function updateSovereigntyScore(score) {
|
|
5216
|
-
sovereigntyScore = Math.min(100, Math.max(0, score || 85));
|
|
5217
|
-
const badge = document.getElementById('sovereigntyScore');
|
|
5218
|
-
badge.textContent = sovereigntyScore;
|
|
5219
|
-
badge.className = 'sovereignty-score';
|
|
5220
|
-
if (sovereigntyScore >= 80) {
|
|
5221
|
-
badge.classList.add('high');
|
|
5222
|
-
} else if (sovereigntyScore >= 50) {
|
|
5223
|
-
badge.classList.add('medium');
|
|
5224
|
-
} else {
|
|
5225
|
-
badge.classList.add('low');
|
|
5514
|
+
if (!response.ok) {
|
|
5515
|
+
console.error('API Error:', response.status);
|
|
5516
|
+
return null;
|
|
5517
|
+
}
|
|
5518
|
+
|
|
5519
|
+
return await response.json();
|
|
5520
|
+
} catch (err) {
|
|
5521
|
+
console.error('Fetch error:', err);
|
|
5522
|
+
return null;
|
|
5523
|
+
}
|
|
5226
5524
|
}
|
|
5227
|
-
}
|
|
5228
5525
|
|
|
5229
|
-
|
|
5526
|
+
function redirectToLogin() {
|
|
5527
|
+
sessionStorage.removeItem('authToken');
|
|
5528
|
+
window.location.href = '/';
|
|
5529
|
+
}
|
|
5230
5530
|
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
tool,
|
|
5236
|
-
outcome,
|
|
5237
|
-
detail,
|
|
5238
|
-
hasInjection,
|
|
5239
|
-
isContextGated
|
|
5240
|
-
} = data;
|
|
5241
|
-
|
|
5242
|
-
const item = {
|
|
5243
|
-
id: 'activity-' + activityCount++,
|
|
5244
|
-
timestamp: timestamp || new Date().toISOString(),
|
|
5245
|
-
tier: tier || 1,
|
|
5246
|
-
tool: tool || 'unknown_tool',
|
|
5247
|
-
outcome: outcome || 'executed',
|
|
5248
|
-
detail: detail || '',
|
|
5249
|
-
hasInjection: !!hasInjection,
|
|
5250
|
-
isContextGated: !!isContextGated
|
|
5251
|
-
};
|
|
5531
|
+
// API Updates
|
|
5532
|
+
async function updateSovereignty() {
|
|
5533
|
+
const data = await fetchAPI('/api/sovereignty');
|
|
5534
|
+
if (!data) return;
|
|
5252
5535
|
|
|
5253
|
-
|
|
5254
|
-
if (activityItems.length > MAX_ACTIVITY_ITEMS) {
|
|
5255
|
-
activityItems.pop();
|
|
5256
|
-
}
|
|
5536
|
+
apiState.sovereignty = data;
|
|
5257
5537
|
|
|
5258
|
-
|
|
5259
|
-
|
|
5538
|
+
const score = calculateSovereigntyScore(data.shr);
|
|
5539
|
+
const badge = document.getElementById('sovereignty-badge');
|
|
5540
|
+
const scoreEl = document.getElementById('sovereignty-score');
|
|
5260
5541
|
|
|
5261
|
-
|
|
5262
|
-
const list = document.getElementById('activityList');
|
|
5542
|
+
scoreEl.textContent = score;
|
|
5263
5543
|
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5544
|
+
badge.classList.remove('degraded', 'inactive');
|
|
5545
|
+
if (score < 70) badge.classList.add('degraded');
|
|
5546
|
+
if (score < 40) badge.classList.add('inactive');
|
|
5547
|
+
|
|
5548
|
+
updateLayerCards(data.shr);
|
|
5267
5549
|
}
|
|
5268
5550
|
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
const tr = document.createElement('div');
|
|
5272
|
-
tr.className = 'activity-item';
|
|
5273
|
-
tr.id = item.id;
|
|
5274
|
-
|
|
5275
|
-
const time = new Date(item.timestamp);
|
|
5276
|
-
const timeStr = time.toLocaleTimeString();
|
|
5277
|
-
|
|
5278
|
-
const tierClass = 't' + item.tier;
|
|
5279
|
-
const outcomeClass = item.outcome === 'denied' ? 'outcome denied' : 'outcome';
|
|
5280
|
-
|
|
5281
|
-
let icon = '\u25CF';
|
|
5282
|
-
if (item.isContextGated) icon = '\u{1F3AF}';
|
|
5283
|
-
else if (item.hasInjection) icon = '\u26A0';
|
|
5284
|
-
else if (item.outcome === 'denied') icon = '\u2717';
|
|
5285
|
-
else icon = '\u2713';
|
|
5286
|
-
|
|
5287
|
-
tr.innerHTML =
|
|
5288
|
-
'<div class="activity-item-icon">' + esc(icon) + '</div>' +
|
|
5289
|
-
'<div class="activity-item-content">' +
|
|
5290
|
-
'<div class="activity-time">' + esc(timeStr) + '</div>' +
|
|
5291
|
-
'<div class="activity-main">' +
|
|
5292
|
-
'<span class="activity-tier ' + tierClass + '">T' + item.tier + '</span>' +
|
|
5293
|
-
'<span class="activity-tool">' + esc(item.tool) + '</span>' +
|
|
5294
|
-
'<span class="activity-outcome ' + (outcomeClass === 'outcome denied' ? 'denied' : '') + '">' + (item.outcome === 'denied' ? '\u2717 denied' : '\u2713 allowed') + '</span>' +
|
|
5295
|
-
'</div>' +
|
|
5296
|
-
'<div class="activity-detail">' + esc(item.detail) + '</div>' +
|
|
5297
|
-
'</div>' +
|
|
5298
|
-
'';
|
|
5299
|
-
|
|
5300
|
-
tr.addEventListener('click', () => {
|
|
5301
|
-
tr.classList.toggle('expanded');
|
|
5302
|
-
});
|
|
5551
|
+
function updateLayerCards(shr) {
|
|
5552
|
+
if (!shr || !shr.layers) return;
|
|
5303
5553
|
|
|
5304
|
-
|
|
5305
|
-
}
|
|
5306
|
-
}
|
|
5554
|
+
const layers = shr.layers;
|
|
5307
5555
|
|
|
5308
|
-
|
|
5556
|
+
updateLayerCard('l1', layers.l1, layers.l1?.encryption || 'AES-256-GCM');
|
|
5557
|
+
updateLayerCard('l2', layers.l2, layers.l2?.isolation_type || 'Process-level');
|
|
5558
|
+
updateLayerCard('l3', layers.l3, layers.l3?.proof_system || 'Schnorr-Pedersen');
|
|
5559
|
+
updateLayerCard('l4', layers.l4, layers.l4?.reputation_mode || 'Weighted');
|
|
5560
|
+
}
|
|
5309
5561
|
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
request_id,
|
|
5313
|
-
operation,
|
|
5314
|
-
tier,
|
|
5315
|
-
reason,
|
|
5316
|
-
context,
|
|
5317
|
-
timestamp
|
|
5318
|
-
} = data;
|
|
5319
|
-
|
|
5320
|
-
const pending = {
|
|
5321
|
-
id: request_id,
|
|
5322
|
-
operation: operation || 'unknown',
|
|
5323
|
-
tier: tier || 1,
|
|
5324
|
-
reason: reason || '',
|
|
5325
|
-
context: context || {},
|
|
5326
|
-
timestamp: timestamp || new Date().toISOString(),
|
|
5327
|
-
remaining: TIMEOUT_SECONDS
|
|
5328
|
-
};
|
|
5562
|
+
function updateLayerCard(layer, layerData, detail) {
|
|
5563
|
+
if (!layerData) return;
|
|
5329
5564
|
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
}
|
|
5565
|
+
const card = document.querySelector(\`[data-layer="\${layer}"]\`);
|
|
5566
|
+
if (!card) return;
|
|
5333
5567
|
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
updatePendingUI();
|
|
5337
|
-
}
|
|
5568
|
+
const status = layerData.status || 'inactive';
|
|
5569
|
+
card.classList.remove('degraded', 'inactive');
|
|
5338
5570
|
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5571
|
+
if (status === 'degraded') {
|
|
5572
|
+
card.classList.add('degraded');
|
|
5573
|
+
} else if (status === 'inactive') {
|
|
5574
|
+
card.classList.add('inactive');
|
|
5575
|
+
}
|
|
5342
5576
|
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
badge.textContent = count;
|
|
5346
|
-
document.getElementById('pendingOverlay').classList.add('active');
|
|
5347
|
-
} else {
|
|
5348
|
-
badge.classList.add('hidden');
|
|
5349
|
-
document.getElementById('pendingOverlay').classList.remove('active');
|
|
5577
|
+
document.getElementById(\`\${layer}-status\`).textContent = status.toUpperCase();
|
|
5578
|
+
document.getElementById(\`\${layer}-detail\`).textContent = detail;
|
|
5350
5579
|
}
|
|
5351
5580
|
|
|
5352
|
-
|
|
5353
|
-
|
|
5581
|
+
async function updateIdentity() {
|
|
5582
|
+
const data = await fetchAPI('/api/identity');
|
|
5583
|
+
if (!data) return;
|
|
5354
5584
|
|
|
5355
|
-
|
|
5356
|
-
const list = document.getElementById('pendingList');
|
|
5357
|
-
list.innerHTML = '';
|
|
5585
|
+
apiState.identity = data;
|
|
5358
5586
|
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5587
|
+
const primary = data.primary || {};
|
|
5588
|
+
document.getElementById('identity-label').textContent = primary.label || '\u2014';
|
|
5589
|
+
document.getElementById('identity-did').textContent = truncate(primary.did, 24);
|
|
5590
|
+
document.getElementById('identity-did').title = primary.did || '';
|
|
5591
|
+
document.getElementById('identity-pubkey').textContent = truncate(primary.publicKey, 24);
|
|
5592
|
+
document.getElementById('identity-pubkey').title = primary.publicKey || '';
|
|
5593
|
+
document.getElementById('identity-created').textContent = formatTime(primary.createdAt);
|
|
5594
|
+
document.getElementById('identity-count').textContent = data.identities?.length || '\u2014';
|
|
5595
|
+
}
|
|
5362
5596
|
|
|
5363
|
-
|
|
5364
|
-
const
|
|
5365
|
-
|
|
5366
|
-
const isUrgent = req.remaining <= 30;
|
|
5597
|
+
async function updateHandshakes() {
|
|
5598
|
+
const data = await fetchAPI('/api/handshakes');
|
|
5599
|
+
if (!data) return;
|
|
5367
5600
|
|
|
5368
|
-
|
|
5369
|
-
'<div class="pending-item-header">' +
|
|
5370
|
-
'<div class="pending-item-op">' + esc(req.operation) + '</div>' +
|
|
5371
|
-
'<div class="pending-item-tier ' + tierClass + '">T' + tier + '</div>' +
|
|
5372
|
-
'</div>' +
|
|
5373
|
-
'<div class="pending-item-reason">' + esc(req.reason) + '</div>' +
|
|
5374
|
-
'<div class="pending-item-timer ' + (isUrgent ? 'urgent' : '') + '">' +
|
|
5375
|
-
'<div class="pending-item-timer-bar">' +
|
|
5376
|
-
'<div class="pending-item-timer-fill" style="width: ' + pct + '%"></div>' +
|
|
5377
|
-
'</div>' +
|
|
5378
|
-
'<span id="timer-' + id + '">' + req.remaining + 's</span>' +
|
|
5379
|
-
'</div>' +
|
|
5380
|
-
'<div class="pending-item-actions">' +
|
|
5381
|
-
'<button class="btn btn-approve" onclick="handleApprove('' + id + '')">Approve</button>' +
|
|
5382
|
-
'<button class="btn btn-deny" onclick="handleDeny('' + id + '')">Deny</button>' +
|
|
5383
|
-
'</div>' +
|
|
5384
|
-
'';
|
|
5601
|
+
apiState.handshakes = data.handshakes || [];
|
|
5385
5602
|
|
|
5386
|
-
|
|
5387
|
-
}
|
|
5388
|
-
}
|
|
5603
|
+
document.getElementById('handshake-count').textContent = data.handshakes?.length || '0';
|
|
5389
5604
|
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5605
|
+
if (data.handshakes && data.handshakes.length > 0) {
|
|
5606
|
+
const latest = data.handshakes[0];
|
|
5607
|
+
document.getElementById('handshake-latest').textContent = truncate(latest.counterpartyId, 20);
|
|
5608
|
+
document.getElementById('handshake-latest').title = latest.counterpartyId || '';
|
|
5609
|
+
document.getElementById('handshake-tier').textContent = (latest.trustTier || 'Unverified').toUpperCase();
|
|
5610
|
+
document.getElementById('handshake-time').textContent = formatTime(latest.completedAt);
|
|
5611
|
+
} else {
|
|
5612
|
+
document.getElementById('handshake-latest').textContent = '\u2014';
|
|
5613
|
+
document.getElementById('handshake-tier').textContent = 'Unverified';
|
|
5614
|
+
document.getElementById('handshake-time').textContent = '\u2014';
|
|
5615
|
+
}
|
|
5395
5616
|
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
removePendingRequest(id);
|
|
5399
|
-
}).catch(() => {});
|
|
5400
|
-
};
|
|
5617
|
+
updateHandshakeTable(data.handshakes || []);
|
|
5618
|
+
}
|
|
5401
5619
|
|
|
5402
|
-
|
|
5620
|
+
function updateHandshakeTable(handshakes) {
|
|
5621
|
+
const table = document.getElementById('handshake-table');
|
|
5403
5622
|
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
type,
|
|
5409
|
-
details
|
|
5410
|
-
} = data;
|
|
5411
|
-
|
|
5412
|
-
const threat = {
|
|
5413
|
-
id: 'threat-' + threatCount++,
|
|
5414
|
-
timestamp: timestamp || new Date().toISOString(),
|
|
5415
|
-
severity: severity || 'medium',
|
|
5416
|
-
type: type || 'unknown',
|
|
5417
|
-
details: details || ''
|
|
5418
|
-
};
|
|
5623
|
+
if (!handshakes || handshakes.length === 0) {
|
|
5624
|
+
table.innerHTML = '<div class="table-empty">No handshakes completed yet</div>';
|
|
5625
|
+
return;
|
|
5626
|
+
}
|
|
5419
5627
|
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5628
|
+
table.innerHTML = handshakes
|
|
5629
|
+
.map(
|
|
5630
|
+
(hs) => \`
|
|
5631
|
+
<div class="table-row">
|
|
5632
|
+
<div class="table-cell strong">\${esc(truncate(hs.counterpartyId, 24))}</div>
|
|
5633
|
+
<div class="table-cell">\${esc(hs.trustTier || 'Unverified')}</div>
|
|
5634
|
+
<div class="table-cell">\${esc(hs.sovereigntyLevel || '\u2014')}</div>
|
|
5635
|
+
<div class="table-cell">\${hs.verified ? 'Yes' : 'No'}</div>
|
|
5636
|
+
<div class="table-cell">\${formatTime(hs.completedAt)}</div>
|
|
5637
|
+
<div class="table-cell">\${formatTime(hs.expiresAt)}</div>
|
|
5638
|
+
</div>
|
|
5639
|
+
\`
|
|
5640
|
+
)
|
|
5641
|
+
.join('');
|
|
5423
5642
|
}
|
|
5424
5643
|
|
|
5425
|
-
|
|
5426
|
-
|
|
5644
|
+
async function updateSHR() {
|
|
5645
|
+
const data = await fetchAPI('/api/shr');
|
|
5646
|
+
if (!data) return;
|
|
5647
|
+
|
|
5648
|
+
apiState.shr = data;
|
|
5649
|
+
renderSHRViewer(data);
|
|
5427
5650
|
}
|
|
5428
5651
|
|
|
5429
|
-
|
|
5430
|
-
|
|
5652
|
+
function renderSHRViewer(shr) {
|
|
5653
|
+
const viewer = document.getElementById('shr-viewer');
|
|
5431
5654
|
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5655
|
+
if (!shr) {
|
|
5656
|
+
viewer.innerHTML = '<div class="empty-state">No SHR available</div>';
|
|
5657
|
+
return;
|
|
5658
|
+
}
|
|
5435
5659
|
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5660
|
+
let html = '';
|
|
5661
|
+
|
|
5662
|
+
// Implementation
|
|
5663
|
+
html += \`
|
|
5664
|
+
<div class="shr-section">
|
|
5665
|
+
<div class="shr-section-header">
|
|
5666
|
+
<div class="shr-toggle">\u25BC</div>
|
|
5667
|
+
<div>Implementation</div>
|
|
5668
|
+
</div>
|
|
5669
|
+
<div class="shr-section-content">
|
|
5670
|
+
<div class="shr-item">
|
|
5671
|
+
<div class="shr-key">sanctuary_version:</div>
|
|
5672
|
+
<div class="shr-value">\${esc(shr.implementation?.sanctuary_version || '\u2014')}</div>
|
|
5673
|
+
</div>
|
|
5674
|
+
<div class="shr-item">
|
|
5675
|
+
<div class="shr-key">node_version:</div>
|
|
5676
|
+
<div class="shr-value">\${esc(shr.implementation?.node_version || '\u2014')}</div>
|
|
5677
|
+
</div>
|
|
5678
|
+
<div class="shr-item">
|
|
5679
|
+
<div class="shr-key">generated_by:</div>
|
|
5680
|
+
<div class="shr-value">\${esc(shr.implementation?.generated_by || '\u2014')}</div>
|
|
5681
|
+
</div>
|
|
5682
|
+
</div>
|
|
5683
|
+
</div>
|
|
5684
|
+
\`;
|
|
5685
|
+
|
|
5686
|
+
// Metadata
|
|
5687
|
+
html += \`
|
|
5688
|
+
<div class="shr-section">
|
|
5689
|
+
<div class="shr-section-header">
|
|
5690
|
+
<div class="shr-toggle">\u25BC</div>
|
|
5691
|
+
<div>Metadata</div>
|
|
5692
|
+
</div>
|
|
5693
|
+
<div class="shr-section-content">
|
|
5694
|
+
<div class="shr-item">
|
|
5695
|
+
<div class="shr-key">instance_id:</div>
|
|
5696
|
+
<div class="shr-value">\${esc(truncate(shr.instance_id, 20))}</div>
|
|
5697
|
+
</div>
|
|
5698
|
+
<div class="shr-item">
|
|
5699
|
+
<div class="shr-key">generated_at:</div>
|
|
5700
|
+
<div class="shr-value">\${formatTime(shr.generated_at)}</div>
|
|
5701
|
+
</div>
|
|
5702
|
+
<div class="shr-item">
|
|
5703
|
+
<div class="shr-key">expires_at:</div>
|
|
5704
|
+
<div class="shr-value">\${formatTime(shr.expires_at)}</div>
|
|
5705
|
+
</div>
|
|
5706
|
+
</div>
|
|
5707
|
+
</div>
|
|
5708
|
+
\`;
|
|
5709
|
+
|
|
5710
|
+
// Layers
|
|
5711
|
+
if (shr.layers) {
|
|
5712
|
+
html += \`<div class="shr-section">
|
|
5713
|
+
<div class="shr-section-header">
|
|
5714
|
+
<div class="shr-toggle">\u25BC</div>
|
|
5715
|
+
<div>Layers</div>
|
|
5716
|
+
</div>
|
|
5717
|
+
<div class="shr-section-content">
|
|
5718
|
+
\`;
|
|
5719
|
+
|
|
5720
|
+
for (const [key, layer] of Object.entries(shr.layers)) {
|
|
5721
|
+
html += \`
|
|
5722
|
+
<div style="margin-bottom: 12px;">
|
|
5723
|
+
<div style="color: var(--blue); font-weight: 600; margin-bottom: 4px;">\${esc(key)}</div>
|
|
5724
|
+
<div style="padding-left: 12px;">
|
|
5725
|
+
\`;
|
|
5726
|
+
|
|
5727
|
+
for (const [lkey, lvalue] of Object.entries(layer || {})) {
|
|
5728
|
+
const displayValue =
|
|
5729
|
+
typeof lvalue === 'boolean'
|
|
5730
|
+
? lvalue
|
|
5731
|
+
? 'true'
|
|
5732
|
+
: 'false'
|
|
5733
|
+
: esc(String(lvalue));
|
|
5734
|
+
html += \`
|
|
5735
|
+
<div class="shr-item">
|
|
5736
|
+
<div class="shr-key">\${esc(lkey)}:</div>
|
|
5737
|
+
<div class="shr-value">\${displayValue}</div>
|
|
5738
|
+
</div>
|
|
5739
|
+
\`;
|
|
5740
|
+
}
|
|
5441
5741
|
|
|
5442
|
-
|
|
5443
|
-
|
|
5742
|
+
html += \`
|
|
5743
|
+
</div>
|
|
5744
|
+
</div>
|
|
5745
|
+
\`;
|
|
5746
|
+
}
|
|
5444
5747
|
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5748
|
+
html += \`
|
|
5749
|
+
</div>
|
|
5750
|
+
</div>
|
|
5751
|
+
\`;
|
|
5752
|
+
}
|
|
5753
|
+
|
|
5754
|
+
// Capabilities
|
|
5755
|
+
if (shr.capabilities) {
|
|
5756
|
+
html += \`
|
|
5757
|
+
<div class="shr-section">
|
|
5758
|
+
<div class="shr-section-header">
|
|
5759
|
+
<div class="shr-toggle">\u25BC</div>
|
|
5760
|
+
<div>Capabilities</div>
|
|
5761
|
+
</div>
|
|
5762
|
+
<div class="shr-section-content">
|
|
5763
|
+
\`;
|
|
5764
|
+
|
|
5765
|
+
for (const [key, value] of Object.entries(shr.capabilities)) {
|
|
5766
|
+
const displayValue = value ? 'true' : 'false';
|
|
5767
|
+
html += \`
|
|
5768
|
+
<div class="shr-item">
|
|
5769
|
+
<div class="shr-key">\${esc(key)}:</div>
|
|
5770
|
+
<div class="shr-value">\${displayValue}</div>
|
|
5771
|
+
</div>
|
|
5772
|
+
\`;
|
|
5773
|
+
}
|
|
5774
|
+
|
|
5775
|
+
html += \`
|
|
5776
|
+
</div>
|
|
5777
|
+
</div>
|
|
5778
|
+
\`;
|
|
5779
|
+
}
|
|
5780
|
+
|
|
5781
|
+
// Signature
|
|
5782
|
+
html += \`
|
|
5783
|
+
<div class="shr-section">
|
|
5784
|
+
<div class="shr-section-header">
|
|
5785
|
+
<div class="shr-toggle">\u25BC</div>
|
|
5786
|
+
<div>Signature</div>
|
|
5787
|
+
</div>
|
|
5788
|
+
<div class="shr-section-content">
|
|
5789
|
+
<div class="shr-item">
|
|
5790
|
+
<div class="shr-key">signed_by:</div>
|
|
5791
|
+
<div class="shr-value">\${esc(truncate(shr.signed_by, 20))}</div>
|
|
5792
|
+
</div>
|
|
5793
|
+
<div class="shr-item">
|
|
5794
|
+
<div class="shr-key">signature:</div>
|
|
5795
|
+
<div class="shr-value">\${esc(truncate(shr.signature, 32))}</div>
|
|
5796
|
+
</div>
|
|
5797
|
+
</div>
|
|
5798
|
+
</div>
|
|
5799
|
+
\`;
|
|
5800
|
+
|
|
5801
|
+
viewer.innerHTML = html;
|
|
5802
|
+
|
|
5803
|
+
// Add collapse functionality
|
|
5804
|
+
document.querySelectorAll('.shr-section-header').forEach((header) => {
|
|
5805
|
+
header.addEventListener('click', () => {
|
|
5806
|
+
header.closest('.shr-section').classList.toggle('collapsed');
|
|
5807
|
+
});
|
|
5808
|
+
});
|
|
5457
5809
|
}
|
|
5458
|
-
}
|
|
5459
5810
|
|
|
5460
|
-
|
|
5811
|
+
async function updateStatus() {
|
|
5812
|
+
const data = await fetchAPI('/api/status');
|
|
5813
|
+
if (!data) return;
|
|
5461
5814
|
|
|
5462
|
-
|
|
5463
|
-
if (evtSource) evtSource.close();
|
|
5464
|
-
connect();
|
|
5465
|
-
}
|
|
5815
|
+
apiState.status = data;
|
|
5466
5816
|
|
|
5467
|
-
|
|
5468
|
-
|
|
5817
|
+
document.getElementById('protections-count').textContent = data.protectionsCount || '0';
|
|
5818
|
+
document.getElementById('uptime-value').textContent = formatUptime(data.uptime);
|
|
5469
5819
|
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
}
|
|
5820
|
+
const connectionStatus = document.getElementById('connection-status');
|
|
5821
|
+
connectionStatus.classList.toggle('disconnected', !data.connected);
|
|
5822
|
+
}
|
|
5473
5823
|
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5824
|
+
function formatUptime(seconds) {
|
|
5825
|
+
if (!seconds) return '\u2014';
|
|
5826
|
+
const hours = Math.floor(seconds / 3600);
|
|
5827
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
5828
|
+
if (hours > 0) return \`\${hours}h \${minutes}m\`;
|
|
5829
|
+
return \`\${minutes}m\`;
|
|
5830
|
+
}
|
|
5477
5831
|
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
}
|
|
5486
|
-
if (data.pending) {
|
|
5487
|
-
data.pending.forEach(addPendingRequest);
|
|
5488
|
-
}
|
|
5489
|
-
});
|
|
5832
|
+
// SSE Setup
|
|
5833
|
+
function setupSSE() {
|
|
5834
|
+
const eventSource = new EventSource(API_BASE + '/api/events', {
|
|
5835
|
+
headers: {
|
|
5836
|
+
'Authorization': 'Bearer ' + AUTH_TOKEN,
|
|
5837
|
+
},
|
|
5838
|
+
});
|
|
5490
5839
|
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
});
|
|
5840
|
+
eventSource.addEventListener('init', (e) => {
|
|
5841
|
+
console.log('Connected to SSE');
|
|
5842
|
+
});
|
|
5495
5843
|
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
});
|
|
5844
|
+
eventSource.addEventListener('sovereignty-update', () => {
|
|
5845
|
+
updateSovereignty();
|
|
5846
|
+
});
|
|
5500
5847
|
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
addActivityItem({
|
|
5504
|
-
timestamp: data.timestamp,
|
|
5505
|
-
tier: data.tier || 1,
|
|
5506
|
-
tool: data.tool || 'unknown',
|
|
5507
|
-
outcome: data.outcome || 'executed',
|
|
5508
|
-
detail: data.detail || ''
|
|
5848
|
+
eventSource.addEventListener('handshake-update', () => {
|
|
5849
|
+
updateHandshakes();
|
|
5509
5850
|
});
|
|
5510
|
-
});
|
|
5511
5851
|
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
isContextGated: true
|
|
5852
|
+
eventSource.addEventListener('tool-call', (e) => {
|
|
5853
|
+
const data = JSON.parse(e.data);
|
|
5854
|
+
addActivityItem({
|
|
5855
|
+
type: 'tool-call',
|
|
5856
|
+
title: 'Tool Call',
|
|
5857
|
+
content: data.toolName,
|
|
5858
|
+
timestamp: new Date().toISOString(),
|
|
5859
|
+
});
|
|
5521
5860
|
});
|
|
5522
|
-
});
|
|
5523
5861
|
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
hasInjection: true
|
|
5862
|
+
eventSource.addEventListener('context-gate-decision', (e) => {
|
|
5863
|
+
const data = JSON.parse(e.data);
|
|
5864
|
+
addActivityItem({
|
|
5865
|
+
type: 'context-gate',
|
|
5866
|
+
title: 'Context Gate',
|
|
5867
|
+
content: data.decision,
|
|
5868
|
+
timestamp: new Date().toISOString(),
|
|
5869
|
+
});
|
|
5533
5870
|
});
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5871
|
+
|
|
5872
|
+
eventSource.addEventListener('injection-alert', (e) => {
|
|
5873
|
+
const data = JSON.parse(e.data);
|
|
5874
|
+
addActivityItem({
|
|
5875
|
+
type: 'injection',
|
|
5876
|
+
title: 'Injection Alert',
|
|
5877
|
+
content: data.pattern,
|
|
5878
|
+
timestamp: new Date().toISOString(),
|
|
5879
|
+
});
|
|
5880
|
+
addThreatAlert(data);
|
|
5539
5881
|
});
|
|
5540
|
-
});
|
|
5541
5882
|
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5883
|
+
eventSource.addEventListener('pending-request', (e) => {
|
|
5884
|
+
const data = JSON.parse(e.data);
|
|
5885
|
+
addPendingRequest(data);
|
|
5886
|
+
});
|
|
5546
5887
|
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5888
|
+
eventSource.addEventListener('request-resolved', (e) => {
|
|
5889
|
+
const data = JSON.parse(e.data);
|
|
5890
|
+
removePendingRequest(data.requestId);
|
|
5891
|
+
});
|
|
5551
5892
|
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5893
|
+
eventSource.onerror = () => {
|
|
5894
|
+
console.error('SSE error');
|
|
5895
|
+
setTimeout(setupSSE, 5000);
|
|
5896
|
+
};
|
|
5897
|
+
}
|
|
5557
5898
|
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5899
|
+
// Activity Feed
|
|
5900
|
+
function addActivityItem(item) {
|
|
5901
|
+
activityLog.unshift(item);
|
|
5902
|
+
if (activityLog.length > maxActivityItems) {
|
|
5903
|
+
activityLog.pop();
|
|
5904
|
+
}
|
|
5562
5905
|
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5906
|
+
const feed = document.getElementById('activity-feed');
|
|
5907
|
+
const html = \`
|
|
5908
|
+
<div class="activity-item \${item.type}">
|
|
5909
|
+
<div class="activity-type">\${esc(item.title)}</div>
|
|
5910
|
+
<div class="activity-content">\${esc(item.content)}</div>
|
|
5911
|
+
<div class="activity-time">\${formatTime(item.timestamp)}</div>
|
|
5912
|
+
</div>
|
|
5913
|
+
\`;
|
|
5570
5914
|
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
const el = document.getElementById('encryptionStatus');
|
|
5581
|
-
el.className = 'protection-card-status ' + (status.encryption ? 'active' : 'inactive');
|
|
5582
|
-
el.textContent = status.encryption ? '\u2713 Active' : '\u2717 Inactive';
|
|
5583
|
-
}
|
|
5584
|
-
if (status.approval_gate !== undefined) {
|
|
5585
|
-
const el = document.getElementById('approvalStatus');
|
|
5586
|
-
el.className = 'protection-card-status ' + (status.approval_gate ? 'active' : 'inactive');
|
|
5587
|
-
el.textContent = status.approval_gate ? '\u2713 Active' : '\u2717 Inactive';
|
|
5588
|
-
}
|
|
5589
|
-
if (status.context_gating !== undefined) {
|
|
5590
|
-
const el = document.getElementById('contextStatus');
|
|
5591
|
-
el.className = 'protection-card-status ' + (status.context_gating ? 'active' : 'inactive');
|
|
5592
|
-
el.textContent = status.context_gating ? '\u2713 Active' : '\u2717 Inactive';
|
|
5915
|
+
if (feed.querySelector('.empty-state')) {
|
|
5916
|
+
feed.innerHTML = '';
|
|
5917
|
+
}
|
|
5918
|
+
|
|
5919
|
+
feed.insertAdjacentHTML('afterbegin', html);
|
|
5920
|
+
|
|
5921
|
+
if (feed.children.length > maxActivityItems) {
|
|
5922
|
+
feed.lastChild.remove();
|
|
5923
|
+
}
|
|
5593
5924
|
}
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5925
|
+
|
|
5926
|
+
// Pending Requests
|
|
5927
|
+
function addPendingRequest(request) {
|
|
5928
|
+
pendingRequests.set(request.requestId, {
|
|
5929
|
+
id: request.requestId,
|
|
5930
|
+
title: request.title,
|
|
5931
|
+
details: request.details,
|
|
5932
|
+
expiresAt: new Date(Date.now() + TIMEOUT_SECONDS * 1000),
|
|
5933
|
+
});
|
|
5934
|
+
|
|
5935
|
+
updatePendingDisplay();
|
|
5598
5936
|
}
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5937
|
+
|
|
5938
|
+
function removePendingRequest(requestId) {
|
|
5939
|
+
pendingRequests.delete(requestId);
|
|
5940
|
+
updatePendingDisplay();
|
|
5603
5941
|
}
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5942
|
+
|
|
5943
|
+
function updatePendingDisplay() {
|
|
5944
|
+
const badge = document.getElementById('pending-item-badge');
|
|
5945
|
+
const count = pendingRequests.size;
|
|
5946
|
+
|
|
5947
|
+
if (count > 0) {
|
|
5948
|
+
document.getElementById('pending-count').textContent = count;
|
|
5949
|
+
badge.style.display = 'flex';
|
|
5950
|
+
} else {
|
|
5951
|
+
badge.style.display = 'none';
|
|
5952
|
+
}
|
|
5953
|
+
|
|
5954
|
+
const overlay = document.getElementById('pending-overlay');
|
|
5955
|
+
const items = document.getElementById('pending-items');
|
|
5956
|
+
|
|
5957
|
+
if (count === 0) {
|
|
5958
|
+
items.innerHTML = '';
|
|
5959
|
+
overlay.classList.remove('show');
|
|
5960
|
+
return;
|
|
5961
|
+
}
|
|
5962
|
+
|
|
5963
|
+
let html = '';
|
|
5964
|
+
for (const req of pendingRequests.values()) {
|
|
5965
|
+
const remaining = Math.max(0, Math.floor((req.expiresAt - Date.now()) / 1000));
|
|
5966
|
+
html += \`
|
|
5967
|
+
<div class="pending-item">
|
|
5968
|
+
<div class="pending-title">\${esc(req.title)}</div>
|
|
5969
|
+
<div class="pending-countdown">Expires in \${remaining}s</div>
|
|
5970
|
+
<div class="pending-actions">
|
|
5971
|
+
<button class="pending-btn pending-approve" data-id="\${req.id}">Approve</button>
|
|
5972
|
+
<button class="pending-btn pending-deny" data-id="\${req.id}">Deny</button>
|
|
5973
|
+
</div>
|
|
5974
|
+
</div>
|
|
5975
|
+
\`;
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5978
|
+
items.innerHTML = html;
|
|
5979
|
+
|
|
5980
|
+
document.querySelectorAll('.pending-approve').forEach((btn) => {
|
|
5981
|
+
btn.addEventListener('click', async () => {
|
|
5982
|
+
const id = btn.getAttribute('data-id');
|
|
5983
|
+
await fetchAPI(\`/api/approve/\${id}\`);
|
|
5984
|
+
});
|
|
5985
|
+
});
|
|
5986
|
+
|
|
5987
|
+
document.querySelectorAll('.pending-deny').forEach((btn) => {
|
|
5988
|
+
btn.addEventListener('click', async () => {
|
|
5989
|
+
const id = btn.getAttribute('data-id');
|
|
5990
|
+
await fetchAPI(\`/api/deny/\${id}\`);
|
|
5991
|
+
});
|
|
5992
|
+
});
|
|
5608
5993
|
}
|
|
5609
|
-
}
|
|
5610
5994
|
|
|
5611
|
-
|
|
5995
|
+
// Threat Panel
|
|
5996
|
+
function addThreatAlert(alert) {
|
|
5997
|
+
const panel = document.querySelector('.threat-panel');
|
|
5998
|
+
const content = document.getElementById('threat-alerts');
|
|
5612
5999
|
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
6000
|
+
if (content.querySelector('.empty-state')) {
|
|
6001
|
+
content.innerHTML = '';
|
|
6002
|
+
}
|
|
6003
|
+
|
|
6004
|
+
panel.classList.remove('collapsed');
|
|
6005
|
+
|
|
6006
|
+
const html = \`
|
|
6007
|
+
<div class="threat-alert">
|
|
6008
|
+
<div class="threat-type">\${esc(alert.type || 'Injection Alert')}</div>
|
|
6009
|
+
<div class="threat-message">\${esc(alert.message || alert.pattern || '\u2014')}</div>
|
|
6010
|
+
</div>
|
|
6011
|
+
\`;
|
|
6012
|
+
|
|
6013
|
+
content.insertAdjacentHTML('afterbegin', html);
|
|
6014
|
+
|
|
6015
|
+
const alerts = content.querySelectorAll('.threat-alert');
|
|
6016
|
+
if (alerts.length > 10) {
|
|
6017
|
+
alerts[alerts.length - 1].remove();
|
|
6018
|
+
}
|
|
5618
6019
|
}
|
|
5619
|
-
connect();
|
|
5620
6020
|
|
|
5621
|
-
//
|
|
5622
|
-
|
|
5623
|
-
|
|
6021
|
+
// Threat Panel Toggle
|
|
6022
|
+
document.querySelector('.threat-header').addEventListener('click', () => {
|
|
6023
|
+
document.querySelector('.threat-panel').classList.toggle('collapsed');
|
|
6024
|
+
});
|
|
6025
|
+
|
|
6026
|
+
// SHR Copy Button
|
|
6027
|
+
document.getElementById('copy-shr-btn').addEventListener('click', async () => {
|
|
6028
|
+
if (!apiState.shr) return;
|
|
5624
6029
|
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
const
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
6030
|
+
const json = JSON.stringify(apiState.shr, null, 2);
|
|
6031
|
+
try {
|
|
6032
|
+
await navigator.clipboard.writeText(json);
|
|
6033
|
+
const btn = document.getElementById('copy-shr-btn');
|
|
6034
|
+
const original = btn.textContent;
|
|
6035
|
+
btn.textContent = 'Copied!';
|
|
6036
|
+
setTimeout(() => {
|
|
6037
|
+
btn.textContent = original;
|
|
6038
|
+
}, 2000);
|
|
6039
|
+
} catch (err) {
|
|
6040
|
+
console.error('Copy failed:', err);
|
|
5633
6041
|
}
|
|
5634
|
-
}
|
|
6042
|
+
});
|
|
5635
6043
|
|
|
5636
|
-
//
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
6044
|
+
// Pending Overlay Toggle
|
|
6045
|
+
document.getElementById('pending-item-badge').addEventListener('click', () => {
|
|
6046
|
+
document.getElementById('pending-overlay').classList.toggle('show');
|
|
6047
|
+
});
|
|
6048
|
+
|
|
6049
|
+
// Initialize
|
|
6050
|
+
async function initialize() {
|
|
6051
|
+
if (!AUTH_TOKEN) {
|
|
6052
|
+
redirectToLogin();
|
|
6053
|
+
return;
|
|
5643
6054
|
}
|
|
5644
|
-
} catch (e) {
|
|
5645
|
-
// Ignore
|
|
5646
|
-
}
|
|
5647
|
-
})();
|
|
5648
6055
|
|
|
5649
|
-
|
|
5650
|
-
|
|
6056
|
+
// Initial data fetch
|
|
6057
|
+
await Promise.all([
|
|
6058
|
+
updateSovereignty(),
|
|
6059
|
+
updateIdentity(),
|
|
6060
|
+
updateHandshakes(),
|
|
6061
|
+
updateSHR(),
|
|
6062
|
+
updateStatus(),
|
|
6063
|
+
]);
|
|
5651
6064
|
|
|
6065
|
+
// Setup SSE for real-time updates
|
|
6066
|
+
setupSSE();
|
|
6067
|
+
|
|
6068
|
+
// Refresh status periodically
|
|
6069
|
+
setInterval(updateStatus, 30000);
|
|
6070
|
+
}
|
|
6071
|
+
|
|
6072
|
+
// Start
|
|
6073
|
+
initialize();
|
|
6074
|
+
</script>
|
|
5652
6075
|
</body>
|
|
5653
6076
|
</html>`;
|
|
5654
6077
|
}
|
|
@@ -5669,6 +6092,10 @@ var DashboardApprovalChannel = class {
|
|
|
5669
6092
|
policy = null;
|
|
5670
6093
|
baseline = null;
|
|
5671
6094
|
auditLog = null;
|
|
6095
|
+
identityManager = null;
|
|
6096
|
+
handshakeResults = null;
|
|
6097
|
+
shrOpts = null;
|
|
6098
|
+
_sanctuaryConfig = null;
|
|
5672
6099
|
dashboardHTML;
|
|
5673
6100
|
loginHTML;
|
|
5674
6101
|
authToken;
|
|
@@ -5702,6 +6129,10 @@ var DashboardApprovalChannel = class {
|
|
|
5702
6129
|
this.policy = deps.policy;
|
|
5703
6130
|
this.baseline = deps.baseline;
|
|
5704
6131
|
this.auditLog = deps.auditLog;
|
|
6132
|
+
if (deps.identityManager) this.identityManager = deps.identityManager;
|
|
6133
|
+
if (deps.handshakeResults) this.handshakeResults = deps.handshakeResults;
|
|
6134
|
+
if (deps.shrOpts) this.shrOpts = deps.shrOpts;
|
|
6135
|
+
if (deps.sanctuaryConfig) this._sanctuaryConfig = deps.sanctuaryConfig;
|
|
5705
6136
|
}
|
|
5706
6137
|
/**
|
|
5707
6138
|
* Start the HTTP(S) server for the dashboard.
|
|
@@ -6034,6 +6465,14 @@ var DashboardApprovalChannel = class {
|
|
|
6034
6465
|
this.handlePendingList(res);
|
|
6035
6466
|
} else if (method === "GET" && url.pathname === "/api/audit-log") {
|
|
6036
6467
|
this.handleAuditLog(url, res);
|
|
6468
|
+
} else if (method === "GET" && url.pathname === "/api/sovereignty") {
|
|
6469
|
+
this.handleSovereignty(res);
|
|
6470
|
+
} else if (method === "GET" && url.pathname === "/api/identity") {
|
|
6471
|
+
this.handleIdentity(res);
|
|
6472
|
+
} else if (method === "GET" && url.pathname === "/api/handshakes") {
|
|
6473
|
+
this.handleHandshakes(res);
|
|
6474
|
+
} else if (method === "GET" && url.pathname === "/api/shr") {
|
|
6475
|
+
this.handleSHR(res);
|
|
6037
6476
|
} else if (method === "POST" && url.pathname.startsWith("/api/approve/")) {
|
|
6038
6477
|
if (!this.checkRateLimit(req, res, "decisions")) return;
|
|
6039
6478
|
const id = url.pathname.slice("/api/approve/".length);
|
|
@@ -6223,6 +6662,107 @@ data: ${JSON.stringify(initData)}
|
|
|
6223
6662
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6224
6663
|
res.end(JSON.stringify({ success: true, decision }));
|
|
6225
6664
|
}
|
|
6665
|
+
// ── Sovereignty Data Routes ─────────────────────────────────────────
|
|
6666
|
+
handleSovereignty(res) {
|
|
6667
|
+
if (!this.shrOpts) {
|
|
6668
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6669
|
+
res.end(JSON.stringify({ error: "SHR generator not available" }));
|
|
6670
|
+
return;
|
|
6671
|
+
}
|
|
6672
|
+
const shr = generateSHR(void 0, this.shrOpts);
|
|
6673
|
+
if (typeof shr === "string") {
|
|
6674
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6675
|
+
res.end(JSON.stringify({ error: shr }));
|
|
6676
|
+
return;
|
|
6677
|
+
}
|
|
6678
|
+
const layers = shr.body.layers;
|
|
6679
|
+
let score = 0;
|
|
6680
|
+
for (const layer of [layers.l1, layers.l2, layers.l3, layers.l4]) {
|
|
6681
|
+
if (layer.status === "active") score += 25;
|
|
6682
|
+
else if (layer.status === "degraded") score += 15;
|
|
6683
|
+
}
|
|
6684
|
+
const overallLevel = score === 100 ? "full" : score >= 65 ? "degraded" : score >= 25 ? "minimal" : "unverified";
|
|
6685
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6686
|
+
res.end(JSON.stringify({
|
|
6687
|
+
score,
|
|
6688
|
+
overall_level: overallLevel,
|
|
6689
|
+
layers: {
|
|
6690
|
+
l1: { status: layers.l1.status, detail: layers.l1.encryption, key_custody: layers.l1.key_custody },
|
|
6691
|
+
l2: { status: layers.l2.status, detail: layers.l2.isolation_type, attestation: layers.l2.attestation_available },
|
|
6692
|
+
l3: { status: layers.l3.status, detail: layers.l3.proof_system, selective_disclosure: layers.l3.selective_disclosure },
|
|
6693
|
+
l4: { status: layers.l4.status, detail: layers.l4.attestation_format, reputation_portable: layers.l4.reputation_portable }
|
|
6694
|
+
},
|
|
6695
|
+
degradations: shr.body.degradations,
|
|
6696
|
+
capabilities: shr.body.capabilities,
|
|
6697
|
+
config_loaded: this._sanctuaryConfig != null
|
|
6698
|
+
}));
|
|
6699
|
+
}
|
|
6700
|
+
handleIdentity(res) {
|
|
6701
|
+
if (!this.identityManager) {
|
|
6702
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6703
|
+
res.end(JSON.stringify({ identities: [], count: 0 }));
|
|
6704
|
+
return;
|
|
6705
|
+
}
|
|
6706
|
+
const identities = this.identityManager.list().map((id) => ({
|
|
6707
|
+
identity_id: id.identity_id,
|
|
6708
|
+
label: id.label,
|
|
6709
|
+
public_key: id.public_key,
|
|
6710
|
+
did: id.did,
|
|
6711
|
+
created_at: id.created_at,
|
|
6712
|
+
key_type: id.key_type,
|
|
6713
|
+
key_protection: id.key_protection,
|
|
6714
|
+
rotation_count: id.rotation_history?.length ?? 0
|
|
6715
|
+
}));
|
|
6716
|
+
const primary = this.identityManager.getDefault();
|
|
6717
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6718
|
+
res.end(JSON.stringify({
|
|
6719
|
+
identities,
|
|
6720
|
+
count: identities.length,
|
|
6721
|
+
primary_id: primary?.identity_id ?? null
|
|
6722
|
+
}));
|
|
6723
|
+
}
|
|
6724
|
+
handleHandshakes(res) {
|
|
6725
|
+
if (!this.handshakeResults) {
|
|
6726
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6727
|
+
res.end(JSON.stringify({ handshakes: [], count: 0 }));
|
|
6728
|
+
return;
|
|
6729
|
+
}
|
|
6730
|
+
const handshakes = Array.from(this.handshakeResults.values()).map((h) => ({
|
|
6731
|
+
counterparty_id: h.counterparty_id,
|
|
6732
|
+
verified: h.verified,
|
|
6733
|
+
sovereignty_level: h.sovereignty_level,
|
|
6734
|
+
trust_tier: h.trust_tier,
|
|
6735
|
+
completed_at: h.completed_at,
|
|
6736
|
+
expires_at: h.expires_at,
|
|
6737
|
+
errors: h.errors
|
|
6738
|
+
}));
|
|
6739
|
+
handshakes.sort((a, b) => new Date(b.completed_at).getTime() - new Date(a.completed_at).getTime());
|
|
6740
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6741
|
+
res.end(JSON.stringify({
|
|
6742
|
+
handshakes,
|
|
6743
|
+
count: handshakes.length,
|
|
6744
|
+
tier_distribution: {
|
|
6745
|
+
verified_sovereign: handshakes.filter((h) => h.trust_tier === "verified-sovereign").length,
|
|
6746
|
+
verified_degraded: handshakes.filter((h) => h.trust_tier === "verified-degraded").length,
|
|
6747
|
+
unverified: handshakes.filter((h) => h.trust_tier === "unverified").length
|
|
6748
|
+
}
|
|
6749
|
+
}));
|
|
6750
|
+
}
|
|
6751
|
+
handleSHR(res) {
|
|
6752
|
+
if (!this.shrOpts) {
|
|
6753
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6754
|
+
res.end(JSON.stringify({ error: "SHR generator not available" }));
|
|
6755
|
+
return;
|
|
6756
|
+
}
|
|
6757
|
+
const shr = generateSHR(void 0, this.shrOpts);
|
|
6758
|
+
if (typeof shr === "string") {
|
|
6759
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6760
|
+
res.end(JSON.stringify({ error: shr }));
|
|
6761
|
+
return;
|
|
6762
|
+
}
|
|
6763
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6764
|
+
res.end(JSON.stringify(shr));
|
|
6765
|
+
}
|
|
6226
6766
|
// ── SSE Broadcasting ────────────────────────────────────────────────
|
|
6227
6767
|
broadcastSSE(event, data) {
|
|
6228
6768
|
const message = `event: ${event}
|
|
@@ -7389,108 +7929,6 @@ function createPrincipalPolicyTools(policy, baseline, auditLog) {
|
|
|
7389
7929
|
];
|
|
7390
7930
|
}
|
|
7391
7931
|
|
|
7392
|
-
// src/shr/types.ts
|
|
7393
|
-
function deepSortKeys(obj) {
|
|
7394
|
-
if (obj === null || typeof obj !== "object") return obj;
|
|
7395
|
-
if (Array.isArray(obj)) return obj.map(deepSortKeys);
|
|
7396
|
-
const sorted = {};
|
|
7397
|
-
for (const key of Object.keys(obj).sort()) {
|
|
7398
|
-
sorted[key] = deepSortKeys(obj[key]);
|
|
7399
|
-
}
|
|
7400
|
-
return sorted;
|
|
7401
|
-
}
|
|
7402
|
-
function canonicalizeForSigning(body) {
|
|
7403
|
-
return JSON.stringify(deepSortKeys(body));
|
|
7404
|
-
}
|
|
7405
|
-
|
|
7406
|
-
// src/shr/generator.ts
|
|
7407
|
-
init_encoding();
|
|
7408
|
-
var DEFAULT_VALIDITY_MS = 60 * 60 * 1e3;
|
|
7409
|
-
function generateSHR(identityId, opts) {
|
|
7410
|
-
const { config, identityManager, masterKey, validityMs } = opts;
|
|
7411
|
-
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
7412
|
-
if (!identity) {
|
|
7413
|
-
return "No identity available for signing. Create an identity first.";
|
|
7414
|
-
}
|
|
7415
|
-
const now = /* @__PURE__ */ new Date();
|
|
7416
|
-
const expiresAt = new Date(now.getTime() + (validityMs ?? DEFAULT_VALIDITY_MS));
|
|
7417
|
-
const degradations = [];
|
|
7418
|
-
if (config.execution.environment === "local-process") {
|
|
7419
|
-
degradations.push({
|
|
7420
|
-
layer: "l2",
|
|
7421
|
-
code: "PROCESS_ISOLATION_ONLY",
|
|
7422
|
-
severity: "warning",
|
|
7423
|
-
description: "Process-level isolation only (no TEE)",
|
|
7424
|
-
mitigation: "TEE support planned for a future release"
|
|
7425
|
-
});
|
|
7426
|
-
degradations.push({
|
|
7427
|
-
layer: "l2",
|
|
7428
|
-
code: "SELF_REPORTED_ATTESTATION",
|
|
7429
|
-
severity: "warning",
|
|
7430
|
-
description: "Attestation is self-reported (no hardware root of trust)",
|
|
7431
|
-
mitigation: "TEE attestation planned for a future release"
|
|
7432
|
-
});
|
|
7433
|
-
}
|
|
7434
|
-
const body = {
|
|
7435
|
-
shr_version: "1.0",
|
|
7436
|
-
implementation: {
|
|
7437
|
-
sanctuary_version: config.version,
|
|
7438
|
-
node_version: process.versions.node,
|
|
7439
|
-
generated_by: "sanctuary-mcp-server"
|
|
7440
|
-
},
|
|
7441
|
-
instance_id: identity.identity_id,
|
|
7442
|
-
generated_at: now.toISOString(),
|
|
7443
|
-
expires_at: expiresAt.toISOString(),
|
|
7444
|
-
layers: {
|
|
7445
|
-
l1: {
|
|
7446
|
-
status: "active",
|
|
7447
|
-
encryption: config.state.encryption,
|
|
7448
|
-
key_custody: "self",
|
|
7449
|
-
integrity: config.state.integrity,
|
|
7450
|
-
identity_type: config.state.identity_provider,
|
|
7451
|
-
state_portable: true
|
|
7452
|
-
},
|
|
7453
|
-
l2: {
|
|
7454
|
-
status: config.execution.environment === "local-process" ? "degraded" : "active",
|
|
7455
|
-
isolation_type: config.execution.environment,
|
|
7456
|
-
attestation_available: config.execution.attestation
|
|
7457
|
-
},
|
|
7458
|
-
l3: {
|
|
7459
|
-
status: "active",
|
|
7460
|
-
proof_system: config.disclosure.proof_system,
|
|
7461
|
-
selective_disclosure: true
|
|
7462
|
-
},
|
|
7463
|
-
l4: {
|
|
7464
|
-
status: "active",
|
|
7465
|
-
reputation_mode: config.reputation.mode,
|
|
7466
|
-
attestation_format: config.reputation.attestation_format,
|
|
7467
|
-
reputation_portable: true
|
|
7468
|
-
}
|
|
7469
|
-
},
|
|
7470
|
-
capabilities: {
|
|
7471
|
-
handshake: true,
|
|
7472
|
-
shr_exchange: true,
|
|
7473
|
-
reputation_verify: true,
|
|
7474
|
-
encrypted_channel: false
|
|
7475
|
-
// Not yet implemented
|
|
7476
|
-
},
|
|
7477
|
-
degradations
|
|
7478
|
-
};
|
|
7479
|
-
const canonical = canonicalizeForSigning(body);
|
|
7480
|
-
const payload = stringToBytes(canonical);
|
|
7481
|
-
const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
7482
|
-
const signatureBytes = sign(
|
|
7483
|
-
payload,
|
|
7484
|
-
identity.encrypted_private_key,
|
|
7485
|
-
encryptionKey
|
|
7486
|
-
);
|
|
7487
|
-
return {
|
|
7488
|
-
body,
|
|
7489
|
-
signed_by: identity.public_key,
|
|
7490
|
-
signature: toBase64url(signatureBytes)
|
|
7491
|
-
};
|
|
7492
|
-
}
|
|
7493
|
-
|
|
7494
7932
|
// src/shr/verifier.ts
|
|
7495
7933
|
init_encoding();
|
|
7496
7934
|
function verifySHR(shr, now) {
|
|
@@ -8080,6 +8518,154 @@ function deriveTrustTier(level) {
|
|
|
8080
8518
|
}
|
|
8081
8519
|
}
|
|
8082
8520
|
|
|
8521
|
+
// src/handshake/attestation.ts
|
|
8522
|
+
init_encoding();
|
|
8523
|
+
init_encoding();
|
|
8524
|
+
var ATTESTATION_VERSION = "1.0";
|
|
8525
|
+
function deriveTrustTier2(level) {
|
|
8526
|
+
switch (level) {
|
|
8527
|
+
case "full":
|
|
8528
|
+
return "verified-sovereign";
|
|
8529
|
+
case "degraded":
|
|
8530
|
+
return "verified-degraded";
|
|
8531
|
+
default:
|
|
8532
|
+
return "unverified";
|
|
8533
|
+
}
|
|
8534
|
+
}
|
|
8535
|
+
function generateAttestation(opts) {
|
|
8536
|
+
const {
|
|
8537
|
+
attesterSHR,
|
|
8538
|
+
subjectSHR,
|
|
8539
|
+
verificationResult,
|
|
8540
|
+
mutual = false,
|
|
8541
|
+
identityManager,
|
|
8542
|
+
masterKey,
|
|
8543
|
+
identityId
|
|
8544
|
+
} = opts;
|
|
8545
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
8546
|
+
if (!identity) {
|
|
8547
|
+
return { error: "No identity available for signing attestation" };
|
|
8548
|
+
}
|
|
8549
|
+
const now = /* @__PURE__ */ new Date();
|
|
8550
|
+
const attesterExpiry = new Date(attesterSHR.body.expires_at);
|
|
8551
|
+
const subjectExpiry = new Date(subjectSHR.body.expires_at);
|
|
8552
|
+
const earliestExpiry = attesterExpiry < subjectExpiry ? attesterExpiry : subjectExpiry;
|
|
8553
|
+
const sovereigntyLevel = verificationResult.valid ? verificationResult.sovereignty_level : "unverified";
|
|
8554
|
+
const body = {
|
|
8555
|
+
attestation_version: ATTESTATION_VERSION,
|
|
8556
|
+
attester_id: attesterSHR.body.instance_id,
|
|
8557
|
+
subject_id: subjectSHR.body.instance_id,
|
|
8558
|
+
attester_shr: attesterSHR,
|
|
8559
|
+
subject_shr: subjectSHR,
|
|
8560
|
+
verification: {
|
|
8561
|
+
subject_shr_valid: verificationResult.valid,
|
|
8562
|
+
subject_sovereignty_level: sovereigntyLevel,
|
|
8563
|
+
subject_trust_tier: deriveTrustTier2(sovereigntyLevel),
|
|
8564
|
+
mutual,
|
|
8565
|
+
errors: verificationResult.errors,
|
|
8566
|
+
warnings: verificationResult.warnings
|
|
8567
|
+
},
|
|
8568
|
+
attested_at: now.toISOString(),
|
|
8569
|
+
expires_at: earliestExpiry.toISOString()
|
|
8570
|
+
};
|
|
8571
|
+
const canonical = JSON.stringify(deepSortKeys(body));
|
|
8572
|
+
const payload = stringToBytes(canonical);
|
|
8573
|
+
const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
8574
|
+
const signatureBytes = sign(
|
|
8575
|
+
payload,
|
|
8576
|
+
identity.encrypted_private_key,
|
|
8577
|
+
encryptionKey
|
|
8578
|
+
);
|
|
8579
|
+
const summary = generateSummary(body);
|
|
8580
|
+
return {
|
|
8581
|
+
body,
|
|
8582
|
+
signed_by: identity.public_key,
|
|
8583
|
+
signature: toBase64url(signatureBytes),
|
|
8584
|
+
summary
|
|
8585
|
+
};
|
|
8586
|
+
}
|
|
8587
|
+
function layerLine(label, status) {
|
|
8588
|
+
const icon = status === "active" ? "\u2713" : status === "degraded" ? "~" : "x";
|
|
8589
|
+
return ` ${icon} ${label}: ${status}`;
|
|
8590
|
+
}
|
|
8591
|
+
function generateSummary(body) {
|
|
8592
|
+
const v = body.verification;
|
|
8593
|
+
const sLayers = body.subject_shr.body.layers;
|
|
8594
|
+
const aLayers = body.attester_shr.body.layers;
|
|
8595
|
+
const tierLabel = v.subject_trust_tier === "verified-sovereign" ? "Verified Sovereign" : v.subject_trust_tier === "verified-degraded" ? "Verified (Degraded)" : "Unverified";
|
|
8596
|
+
const lines = [
|
|
8597
|
+
`--- Sovereignty Attestation ---`,
|
|
8598
|
+
``,
|
|
8599
|
+
`Attester: ${body.attester_id.slice(0, 16)}...`,
|
|
8600
|
+
`Subject: ${body.subject_id.slice(0, 16)}...`,
|
|
8601
|
+
`Result: ${tierLabel}`,
|
|
8602
|
+
``,
|
|
8603
|
+
`Subject Sovereignty Posture:`,
|
|
8604
|
+
layerLine("L1 Cognitive Sovereignty", sLayers.l1.status),
|
|
8605
|
+
layerLine("L2 Operational Isolation", sLayers.l2.status),
|
|
8606
|
+
layerLine("L3 Selective Disclosure", sLayers.l3.status),
|
|
8607
|
+
layerLine("L4 Verifiable Reputation", sLayers.l4.status),
|
|
8608
|
+
``,
|
|
8609
|
+
`Attester Sovereignty Posture:`,
|
|
8610
|
+
layerLine("L1 Cognitive Sovereignty", aLayers.l1.status),
|
|
8611
|
+
layerLine("L2 Operational Isolation", aLayers.l2.status),
|
|
8612
|
+
layerLine("L3 Selective Disclosure", aLayers.l3.status),
|
|
8613
|
+
layerLine("L4 Verifiable Reputation", aLayers.l4.status),
|
|
8614
|
+
``,
|
|
8615
|
+
`Mutual: ${v.mutual ? "Yes" : "One-sided"}`,
|
|
8616
|
+
`Attested: ${body.attested_at}`,
|
|
8617
|
+
`Expires: ${body.expires_at}`,
|
|
8618
|
+
`Signature: ${body.attestation_version} / Ed25519`
|
|
8619
|
+
];
|
|
8620
|
+
if (v.warnings.length > 0) {
|
|
8621
|
+
lines.push(``, `Warnings: ${v.warnings.join("; ")}`);
|
|
8622
|
+
}
|
|
8623
|
+
if (v.errors.length > 0) {
|
|
8624
|
+
lines.push(``, `Errors: ${v.errors.join("; ")}`);
|
|
8625
|
+
}
|
|
8626
|
+
lines.push(``, `--- Verify: compare signed_by against attester's known public key ---`);
|
|
8627
|
+
return lines.join("\n");
|
|
8628
|
+
}
|
|
8629
|
+
function verifyAttestation(attestation, now) {
|
|
8630
|
+
const errors = [];
|
|
8631
|
+
const currentTime = now ?? /* @__PURE__ */ new Date();
|
|
8632
|
+
if (attestation.body.attestation_version !== ATTESTATION_VERSION) {
|
|
8633
|
+
errors.push(
|
|
8634
|
+
`Unsupported attestation version: ${attestation.body.attestation_version}`
|
|
8635
|
+
);
|
|
8636
|
+
}
|
|
8637
|
+
if (!attestation.body.attester_id || !attestation.body.subject_id) {
|
|
8638
|
+
errors.push("Missing attester_id or subject_id");
|
|
8639
|
+
}
|
|
8640
|
+
if (!attestation.body.attester_shr || !attestation.body.subject_shr) {
|
|
8641
|
+
errors.push("Missing attester or subject SHR");
|
|
8642
|
+
}
|
|
8643
|
+
const expired = new Date(attestation.body.expires_at) <= currentTime;
|
|
8644
|
+
if (expired) {
|
|
8645
|
+
errors.push("Attestation has expired");
|
|
8646
|
+
}
|
|
8647
|
+
try {
|
|
8648
|
+
const publicKey = fromBase64url(attestation.signed_by);
|
|
8649
|
+
const canonical = JSON.stringify(deepSortKeys(attestation.body));
|
|
8650
|
+
const payload = stringToBytes(canonical);
|
|
8651
|
+
const signatureBytes = fromBase64url(attestation.signature);
|
|
8652
|
+
const signatureValid = verify(payload, signatureBytes, publicKey);
|
|
8653
|
+
if (!signatureValid) {
|
|
8654
|
+
errors.push("Attestation signature is invalid");
|
|
8655
|
+
}
|
|
8656
|
+
} catch (e) {
|
|
8657
|
+
errors.push(`Signature verification error: ${e.message}`);
|
|
8658
|
+
}
|
|
8659
|
+
return {
|
|
8660
|
+
valid: errors.length === 0,
|
|
8661
|
+
errors,
|
|
8662
|
+
attester_id: attestation.body.attester_id ?? "unknown",
|
|
8663
|
+
subject_id: attestation.body.subject_id ?? "unknown",
|
|
8664
|
+
trust_tier: errors.length === 0 ? attestation.body.verification.subject_trust_tier : "unverified",
|
|
8665
|
+
expired
|
|
8666
|
+
};
|
|
8667
|
+
}
|
|
8668
|
+
|
|
8083
8669
|
// src/handshake/tools.ts
|
|
8084
8670
|
function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
8085
8671
|
const sessions = /* @__PURE__ */ new Map();
|
|
@@ -8265,6 +8851,103 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
|
8265
8851
|
result: session.result ?? null
|
|
8266
8852
|
});
|
|
8267
8853
|
}
|
|
8854
|
+
},
|
|
8855
|
+
// ─── Streamlined Exchange ─────────────────────────────────────────
|
|
8856
|
+
{
|
|
8857
|
+
name: "sanctuary/handshake_exchange",
|
|
8858
|
+
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).",
|
|
8859
|
+
inputSchema: {
|
|
8860
|
+
type: "object",
|
|
8861
|
+
properties: {
|
|
8862
|
+
counterparty_shr: {
|
|
8863
|
+
type: "object",
|
|
8864
|
+
description: "The counterparty's signed SHR (SignedSHR object with body, signed_by, signature)."
|
|
8865
|
+
},
|
|
8866
|
+
identity_id: {
|
|
8867
|
+
type: "string",
|
|
8868
|
+
description: "Identity to use for the exchange. Defaults to primary identity."
|
|
8869
|
+
}
|
|
8870
|
+
},
|
|
8871
|
+
required: ["counterparty_shr"]
|
|
8872
|
+
},
|
|
8873
|
+
handler: async (args) => {
|
|
8874
|
+
const counterpartySHR = args.counterparty_shr;
|
|
8875
|
+
const ourSHR = generateSHR(args.identity_id, shrOpts);
|
|
8876
|
+
if (typeof ourSHR === "string") {
|
|
8877
|
+
return toolResult({ error: ourSHR });
|
|
8878
|
+
}
|
|
8879
|
+
const verificationResult = verifySHR(counterpartySHR);
|
|
8880
|
+
const attestation = generateAttestation({
|
|
8881
|
+
attesterSHR: ourSHR,
|
|
8882
|
+
subjectSHR: counterpartySHR,
|
|
8883
|
+
verificationResult,
|
|
8884
|
+
mutual: false,
|
|
8885
|
+
identityManager,
|
|
8886
|
+
masterKey,
|
|
8887
|
+
identityId: args.identity_id
|
|
8888
|
+
});
|
|
8889
|
+
if ("error" in attestation) {
|
|
8890
|
+
auditLog.append("l4", "handshake_exchange", ourSHR.body.instance_id, void 0, "failure");
|
|
8891
|
+
return toolResult({ error: attestation.error });
|
|
8892
|
+
}
|
|
8893
|
+
if (verificationResult.valid) {
|
|
8894
|
+
const sovereigntyLevel = verificationResult.sovereignty_level;
|
|
8895
|
+
const trustTier = sovereigntyLevel === "full" ? "verified-sovereign" : sovereigntyLevel === "degraded" ? "verified-degraded" : "unverified";
|
|
8896
|
+
handshakeResults.set(verificationResult.counterparty_id, {
|
|
8897
|
+
counterparty_id: verificationResult.counterparty_id,
|
|
8898
|
+
counterparty_shr: counterpartySHR,
|
|
8899
|
+
verified: true,
|
|
8900
|
+
sovereignty_level: sovereigntyLevel,
|
|
8901
|
+
trust_tier: trustTier,
|
|
8902
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8903
|
+
expires_at: verificationResult.expires_at,
|
|
8904
|
+
errors: []
|
|
8905
|
+
});
|
|
8906
|
+
}
|
|
8907
|
+
auditLog.append("l4", "handshake_exchange", ourSHR.body.instance_id);
|
|
8908
|
+
return toolResult({
|
|
8909
|
+
attestation,
|
|
8910
|
+
our_shr: ourSHR,
|
|
8911
|
+
verification: {
|
|
8912
|
+
counterparty_valid: verificationResult.valid,
|
|
8913
|
+
counterparty_sovereignty: verificationResult.sovereignty_level,
|
|
8914
|
+
counterparty_id: verificationResult.counterparty_id,
|
|
8915
|
+
errors: verificationResult.errors,
|
|
8916
|
+
warnings: verificationResult.warnings
|
|
8917
|
+
},
|
|
8918
|
+
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.",
|
|
8919
|
+
_content_trust: "external"
|
|
8920
|
+
});
|
|
8921
|
+
}
|
|
8922
|
+
},
|
|
8923
|
+
{
|
|
8924
|
+
name: "sanctuary/handshake_verify_attestation",
|
|
8925
|
+
description: "Verify a signed attestation artifact from another agent. Checks the Ed25519 signature, temporal validity, and structural integrity.",
|
|
8926
|
+
inputSchema: {
|
|
8927
|
+
type: "object",
|
|
8928
|
+
properties: {
|
|
8929
|
+
attestation: {
|
|
8930
|
+
type: "object",
|
|
8931
|
+
description: "The SignedAttestation object to verify (body, signed_by, signature, summary)."
|
|
8932
|
+
}
|
|
8933
|
+
},
|
|
8934
|
+
required: ["attestation"]
|
|
8935
|
+
},
|
|
8936
|
+
handler: async (args) => {
|
|
8937
|
+
const attestation = args.attestation;
|
|
8938
|
+
const result = verifyAttestation(attestation);
|
|
8939
|
+
auditLog.append(
|
|
8940
|
+
"l4",
|
|
8941
|
+
"handshake_verify_attestation",
|
|
8942
|
+
result.attester_id,
|
|
8943
|
+
void 0,
|
|
8944
|
+
result.valid ? "success" : "failure"
|
|
8945
|
+
);
|
|
8946
|
+
return toolResult({
|
|
8947
|
+
...result,
|
|
8948
|
+
_content_trust: "external"
|
|
8949
|
+
});
|
|
8950
|
+
}
|
|
8268
8951
|
}
|
|
8269
8952
|
];
|
|
8270
8953
|
return { tools, handshakeResults };
|
|
@@ -12116,7 +12799,15 @@ async function createSanctuaryServer(options) {
|
|
|
12116
12799
|
tls: config.dashboard.tls,
|
|
12117
12800
|
auto_open: config.dashboard.auto_open
|
|
12118
12801
|
});
|
|
12119
|
-
dashboard.setDependencies({
|
|
12802
|
+
dashboard.setDependencies({
|
|
12803
|
+
policy,
|
|
12804
|
+
baseline,
|
|
12805
|
+
auditLog,
|
|
12806
|
+
identityManager,
|
|
12807
|
+
handshakeResults,
|
|
12808
|
+
shrOpts: { config, identityManager, masterKey },
|
|
12809
|
+
sanctuaryConfig: config
|
|
12810
|
+
});
|
|
12120
12811
|
await dashboard.start();
|
|
12121
12812
|
approvalChannel = dashboard;
|
|
12122
12813
|
} else if (config.webhook.enabled && config.webhook.url && config.webhook.secret) {
|
|
@@ -12222,6 +12913,7 @@ async function createSanctuaryServer(options) {
|
|
|
12222
12913
|
return { server, config };
|
|
12223
12914
|
}
|
|
12224
12915
|
|
|
12916
|
+
exports.ATTESTATION_VERSION = ATTESTATION_VERSION;
|
|
12225
12917
|
exports.ApprovalGate = ApprovalGate;
|
|
12226
12918
|
exports.AuditLog = AuditLog;
|
|
12227
12919
|
exports.AutoApproveChannel = AutoApproveChannel;
|
|
@@ -12253,6 +12945,7 @@ exports.createRangeProof = createRangeProof;
|
|
|
12253
12945
|
exports.createSanctuaryServer = createSanctuaryServer;
|
|
12254
12946
|
exports.evaluateField = evaluateField;
|
|
12255
12947
|
exports.filterContext = filterContext;
|
|
12948
|
+
exports.generateAttestation = generateAttestation;
|
|
12256
12949
|
exports.generateSHR = generateSHR;
|
|
12257
12950
|
exports.getTemplate = getTemplate;
|
|
12258
12951
|
exports.initiateHandshake = initiateHandshake;
|
|
@@ -12264,6 +12957,7 @@ exports.resolveTier = resolveTier;
|
|
|
12264
12957
|
exports.respondToHandshake = respondToHandshake;
|
|
12265
12958
|
exports.signPayload = signPayload;
|
|
12266
12959
|
exports.tierDistribution = tierDistribution;
|
|
12960
|
+
exports.verifyAttestation = verifyAttestation;
|
|
12267
12961
|
exports.verifyBridgeCommitment = verifyBridgeCommitment;
|
|
12268
12962
|
exports.verifyCompletion = verifyCompletion;
|
|
12269
12963
|
exports.verifyPedersenCommitment = verifyPedersenCommitment;
|