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