@sanctuary-framework/mcp-server 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -228,7 +228,7 @@ function defaultConfig() {
228
228
  }
229
229
  },
230
230
  disclosure: {
231
- proof_system: "commitment-only",
231
+ proof_system: "schnorr-pedersen",
232
232
  default_policy: "minimum-necessary"
233
233
  },
234
234
  reputation: {
@@ -342,7 +342,7 @@ function validateConfig(config) {
342
342
  `Unimplemented config value: execution.environment = "${config.execution.environment}". Only ${[...implementedEnvironment].map((v) => `"${v}"`).join(", ")} are currently implemented. Using an unimplemented environment would silently degrade security.`
343
343
  );
344
344
  }
345
- const implementedProofSystem = /* @__PURE__ */ new Set(["commitment-only"]);
345
+ const implementedProofSystem = /* @__PURE__ */ new Set(["schnorr-pedersen", "commitment-only"]);
346
346
  if (!implementedProofSystem.has(config.disclosure.proof_system)) {
347
347
  errors.push(
348
348
  `Unimplemented config value: disclosure.proof_system = "${config.disclosure.proof_system}". Only ${[...implementedProofSystem].map((v) => `"${v}"`).join(", ")} is currently implemented. Using an unimplemented proof system would silently degrade security.`
@@ -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
@@ -4065,174 +4069,255 @@ function generateLoginHTML(options) {
4065
4069
  return `<!DOCTYPE html>
4066
4070
  <html lang="en">
4067
4071
  <head>
4068
- <meta charset="utf-8">
4069
- <meta name="viewport" content="width=device-width, initial-scale=1">
4070
- <title>Sanctuary \u2014 Login</title>
4071
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
4072
- <style>
4073
- :root {
4074
- --bg: #0d1117;
4075
- --surface: #161b22;
4076
- --border: #30363d;
4077
- --text-primary: #e6edf3;
4078
- --text-secondary: #8b949e;
4079
- --green: #3fb950;
4080
- --red: #f85149;
4081
- --blue: #58a6ff;
4082
- --mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
4083
- --sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
4084
- --radius: 6px;
4085
- }
4086
- * { box-sizing: border-box; margin: 0; padding: 0; }
4087
- html, body { width: 100%; height: 100%; }
4088
- body {
4089
- font-family: var(--sans);
4090
- background: var(--bg);
4091
- color: var(--text-primary);
4092
- display: flex;
4093
- align-items: center;
4094
- justify-content: center;
4095
- }
4096
- .login-container {
4097
- width: 100%;
4098
- max-width: 400px;
4099
- padding: 40px 32px;
4100
- background: var(--surface);
4101
- border: 1px solid var(--border);
4102
- border-radius: 12px;
4103
- }
4104
- .login-logo {
4105
- text-align: center;
4106
- font-size: 20px;
4107
- font-weight: 700;
4108
- letter-spacing: -0.5px;
4109
- margin-bottom: 8px;
4110
- }
4111
- .login-logo span { color: var(--blue); }
4112
- .login-version {
4113
- text-align: center;
4114
- font-size: 11px;
4115
- color: var(--text-secondary);
4116
- font-family: var(--mono);
4117
- margin-bottom: 32px;
4118
- }
4119
- .login-label {
4120
- display: block;
4121
- font-size: 13px;
4122
- font-weight: 600;
4123
- color: var(--text-secondary);
4124
- margin-bottom: 8px;
4125
- }
4126
- .login-input {
4127
- width: 100%;
4128
- padding: 10px 14px;
4129
- background: var(--bg);
4130
- border: 1px solid var(--border);
4131
- border-radius: var(--radius);
4132
- color: var(--text-primary);
4133
- font-family: var(--mono);
4134
- font-size: 14px;
4135
- outline: none;
4136
- transition: border-color 0.15s;
4137
- }
4138
- .login-input:focus { border-color: var(--blue); }
4139
- .login-input::placeholder { color: var(--text-secondary); opacity: 0.5; }
4140
- .login-btn {
4141
- width: 100%;
4142
- margin-top: 20px;
4143
- padding: 10px;
4144
- background: var(--blue);
4145
- color: var(--bg);
4146
- border: none;
4147
- border-radius: var(--radius);
4148
- font-size: 14px;
4149
- font-weight: 600;
4150
- cursor: pointer;
4151
- transition: opacity 0.15s;
4152
- font-family: var(--sans);
4153
- }
4154
- .login-btn:hover { opacity: 0.9; }
4155
- .login-btn:disabled { opacity: 0.5; cursor: not-allowed; }
4156
- .login-error {
4157
- margin-top: 16px;
4158
- padding: 10px 14px;
4159
- background: rgba(248, 81, 73, 0.1);
4160
- border: 1px solid var(--red);
4161
- border-radius: var(--radius);
4162
- font-size: 12px;
4163
- color: var(--red);
4164
- display: none;
4165
- }
4166
- .login-hint {
4167
- margin-top: 24px;
4168
- padding-top: 16px;
4169
- border-top: 1px solid var(--border);
4170
- font-size: 11px;
4171
- color: var(--text-secondary);
4172
- line-height: 1.5;
4173
- }
4174
- .login-hint code {
4175
- font-family: var(--mono);
4176
- background: var(--bg);
4177
- padding: 1px 4px;
4178
- border-radius: 3px;
4179
- font-size: 10px;
4180
- }
4181
- </style>
4072
+ <meta charset="UTF-8">
4073
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4074
+ <title>Sanctuary Dashboard</title>
4075
+ <style>
4076
+ :root {
4077
+ --bg: #0d1117;
4078
+ --surface: #161b22;
4079
+ --border: #30363d;
4080
+ --text-primary: #e6edf3;
4081
+ --text-secondary: #8b949e;
4082
+ --green: #3fb950;
4083
+ --amber: #d29922;
4084
+ --red: #f85149;
4085
+ --blue: #58a6ff;
4086
+ }
4087
+
4088
+ * {
4089
+ margin: 0;
4090
+ padding: 0;
4091
+ box-sizing: border-box;
4092
+ }
4093
+
4094
+ body {
4095
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
4096
+ background-color: var(--bg);
4097
+ color: var(--text-primary);
4098
+ min-height: 100vh;
4099
+ display: flex;
4100
+ align-items: center;
4101
+ justify-content: center;
4102
+ }
4103
+
4104
+ .login-container {
4105
+ width: 100%;
4106
+ max-width: 400px;
4107
+ padding: 20px;
4108
+ }
4109
+
4110
+ .login-card {
4111
+ background-color: var(--surface);
4112
+ border: 1px solid var(--border);
4113
+ border-radius: 8px;
4114
+ padding: 40px 32px;
4115
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
4116
+ }
4117
+
4118
+ .login-header {
4119
+ display: flex;
4120
+ align-items: center;
4121
+ gap: 12px;
4122
+ margin-bottom: 32px;
4123
+ }
4124
+
4125
+ .logo {
4126
+ font-size: 24px;
4127
+ font-weight: 700;
4128
+ color: var(--blue);
4129
+ }
4130
+
4131
+ .logo-text {
4132
+ display: flex;
4133
+ flex-direction: column;
4134
+ }
4135
+
4136
+ .logo-text .title {
4137
+ font-size: 18px;
4138
+ font-weight: 600;
4139
+ letter-spacing: -0.5px;
4140
+ }
4141
+
4142
+ .logo-text .version {
4143
+ font-size: 12px;
4144
+ color: var(--text-secondary);
4145
+ margin-top: 2px;
4146
+ }
4147
+
4148
+ .form-group {
4149
+ margin-bottom: 24px;
4150
+ }
4151
+
4152
+ label {
4153
+ display: block;
4154
+ font-size: 14px;
4155
+ font-weight: 500;
4156
+ margin-bottom: 8px;
4157
+ color: var(--text-primary);
4158
+ }
4159
+
4160
+ input[type="text"],
4161
+ input[type="password"] {
4162
+ width: 100%;
4163
+ padding: 10px 12px;
4164
+ background-color: var(--bg);
4165
+ border: 1px solid var(--border);
4166
+ border-radius: 6px;
4167
+ color: var(--text-primary);
4168
+ font-size: 14px;
4169
+ font-family: 'JetBrains Mono', monospace;
4170
+ transition: border-color 0.2s;
4171
+ }
4172
+
4173
+ input[type="text"]:focus,
4174
+ input[type="password"]:focus {
4175
+ outline: none;
4176
+ border-color: var(--blue);
4177
+ box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.1);
4178
+ }
4179
+
4180
+ .error-message {
4181
+ display: none;
4182
+ background-color: rgba(248, 81, 73, 0.1);
4183
+ border: 1px solid var(--red);
4184
+ color: #ff9999;
4185
+ padding: 12px;
4186
+ border-radius: 6px;
4187
+ font-size: 13px;
4188
+ margin-bottom: 20px;
4189
+ }
4190
+
4191
+ .error-message.show {
4192
+ display: block;
4193
+ }
4194
+
4195
+ button {
4196
+ width: 100%;
4197
+ padding: 10px 16px;
4198
+ background-color: var(--blue);
4199
+ color: var(--bg);
4200
+ border: none;
4201
+ border-radius: 6px;
4202
+ font-size: 14px;
4203
+ font-weight: 600;
4204
+ cursor: pointer;
4205
+ transition: background-color 0.2s;
4206
+ }
4207
+
4208
+ button:hover {
4209
+ background-color: #79c0ff;
4210
+ }
4211
+
4212
+ button:active {
4213
+ background-color: #4184e4;
4214
+ }
4215
+
4216
+ button:disabled {
4217
+ background-color: var(--text-secondary);
4218
+ cursor: not-allowed;
4219
+ opacity: 0.5;
4220
+ }
4221
+
4222
+ .info-text {
4223
+ font-size: 12px;
4224
+ color: var(--text-secondary);
4225
+ margin-top: 16px;
4226
+ text-align: center;
4227
+ }
4228
+ </style>
4182
4229
  </head>
4183
4230
  <body>
4184
- <div class="login-container">
4185
- <div class="login-logo"><span>&#9670;</span> SANCTUARY</div>
4186
- <div class="login-version">Principal Dashboard v${options.serverVersion}</div>
4187
- <form id="loginForm" onsubmit="return handleLogin(event)">
4188
- <label class="login-label" for="tokenInput">Dashboard Auth Token</label>
4189
- <input class="login-input" type="password" id="tokenInput"
4190
- placeholder="Enter your auth token" autocomplete="off" autofocus required>
4191
- <button class="login-btn" type="submit" id="loginBtn">Open Dashboard</button>
4192
- </form>
4193
- <div class="login-error" id="loginError"></div>
4194
- <div class="login-hint">
4195
- Your token is set via <code>SANCTUARY_DASHBOARD_AUTH_TOKEN</code> environment variable,
4196
- or check your server's startup output.
4231
+ <div class="login-container">
4232
+ <div class="login-card">
4233
+ <div class="login-header">
4234
+ <div class="logo">\u25C6</div>
4235
+ <div class="logo-text">
4236
+ <div class="title">SANCTUARY</div>
4237
+ <div class="version">v${options.serverVersion}</div>
4238
+ </div>
4239
+ </div>
4240
+
4241
+ <div id="error-message" class="error-message"></div>
4242
+
4243
+ <form id="login-form">
4244
+ <div class="form-group">
4245
+ <label for="auth-token">Bearer Token</label>
4246
+ <input
4247
+ type="text"
4248
+ id="auth-token"
4249
+ name="token"
4250
+ placeholder="Paste your session token..."
4251
+ autocomplete="off"
4252
+ spellcheck="false"
4253
+ required
4254
+ />
4255
+ </div>
4256
+
4257
+ <button type="submit" id="login-button">Open Dashboard</button>
4258
+ </form>
4259
+
4260
+ <div class="info-text">
4261
+ Session tokens expire after 1 hour of inactivity
4262
+ </div>
4263
+ </div>
4197
4264
  </div>
4198
- </div>
4199
- <script>
4200
- async function handleLogin(e) {
4201
- e.preventDefault();
4202
- var btn = document.getElementById('loginBtn');
4203
- var errEl = document.getElementById('loginError');
4204
- var token = document.getElementById('tokenInput').value.trim();
4205
- if (!token) return false;
4206
- btn.disabled = true;
4207
- btn.textContent = 'Authenticating...';
4208
- errEl.style.display = 'none';
4209
- try {
4210
- var resp = await fetch('/auth/session', {
4211
- method: 'POST',
4212
- headers: { 'Authorization': 'Bearer ' + token }
4265
+
4266
+ <script>
4267
+ const loginForm = document.getElementById('login-form');
4268
+ const authTokenInput = document.getElementById('auth-token');
4269
+ const errorMessage = document.getElementById('error-message');
4270
+ const loginButton = document.getElementById('login-button');
4271
+
4272
+ loginForm.addEventListener('submit', async (e) => {
4273
+ e.preventDefault();
4274
+ const token = authTokenInput.value.trim();
4275
+
4276
+ if (!token) {
4277
+ showError('Token is required');
4278
+ return;
4279
+ }
4280
+
4281
+ loginButton.disabled = true;
4282
+ loginButton.textContent = 'Verifying...';
4283
+ errorMessage.classList.remove('show');
4284
+
4285
+ try {
4286
+ const response = await fetch('/auth/session', {
4287
+ method: 'POST',
4288
+ headers: {
4289
+ 'Content-Type': 'application/json',
4290
+ 'Authorization': 'Bearer ' + token,
4291
+ },
4292
+ body: JSON.stringify({ token }),
4293
+ });
4294
+
4295
+ if (response.ok) {
4296
+ const data = await response.json();
4297
+ sessionStorage.setItem('authToken', token);
4298
+ window.location.href = '/dashboard';
4299
+ } else if (response.status === 401) {
4300
+ showError('Invalid token. Please check and try again.');
4301
+ } else {
4302
+ showError('Authentication failed. Please try again.');
4303
+ }
4304
+ } catch (err) {
4305
+ showError('Connection error. Please check your network.');
4306
+ } finally {
4307
+ loginButton.disabled = false;
4308
+ loginButton.textContent = 'Open Dashboard';
4309
+ }
4213
4310
  });
4214
- if (!resp.ok) {
4215
- var data = await resp.json().catch(function() { return {}; });
4216
- throw new Error(data.error || 'Authentication failed');
4217
- }
4218
- var result = await resp.json();
4219
- // Store token in sessionStorage for auto-renewal inside the dashboard
4220
- try { sessionStorage.setItem('sanctuary_token', token); } catch(_) {}
4221
- // Set session cookie
4222
- var maxAge = result.expires_in_seconds || 300;
4223
- document.cookie = 'sanctuary_session=' + result.session_id +
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>
4311
+
4312
+ function showError(message) {
4313
+ errorMessage.textContent = message;
4314
+ errorMessage.classList.add('show');
4315
+ }
4316
+
4317
+ authTokenInput.addEventListener('input', () => {
4318
+ errorMessage.classList.remove('show');
4319
+ });
4320
+ </script>
4236
4321
  </body>
4237
4322
  </html>`;
4238
4323
  }
@@ -4240,1412 +4325,1648 @@ function generateDashboardHTML(options) {
4240
4325
  return `<!DOCTYPE html>
4241
4326
  <html lang="en">
4242
4327
  <head>
4243
- <meta charset="utf-8">
4244
- <meta name="viewport" content="width=device-width, initial-scale=1">
4245
- <title>Sanctuary \u2014 Principal Dashboard</title>
4246
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
4247
- <style>
4248
- :root {
4249
- --bg: #0d1117;
4250
- --surface: #161b22;
4251
- --border: #30363d;
4252
- --text-primary: #e6edf3;
4253
- --text-secondary: #8b949e;
4254
- --green: #3fb950;
4255
- --amber: #d29922;
4256
- --red: #f85149;
4257
- --blue: #58a6ff;
4258
- --mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
4259
- --sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
4260
- --radius: 6px;
4261
- }
4262
-
4263
- * {
4264
- box-sizing: border-box;
4265
- margin: 0;
4266
- padding: 0;
4267
- }
4328
+ <meta charset="UTF-8">
4329
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4330
+ <title>Sanctuary Dashboard</title>
4331
+ <style>
4332
+ :root {
4333
+ --bg: #0d1117;
4334
+ --surface: #161b22;
4335
+ --border: #30363d;
4336
+ --text-primary: #e6edf3;
4337
+ --text-secondary: #8b949e;
4338
+ --green: #3fb950;
4339
+ --amber: #d29922;
4340
+ --red: #f85149;
4341
+ --blue: #58a6ff;
4342
+ --success: #3fb950;
4343
+ --warning: #d29922;
4344
+ --error: #f85149;
4345
+ --muted: #21262d;
4346
+ }
4347
+
4348
+ * {
4349
+ margin: 0;
4350
+ padding: 0;
4351
+ box-sizing: border-box;
4352
+ }
4353
+
4354
+ html, body {
4355
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
4356
+ background-color: var(--bg);
4357
+ color: var(--text-primary);
4358
+ height: 100%;
4359
+ overflow: hidden;
4360
+ }
4361
+
4362
+ body {
4363
+ display: flex;
4364
+ flex-direction: column;
4365
+ }
4366
+
4367
+ /* Status Bar */
4368
+ .status-bar {
4369
+ position: fixed;
4370
+ top: 0;
4371
+ left: 0;
4372
+ right: 0;
4373
+ height: 56px;
4374
+ background-color: var(--surface);
4375
+ border-bottom: 1px solid var(--border);
4376
+ display: flex;
4377
+ align-items: center;
4378
+ padding: 0 24px;
4379
+ gap: 24px;
4380
+ z-index: 100;
4381
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
4382
+ }
4383
+
4384
+ .status-bar-left {
4385
+ display: flex;
4386
+ align-items: center;
4387
+ gap: 12px;
4388
+ flex: 0 0 auto;
4389
+ }
4268
4390
 
4269
- html, body {
4270
- width: 100%;
4271
- height: 100%;
4272
- overflow: hidden;
4273
- }
4274
-
4275
- body {
4276
- font-family: var(--sans);
4277
- background: var(--bg);
4278
- color: var(--text-primary);
4279
- display: flex;
4280
- flex-direction: column;
4281
- }
4282
-
4283
- /* \u2500\u2500 Top Status Bar (fixed) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4391
+ .logo-icon {
4392
+ font-size: 20px;
4393
+ color: var(--blue);
4394
+ font-weight: 700;
4395
+ }
4284
4396
 
4285
- .status-bar {
4286
- position: fixed;
4287
- top: 0;
4288
- left: 0;
4289
- right: 0;
4290
- height: 56px;
4291
- background: var(--surface);
4292
- border-bottom: 1px solid var(--border);
4293
- display: flex;
4294
- align-items: center;
4295
- padding: 0 20px;
4296
- gap: 24px;
4297
- z-index: 1000;
4298
- }
4397
+ .logo-info {
4398
+ display: flex;
4399
+ flex-direction: column;
4400
+ }
4299
4401
 
4300
- .status-bar-left {
4301
- display: flex;
4302
- align-items: center;
4303
- gap: 12px;
4304
- flex: 0 0 auto;
4305
- }
4402
+ .logo-title {
4403
+ font-size: 13px;
4404
+ font-weight: 600;
4405
+ line-height: 1;
4406
+ color: var(--text-primary);
4407
+ }
4306
4408
 
4307
- .sanctuary-logo {
4308
- font-weight: 700;
4309
- font-size: 16px;
4310
- letter-spacing: -0.5px;
4311
- color: var(--text-primary);
4312
- }
4409
+ .logo-version {
4410
+ font-size: 11px;
4411
+ color: var(--text-secondary);
4412
+ margin-top: 2px;
4413
+ }
4313
4414
 
4314
- .sanctuary-logo span {
4315
- color: var(--blue);
4316
- }
4415
+ .status-bar-center {
4416
+ flex: 1;
4417
+ display: flex;
4418
+ justify-content: center;
4419
+ }
4317
4420
 
4318
- .version {
4319
- font-size: 11px;
4320
- color: var(--text-secondary);
4321
- font-family: var(--mono);
4322
- }
4421
+ .sovereignty-badge {
4422
+ display: flex;
4423
+ align-items: center;
4424
+ gap: 8px;
4425
+ padding: 8px 16px;
4426
+ background-color: rgba(63, 185, 80, 0.1);
4427
+ border: 1px solid rgba(63, 185, 80, 0.3);
4428
+ border-radius: 6px;
4429
+ font-size: 13px;
4430
+ font-weight: 500;
4431
+ }
4323
4432
 
4324
- .status-bar-center {
4325
- flex: 1;
4326
- display: flex;
4327
- align-items: center;
4328
- justify-content: center;
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
- }
4433
+ .sovereignty-badge.degraded {
4434
+ background-color: rgba(210, 153, 34, 0.1);
4435
+ border-color: rgba(210, 153, 34, 0.3);
4436
+ }
4342
4437
 
4343
- .sovereignty-score {
4344
- display: flex;
4345
- align-items: center;
4346
- justify-content: center;
4347
- width: 28px;
4348
- height: 28px;
4349
- border-radius: 50%;
4350
- font-family: var(--mono);
4351
- font-weight: 700;
4352
- font-size: 12px;
4353
- background: var(--blue);
4354
- color: var(--bg);
4355
- }
4438
+ .sovereignty-badge.inactive {
4439
+ background-color: rgba(248, 81, 73, 0.1);
4440
+ border-color: rgba(248, 81, 73, 0.3);
4441
+ }
4356
4442
 
4357
- .sovereignty-score.high {
4358
- background: var(--green);
4359
- }
4443
+ .sovereignty-score {
4444
+ font-weight: 700;
4445
+ color: var(--green);
4446
+ }
4360
4447
 
4361
- .sovereignty-score.medium {
4362
- background: var(--amber);
4363
- }
4448
+ .sovereignty-badge.degraded .sovereignty-score {
4449
+ color: var(--amber);
4450
+ }
4364
4451
 
4365
- .sovereignty-score.low {
4366
- background: var(--red);
4367
- }
4452
+ .sovereignty-badge.inactive .sovereignty-score {
4453
+ color: var(--red);
4454
+ }
4368
4455
 
4369
- .status-bar-right {
4370
- display: flex;
4371
- align-items: center;
4372
- gap: 16px;
4373
- flex: 0 0 auto;
4374
- }
4456
+ .status-bar-right {
4457
+ display: flex;
4458
+ align-items: center;
4459
+ gap: 16px;
4460
+ flex: 0 0 auto;
4461
+ }
4375
4462
 
4376
- .protections-indicator {
4377
- display: flex;
4378
- align-items: center;
4379
- gap: 6px;
4380
- font-size: 12px;
4381
- color: var(--text-secondary);
4382
- font-family: var(--mono);
4383
- }
4463
+ .status-item {
4464
+ display: flex;
4465
+ align-items: center;
4466
+ gap: 6px;
4467
+ font-size: 12px;
4468
+ color: var(--text-secondary);
4469
+ }
4384
4470
 
4385
- .protections-indicator .count {
4386
- color: var(--text-primary);
4387
- font-weight: 600;
4388
- }
4471
+ .status-item strong {
4472
+ color: var(--text-primary);
4473
+ font-weight: 500;
4474
+ }
4389
4475
 
4390
- .uptime {
4391
- display: flex;
4392
- align-items: center;
4393
- gap: 6px;
4394
- font-size: 12px;
4395
- color: var(--text-secondary);
4396
- font-family: var(--mono);
4397
- }
4476
+ .status-dot {
4477
+ width: 8px;
4478
+ height: 8px;
4479
+ border-radius: 50%;
4480
+ background-color: var(--green);
4481
+ }
4398
4482
 
4399
- .status-dot {
4400
- width: 8px;
4401
- height: 8px;
4402
- border-radius: 50%;
4403
- background: var(--green);
4404
- animation: pulse 2s ease-in-out infinite;
4405
- }
4483
+ .status-dot.disconnected {
4484
+ background-color: var(--red);
4485
+ }
4406
4486
 
4407
- .status-dot.disconnected {
4408
- background: var(--red);
4409
- animation: none;
4410
- }
4487
+ .pending-badge {
4488
+ display: flex;
4489
+ align-items: center;
4490
+ gap: 6px;
4491
+ padding: 4px 8px;
4492
+ background-color: var(--blue);
4493
+ color: var(--bg);
4494
+ border-radius: 4px;
4495
+ font-size: 11px;
4496
+ font-weight: 600;
4497
+ cursor: pointer;
4498
+ }
4411
4499
 
4412
- @keyframes pulse {
4413
- 0%, 100% { opacity: 1; }
4414
- 50% { opacity: 0.5; }
4415
- }
4500
+ /* Main Content */
4501
+ .main-content {
4502
+ flex: 1;
4503
+ margin-top: 56px;
4504
+ overflow-y: auto;
4505
+ padding: 24px;
4506
+ }
4416
4507
 
4417
- .pending-badge {
4418
- display: inline-flex;
4419
- align-items: center;
4420
- justify-content: center;
4421
- min-width: 24px;
4422
- height: 24px;
4423
- padding: 0 6px;
4424
- background: var(--red);
4425
- color: white;
4426
- border-radius: 12px;
4427
- font-size: 11px;
4428
- font-weight: 700;
4429
- animation: pulse 1s ease-in-out infinite;
4430
- }
4508
+ .grid {
4509
+ display: grid;
4510
+ gap: 20px;
4511
+ }
4431
4512
 
4432
- .pending-badge.hidden {
4433
- display: none;
4434
- }
4513
+ /* Row 1: Sovereignty Layers */
4514
+ .sovereignty-layers {
4515
+ display: grid;
4516
+ grid-template-columns: repeat(4, 1fr);
4517
+ gap: 16px;
4518
+ }
4435
4519
 
4436
- /* \u2500\u2500 Main Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4520
+ .layer-card {
4521
+ background-color: var(--surface);
4522
+ border: 1px solid var(--border);
4523
+ border-radius: 8px;
4524
+ padding: 20px;
4525
+ display: flex;
4526
+ flex-direction: column;
4527
+ gap: 12px;
4528
+ }
4437
4529
 
4438
- .main-container {
4439
- flex: 1;
4440
- display: flex;
4441
- margin-top: 56px;
4442
- overflow: hidden;
4443
- }
4530
+ .layer-card.degraded {
4531
+ border-color: var(--amber);
4532
+ background-color: rgba(210, 153, 34, 0.05);
4533
+ }
4444
4534
 
4445
- .activity-feed {
4446
- flex: 3;
4447
- display: flex;
4448
- flex-direction: column;
4449
- border-right: 1px solid var(--border);
4450
- overflow: hidden;
4451
- }
4535
+ .layer-card.inactive {
4536
+ border-color: var(--red);
4537
+ background-color: rgba(248, 81, 73, 0.05);
4538
+ }
4452
4539
 
4453
- .feed-header {
4454
- padding: 16px 20px;
4455
- border-bottom: 1px solid var(--border);
4456
- display: flex;
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
- }
4540
+ .layer-name {
4541
+ font-size: 12px;
4542
+ font-weight: 600;
4543
+ color: var(--text-secondary);
4544
+ text-transform: uppercase;
4545
+ letter-spacing: 0.5px;
4546
+ }
4465
4547
 
4466
- .feed-header-dot {
4467
- width: 6px;
4468
- height: 6px;
4469
- border-radius: 50%;
4470
- background: var(--green);
4471
- }
4548
+ .layer-title {
4549
+ font-size: 14px;
4550
+ font-weight: 600;
4551
+ color: var(--text-primary);
4552
+ }
4472
4553
 
4473
- .activity-list {
4474
- flex: 1;
4475
- overflow-y: auto;
4476
- overflow-x: hidden;
4477
- }
4554
+ .layer-status {
4555
+ display: inline-flex;
4556
+ align-items: center;
4557
+ gap: 6px;
4558
+ padding: 4px 8px;
4559
+ background-color: rgba(63, 185, 80, 0.15);
4560
+ color: var(--green);
4561
+ border-radius: 4px;
4562
+ font-size: 11px;
4563
+ font-weight: 600;
4564
+ width: fit-content;
4565
+ }
4478
4566
 
4479
- .activity-item {
4480
- padding: 12px 20px;
4481
- border-bottom: 1px solid rgba(48, 54, 61, 0.5);
4482
- font-size: 13px;
4483
- font-family: var(--mono);
4484
- cursor: pointer;
4485
- transition: background 0.15s;
4486
- display: flex;
4487
- align-items: flex-start;
4488
- gap: 10px;
4489
- }
4567
+ .layer-card.degraded .layer-status {
4568
+ background-color: rgba(210, 153, 34, 0.15);
4569
+ color: var(--amber);
4570
+ }
4490
4571
 
4491
- .activity-item:hover {
4492
- background: rgba(88, 166, 255, 0.05);
4493
- }
4572
+ .layer-card.inactive .layer-status {
4573
+ background-color: rgba(248, 81, 73, 0.15);
4574
+ color: var(--red);
4575
+ }
4494
4576
 
4495
- .activity-item-icon {
4496
- flex: 0 0 auto;
4497
- width: 16px;
4498
- text-align: center;
4499
- font-size: 12px;
4500
- color: var(--text-secondary);
4501
- margin-top: 1px;
4502
- }
4577
+ .layer-detail {
4578
+ font-size: 12px;
4579
+ color: var(--text-secondary);
4580
+ font-family: 'JetBrains Mono', monospace;
4581
+ padding: 8px;
4582
+ background-color: var(--bg);
4583
+ border-radius: 4px;
4584
+ border-left: 2px solid var(--blue);
4585
+ }
4503
4586
 
4504
- .activity-item-content {
4505
- flex: 1;
4506
- min-width: 0;
4507
- }
4587
+ /* Row 2: Info Cards */
4588
+ .info-cards {
4589
+ display: grid;
4590
+ grid-template-columns: repeat(3, 1fr);
4591
+ gap: 16px;
4592
+ }
4508
4593
 
4509
- .activity-time {
4510
- color: var(--text-secondary);
4511
- font-size: 11px;
4512
- margin-bottom: 2px;
4513
- }
4594
+ .info-card {
4595
+ background-color: var(--surface);
4596
+ border: 1px solid var(--border);
4597
+ border-radius: 8px;
4598
+ padding: 20px;
4599
+ }
4514
4600
 
4515
- .activity-main {
4516
- display: flex;
4517
- gap: 8px;
4518
- align-items: baseline;
4519
- margin-bottom: 4px;
4520
- }
4601
+ .card-header {
4602
+ font-size: 12px;
4603
+ font-weight: 600;
4604
+ color: var(--text-secondary);
4605
+ text-transform: uppercase;
4606
+ letter-spacing: 0.5px;
4607
+ margin-bottom: 16px;
4608
+ }
4521
4609
 
4522
- .activity-tier {
4523
- display: inline-flex;
4524
- align-items: center;
4525
- justify-content: center;
4526
- width: 24px;
4527
- height: 16px;
4528
- font-size: 10px;
4529
- font-weight: 700;
4530
- border-radius: 3px;
4531
- text-transform: uppercase;
4532
- flex: 0 0 auto;
4533
- }
4610
+ .card-row {
4611
+ display: flex;
4612
+ justify-content: space-between;
4613
+ align-items: center;
4614
+ margin-bottom: 12px;
4615
+ font-size: 13px;
4616
+ }
4534
4617
 
4535
- .activity-tier.t1 {
4536
- background: rgba(248, 81, 73, 0.2);
4537
- color: var(--red);
4538
- }
4618
+ .card-row:last-child {
4619
+ margin-bottom: 0;
4620
+ }
4539
4621
 
4540
- .activity-tier.t2 {
4541
- background: rgba(210, 153, 34, 0.2);
4542
- color: var(--amber);
4543
- }
4622
+ .card-label {
4623
+ color: var(--text-secondary);
4624
+ }
4544
4625
 
4545
- .activity-tier.t3 {
4546
- background: rgba(63, 185, 80, 0.2);
4547
- color: var(--green);
4548
- }
4626
+ .card-value {
4627
+ color: var(--text-primary);
4628
+ font-family: 'JetBrains Mono', monospace;
4629
+ font-weight: 500;
4630
+ }
4549
4631
 
4550
- .activity-tool {
4551
- color: var(--text-primary);
4552
- font-weight: 600;
4553
- }
4632
+ .identity-badge {
4633
+ display: inline-flex;
4634
+ align-items: center;
4635
+ gap: 4px;
4636
+ padding: 2px 6px;
4637
+ background-color: rgba(88, 166, 255, 0.15);
4638
+ color: var(--blue);
4639
+ border-radius: 3px;
4640
+ font-size: 10px;
4641
+ font-weight: 600;
4642
+ text-transform: uppercase;
4643
+ }
4554
4644
 
4555
- .activity-outcome {
4556
- color: var(--green);
4557
- }
4645
+ .trust-tier-badge {
4646
+ display: inline-flex;
4647
+ align-items: center;
4648
+ gap: 4px;
4649
+ padding: 2px 6px;
4650
+ background-color: rgba(63, 185, 80, 0.15);
4651
+ color: var(--green);
4652
+ border-radius: 3px;
4653
+ font-size: 10px;
4654
+ font-weight: 600;
4655
+ }
4558
4656
 
4559
- .activity-outcome.denied {
4560
- color: var(--red);
4561
- }
4657
+ .truncated {
4658
+ max-width: 200px;
4659
+ overflow: hidden;
4660
+ text-overflow: ellipsis;
4661
+ white-space: nowrap;
4662
+ }
4562
4663
 
4563
- .activity-detail {
4564
- font-size: 12px;
4565
- color: var(--text-secondary);
4566
- margin-left: 0;
4567
- }
4664
+ /* Row 3: SHR & Activity */
4665
+ .main-panels {
4666
+ display: grid;
4667
+ grid-template-columns: 1fr 1fr;
4668
+ gap: 16px;
4669
+ min-height: 400px;
4670
+ }
4568
4671
 
4569
- .activity-item.expanded .activity-detail {
4570
- display: block;
4571
- margin-top: 8px;
4572
- padding: 10px;
4573
- background: rgba(88, 166, 255, 0.08);
4574
- border-left: 2px solid var(--blue);
4575
- border-radius: 4px;
4576
- }
4672
+ .panel {
4673
+ background-color: var(--surface);
4674
+ border: 1px solid var(--border);
4675
+ border-radius: 8px;
4676
+ display: flex;
4677
+ flex-direction: column;
4678
+ overflow: hidden;
4679
+ }
4577
4680
 
4578
- .activity-empty {
4579
- display: flex;
4580
- flex-direction: column;
4581
- align-items: center;
4582
- justify-content: center;
4583
- height: 100%;
4584
- color: var(--text-secondary);
4585
- }
4681
+ .panel-header {
4682
+ padding: 16px 20px;
4683
+ border-bottom: 1px solid var(--border);
4684
+ display: flex;
4685
+ justify-content: space-between;
4686
+ align-items: center;
4687
+ }
4586
4688
 
4587
- .activity-empty-icon {
4588
- font-size: 32px;
4589
- margin-bottom: 12px;
4590
- }
4689
+ .panel-title {
4690
+ font-size: 14px;
4691
+ font-weight: 600;
4692
+ color: var(--text-primary);
4693
+ }
4591
4694
 
4592
- .activity-empty-text {
4593
- font-size: 14px;
4594
- }
4695
+ .panel-action {
4696
+ background: none;
4697
+ border: none;
4698
+ color: var(--blue);
4699
+ cursor: pointer;
4700
+ font-size: 12px;
4701
+ padding: 0;
4702
+ font-weight: 500;
4703
+ transition: color 0.2s;
4704
+ }
4595
4705
 
4596
- /* \u2500\u2500 Protection Status Sidebar (40%) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4706
+ .panel-action:hover {
4707
+ color: #79c0ff;
4708
+ }
4597
4709
 
4598
- .protection-sidebar {
4599
- flex: 2;
4600
- display: flex;
4601
- flex-direction: column;
4602
- background: rgba(22, 27, 34, 0.5);
4603
- overflow: hidden;
4604
- }
4710
+ .panel-content {
4711
+ flex: 1;
4712
+ overflow-y: auto;
4713
+ padding: 20px;
4714
+ }
4605
4715
 
4606
- .sidebar-header {
4607
- padding: 16px 20px;
4608
- border-bottom: 1px solid var(--border);
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
- }
4716
+ /* SHR Viewer */
4717
+ .shr-json {
4718
+ font-family: 'JetBrains Mono', monospace;
4719
+ font-size: 12px;
4720
+ line-height: 1.6;
4721
+ color: var(--text-secondary);
4722
+ }
4618
4723
 
4619
- .sidebar-content {
4620
- flex: 1;
4621
- overflow-y: auto;
4622
- padding: 16px 16px;
4623
- display: grid;
4624
- grid-template-columns: 1fr 1fr;
4625
- gap: 12px;
4626
- }
4724
+ .shr-section {
4725
+ margin-bottom: 12px;
4726
+ }
4627
4727
 
4628
- .protection-card {
4629
- background: var(--surface);
4630
- border: 1px solid var(--border);
4631
- border-radius: var(--radius);
4632
- padding: 14px;
4633
- display: flex;
4634
- flex-direction: column;
4635
- gap: 8px;
4636
- }
4728
+ .shr-section-header {
4729
+ display: flex;
4730
+ align-items: center;
4731
+ gap: 8px;
4732
+ cursor: pointer;
4733
+ font-weight: 600;
4734
+ color: var(--text-primary);
4735
+ padding: 8px;
4736
+ background-color: var(--bg);
4737
+ border-radius: 4px;
4738
+ user-select: none;
4739
+ }
4637
4740
 
4638
- .protection-card-icon {
4639
- font-size: 14px;
4640
- }
4741
+ .shr-section-header:hover {
4742
+ background-color: var(--muted);
4743
+ }
4641
4744
 
4642
- .protection-card-label {
4643
- font-size: 11px;
4644
- font-weight: 600;
4645
- text-transform: uppercase;
4646
- letter-spacing: 0.5px;
4647
- color: var(--text-secondary);
4648
- }
4745
+ .shr-toggle {
4746
+ width: 16px;
4747
+ height: 16px;
4748
+ display: flex;
4749
+ align-items: center;
4750
+ justify-content: center;
4751
+ font-size: 10px;
4752
+ transition: transform 0.2s;
4753
+ }
4649
4754
 
4650
- .protection-card-status {
4651
- display: flex;
4652
- align-items: center;
4653
- gap: 6px;
4654
- font-size: 12px;
4655
- font-weight: 600;
4656
- }
4755
+ .shr-section.collapsed .shr-toggle {
4756
+ transform: rotate(-90deg);
4757
+ }
4657
4758
 
4658
- .protection-card-status.active {
4659
- color: var(--green);
4660
- }
4759
+ .shr-section-content {
4760
+ padding: 8px 16px;
4761
+ background-color: rgba(0, 0, 0, 0.2);
4762
+ border-radius: 4px;
4763
+ margin-top: 4px;
4764
+ }
4661
4765
 
4662
- .protection-card-status.inactive {
4663
- color: var(--text-secondary);
4664
- }
4766
+ .shr-section.collapsed .shr-section-content {
4767
+ display: none;
4768
+ }
4665
4769
 
4666
- .protection-card-stat {
4667
- font-size: 11px;
4668
- color: var(--text-secondary);
4669
- font-family: var(--mono);
4670
- margin-top: 4px;
4671
- }
4770
+ .shr-item {
4771
+ display: flex;
4772
+ margin-bottom: 4px;
4773
+ }
4672
4774
 
4673
- /* \u2500\u2500 Pending Approvals Overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4775
+ .shr-key {
4776
+ color: var(--blue);
4777
+ margin-right: 8px;
4778
+ min-width: 120px;
4779
+ }
4674
4780
 
4675
- .pending-overlay {
4676
- position: fixed;
4677
- top: 56px;
4678
- right: 0;
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
- }
4781
+ .shr-value {
4782
+ color: var(--green);
4783
+ word-break: break-all;
4784
+ }
4689
4785
 
4690
- .pending-overlay.active {
4691
- width: 380px;
4692
- }
4786
+ /* Activity Feed */
4787
+ .activity-feed {
4788
+ display: flex;
4789
+ flex-direction: column;
4790
+ gap: 12px;
4791
+ }
4693
4792
 
4694
- @media (max-width: 1400px) {
4695
- .pending-overlay.active {
4696
- width: 100%;
4697
- right: auto;
4698
- left: 0;
4793
+ .activity-item {
4794
+ padding: 12px;
4795
+ background-color: var(--bg);
4796
+ border-left: 2px solid var(--border);
4797
+ border-radius: 4px;
4798
+ font-size: 12px;
4699
4799
  }
4700
- }
4701
4800
 
4702
- .pending-overlay-header {
4703
- padding: 16px 20px;
4704
- border-bottom: 1px solid var(--border);
4705
- display: flex;
4706
- align-items: center;
4707
- justify-content: space-between;
4708
- flex: 0 0 auto;
4709
- }
4801
+ .activity-item.tool-call {
4802
+ border-left-color: var(--blue);
4803
+ }
4710
4804
 
4711
- .pending-overlay-title {
4712
- font-size: 13px;
4713
- font-weight: 600;
4714
- text-transform: uppercase;
4715
- letter-spacing: 0.5px;
4716
- color: var(--text-primary);
4717
- }
4805
+ .activity-item.context-gate {
4806
+ border-left-color: var(--amber);
4807
+ }
4718
4808
 
4719
- .pending-overlay-close {
4720
- background: none;
4721
- border: none;
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
- }
4809
+ .activity-item.injection {
4810
+ border-left-color: var(--red);
4811
+ }
4730
4812
 
4731
- .pending-overlay-close:hover {
4732
- color: var(--text-primary);
4733
- }
4813
+ .activity-item.protection {
4814
+ border-left-color: var(--green);
4815
+ }
4734
4816
 
4735
- .pending-list {
4736
- flex: 1;
4737
- overflow-y: auto;
4738
- }
4817
+ .activity-type {
4818
+ font-weight: 600;
4819
+ color: var(--text-primary);
4820
+ margin-bottom: 4px;
4821
+ text-transform: uppercase;
4822
+ font-size: 11px;
4823
+ letter-spacing: 0.5px;
4824
+ }
4739
4825
 
4740
- .pending-item {
4741
- padding: 16px 20px;
4742
- border-bottom: 1px solid rgba(48, 54, 61, 0.5);
4743
- display: flex;
4744
- flex-direction: column;
4745
- gap: 10px;
4746
- }
4826
+ .activity-content {
4827
+ color: var(--text-secondary);
4828
+ font-family: 'JetBrains Mono', monospace;
4829
+ margin-bottom: 4px;
4830
+ word-break: break-all;
4831
+ }
4747
4832
 
4748
- .pending-item-header {
4749
- display: flex;
4750
- align-items: center;
4751
- gap: 8px;
4752
- }
4833
+ .activity-time {
4834
+ font-size: 11px;
4835
+ color: var(--text-secondary);
4836
+ }
4753
4837
 
4754
- .pending-item-op {
4755
- font-family: var(--mono);
4756
- font-size: 12px;
4757
- font-weight: 600;
4758
- color: var(--text-primary);
4759
- flex: 1;
4760
- }
4838
+ .empty-state {
4839
+ display: flex;
4840
+ align-items: center;
4841
+ justify-content: center;
4842
+ height: 100%;
4843
+ color: var(--text-secondary);
4844
+ font-size: 13px;
4845
+ }
4761
4846
 
4762
- .pending-item-tier {
4763
- display: inline-flex;
4764
- align-items: center;
4765
- justify-content: center;
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
- }
4847
+ /* Row 4: Handshake History */
4848
+ .handshake-table {
4849
+ background-color: var(--surface);
4850
+ border: 1px solid var(--border);
4851
+ border-radius: 8px;
4852
+ overflow: hidden;
4853
+ }
4774
4854
 
4775
- .pending-item-tier.tier1 {
4776
- background: var(--red);
4777
- }
4855
+ .table-header {
4856
+ display: grid;
4857
+ grid-template-columns: 2fr 1fr 1fr 1fr 1.5fr 1.5fr;
4858
+ gap: 16px;
4859
+ padding: 16px 20px;
4860
+ border-bottom: 1px solid var(--border);
4861
+ background-color: var(--bg);
4862
+ font-size: 12px;
4863
+ font-weight: 600;
4864
+ color: var(--text-secondary);
4865
+ text-transform: uppercase;
4866
+ letter-spacing: 0.5px;
4867
+ }
4778
4868
 
4779
- .pending-item-tier.tier2 {
4780
- background: var(--amber);
4781
- }
4869
+ .table-rows {
4870
+ max-height: 300px;
4871
+ overflow-y: auto;
4872
+ }
4782
4873
 
4783
- .pending-item-reason {
4784
- font-size: 12px;
4785
- color: var(--text-secondary);
4786
- }
4874
+ .table-row {
4875
+ display: grid;
4876
+ grid-template-columns: 2fr 1fr 1fr 1fr 1.5fr 1.5fr;
4877
+ gap: 16px;
4878
+ padding: 14px 20px;
4879
+ border-bottom: 1px solid var(--border);
4880
+ align-items: center;
4881
+ font-size: 12px;
4882
+ cursor: pointer;
4883
+ transition: background-color 0.2s;
4884
+ }
4787
4885
 
4788
- .pending-item-timer {
4789
- display: flex;
4790
- align-items: center;
4791
- gap: 6px;
4792
- font-size: 11px;
4793
- font-family: var(--mono);
4794
- color: var(--text-secondary);
4795
- }
4886
+ .table-row:hover {
4887
+ background-color: var(--bg);
4888
+ }
4796
4889
 
4797
- .pending-item-timer-bar {
4798
- flex: 1;
4799
- height: 4px;
4800
- background: rgba(48, 54, 61, 0.8);
4801
- border-radius: 2px;
4802
- overflow: hidden;
4803
- }
4890
+ .table-row:last-child {
4891
+ border-bottom: none;
4892
+ }
4804
4893
 
4805
- .pending-item-timer-fill {
4806
- height: 100%;
4807
- background: var(--blue);
4808
- transition: width 0.1s linear;
4809
- }
4894
+ .table-cell {
4895
+ color: var(--text-secondary);
4896
+ font-family: 'JetBrains Mono', monospace;
4897
+ }
4810
4898
 
4811
- .pending-item-timer.urgent .pending-item-timer-fill {
4812
- background: var(--red);
4813
- }
4899
+ .table-cell.strong {
4900
+ color: var(--text-primary);
4901
+ font-weight: 500;
4902
+ }
4814
4903
 
4815
- .pending-item-actions {
4816
- display: flex;
4817
- gap: 8px;
4818
- }
4904
+ .table-empty {
4905
+ padding: 40px 20px;
4906
+ text-align: center;
4907
+ color: var(--text-secondary);
4908
+ font-size: 13px;
4909
+ }
4819
4910
 
4820
- .btn {
4821
- flex: 1;
4822
- padding: 8px 12px;
4823
- border: none;
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
- }
4911
+ /* Pending Overlay */
4912
+ .pending-overlay {
4913
+ position: fixed;
4914
+ top: 0;
4915
+ right: -400px;
4916
+ width: 400px;
4917
+ height: 100vh;
4918
+ background-color: var(--surface);
4919
+ border-left: 1px solid var(--border);
4920
+ box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3);
4921
+ z-index: 200;
4922
+ transition: right 0.3s ease;
4923
+ display: flex;
4924
+ flex-direction: column;
4925
+ overflow-y: auto;
4926
+ }
4831
4927
 
4832
- .btn-approve {
4833
- background: var(--green);
4834
- color: var(--bg);
4835
- }
4928
+ .pending-overlay.show {
4929
+ right: 0;
4930
+ }
4836
4931
 
4837
- .btn-approve:hover {
4838
- background: #4ecf5e;
4839
- }
4932
+ .pending-header {
4933
+ padding: 16px 20px;
4934
+ border-bottom: 1px solid var(--border);
4935
+ font-weight: 600;
4936
+ color: var(--text-primary);
4937
+ }
4840
4938
 
4841
- .btn-deny {
4842
- background: var(--red);
4843
- color: white;
4844
- }
4939
+ .pending-items {
4940
+ flex: 1;
4941
+ overflow-y: auto;
4942
+ padding: 16px;
4943
+ }
4845
4944
 
4846
- .btn-deny:hover {
4847
- background: #f9605e;
4848
- }
4945
+ .pending-item {
4946
+ background-color: var(--bg);
4947
+ border: 1px solid var(--border);
4948
+ border-radius: 6px;
4949
+ padding: 16px;
4950
+ margin-bottom: 12px;
4951
+ }
4849
4952
 
4850
- /* \u2500\u2500 Threat Panel (collapsible footer) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4953
+ .pending-title {
4954
+ font-weight: 600;
4955
+ color: var(--text-primary);
4956
+ margin-bottom: 8px;
4957
+ word-break: break-word;
4958
+ }
4851
4959
 
4852
- .threat-panel {
4853
- position: fixed;
4854
- bottom: 0;
4855
- left: 0;
4856
- right: 0;
4857
- background: var(--surface);
4858
- border-top: 1px solid var(--border);
4859
- max-height: 240px;
4860
- z-index: 500;
4861
- display: flex;
4862
- flex-direction: column;
4863
- transition: max-height 0.3s ease-out;
4864
- }
4960
+ .pending-countdown {
4961
+ font-size: 12px;
4962
+ color: var(--amber);
4963
+ margin-bottom: 12px;
4964
+ font-weight: 500;
4965
+ }
4865
4966
 
4866
- .threat-panel.collapsed {
4867
- max-height: 40px;
4868
- }
4967
+ .pending-actions {
4968
+ display: flex;
4969
+ gap: 8px;
4970
+ }
4869
4971
 
4870
- .threat-header {
4871
- padding: 12px 20px;
4872
- cursor: pointer;
4873
- display: flex;
4874
- align-items: center;
4875
- gap: 8px;
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
- }
4972
+ .pending-btn {
4973
+ flex: 1;
4974
+ padding: 8px 12px;
4975
+ border: none;
4976
+ border-radius: 4px;
4977
+ font-size: 12px;
4978
+ font-weight: 600;
4979
+ cursor: pointer;
4980
+ transition: background-color 0.2s;
4981
+ }
4883
4982
 
4884
- .threat-header:hover {
4885
- background: rgba(88, 166, 255, 0.05);
4886
- }
4983
+ .pending-approve {
4984
+ background-color: var(--green);
4985
+ color: var(--bg);
4986
+ }
4887
4987
 
4888
- .threat-icon {
4889
- font-size: 14px;
4890
- }
4988
+ .pending-approve:hover {
4989
+ background-color: #3fa040;
4990
+ }
4891
4991
 
4892
- .threat-content {
4893
- flex: 1;
4894
- overflow-y: auto;
4895
- padding: 0 20px 12px;
4896
- display: flex;
4897
- flex-direction: column;
4898
- gap: 10px;
4899
- }
4992
+ .pending-deny {
4993
+ background-color: var(--red);
4994
+ color: var(--bg);
4995
+ }
4900
4996
 
4901
- .threat-item {
4902
- padding: 8px 10px;
4903
- background: rgba(248, 81, 73, 0.1);
4904
- border-left: 2px solid var(--red);
4905
- border-radius: 4px;
4906
- font-size: 11px;
4907
- color: var(--text-secondary);
4908
- }
4997
+ .pending-deny:hover {
4998
+ background-color: #e03c3c;
4999
+ }
4909
5000
 
4910
- .threat-item-type {
4911
- font-weight: 600;
4912
- color: var(--red);
4913
- font-family: var(--mono);
4914
- }
5001
+ /* Threat Panel */
5002
+ .threat-panel {
5003
+ background-color: var(--surface);
5004
+ border: 1px solid var(--border);
5005
+ border-radius: 8px;
5006
+ margin-top: 20px;
5007
+ overflow: hidden;
5008
+ }
4915
5009
 
4916
- .threat-empty {
4917
- text-align: center;
4918
- padding: 20px 10px;
4919
- color: var(--text-secondary);
4920
- font-size: 12px;
4921
- }
5010
+ .threat-header {
5011
+ padding: 16px 20px;
5012
+ border-bottom: 1px solid var(--border);
5013
+ display: flex;
5014
+ justify-content: space-between;
5015
+ align-items: center;
5016
+ cursor: pointer;
5017
+ user-select: none;
5018
+ }
4922
5019
 
4923
- /* \u2500\u2500 Scrollbars \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
5020
+ .threat-title {
5021
+ font-weight: 600;
5022
+ color: var(--text-primary);
5023
+ }
4924
5024
 
4925
- ::-webkit-scrollbar {
4926
- width: 6px;
4927
- }
5025
+ .threat-toggle {
5026
+ font-size: 10px;
5027
+ color: var(--text-secondary);
5028
+ transition: transform 0.2s;
5029
+ }
4928
5030
 
4929
- ::-webkit-scrollbar-track {
4930
- background: transparent;
4931
- }
5031
+ .threat-panel.collapsed .threat-toggle {
5032
+ transform: rotate(-90deg);
5033
+ }
4932
5034
 
4933
- ::-webkit-scrollbar-thumb {
4934
- background: var(--border);
4935
- border-radius: 3px;
4936
- }
5035
+ .threat-content {
5036
+ padding: 16px 20px;
5037
+ max-height: 300px;
5038
+ overflow-y: auto;
5039
+ }
4937
5040
 
4938
- ::-webkit-scrollbar-thumb:hover {
4939
- background: rgba(88, 166, 255, 0.3);
4940
- }
5041
+ .threat-panel.collapsed .threat-content {
5042
+ display: none;
5043
+ }
4941
5044
 
4942
- /* \u2500\u2500 Responsive \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
5045
+ .threat-alert {
5046
+ background-color: rgba(248, 81, 73, 0.1);
5047
+ border: 1px solid var(--red);
5048
+ border-radius: 4px;
5049
+ padding: 12px;
5050
+ margin-bottom: 8px;
5051
+ font-size: 12px;
5052
+ }
4943
5053
 
4944
- @media (max-width: 1200px) {
4945
- .protection-sidebar {
4946
- display: none;
5054
+ .threat-alert:last-child {
5055
+ margin-bottom: 0;
4947
5056
  }
4948
5057
 
4949
- .activity-feed {
4950
- border-right: none;
5058
+ .threat-type {
5059
+ font-weight: 600;
5060
+ color: var(--red);
5061
+ margin-bottom: 4px;
5062
+ text-transform: uppercase;
5063
+ font-size: 10px;
5064
+ letter-spacing: 0.5px;
4951
5065
  }
4952
- }
4953
5066
 
4954
- @media (max-width: 768px) {
4955
- .status-bar {
4956
- padding: 0 12px;
4957
- gap: 12px;
4958
- height: 48px;
5067
+ .threat-message {
5068
+ color: var(--text-secondary);
4959
5069
  }
4960
5070
 
4961
- .sanctuary-logo {
4962
- font-size: 14px;
5071
+ /* Scrollbar */
5072
+ ::-webkit-scrollbar {
5073
+ width: 8px;
4963
5074
  }
4964
5075
 
4965
- .status-bar-center {
4966
- display: none;
5076
+ ::-webkit-scrollbar-track {
5077
+ background-color: transparent;
4967
5078
  }
4968
5079
 
4969
- .main-container {
4970
- margin-top: 48px;
5080
+ ::-webkit-scrollbar-thumb {
5081
+ background-color: var(--border);
5082
+ border-radius: 4px;
4971
5083
  }
4972
5084
 
4973
- .activity-item {
4974
- padding: 10px 12px;
5085
+ ::-webkit-scrollbar-thumb:hover {
5086
+ background-color: var(--text-secondary);
4975
5087
  }
4976
5088
 
4977
- .pending-overlay.active {
4978
- width: 100%;
5089
+ /* Responsive */
5090
+ @media (max-width: 1400px) {
5091
+ .sovereignty-layers {
5092
+ grid-template-columns: repeat(2, 1fr);
5093
+ }
5094
+
5095
+ .main-panels {
5096
+ grid-template-columns: 1fr;
5097
+ }
5098
+
5099
+ .pending-overlay {
5100
+ width: 100%;
5101
+ right: -100%;
5102
+ }
4979
5103
  }
4980
5104
 
4981
- .threat-panel {
4982
- max-height: 200px;
5105
+ @media (max-width: 768px) {
5106
+ .status-bar {
5107
+ flex-wrap: wrap;
5108
+ height: auto;
5109
+ padding: 12px;
5110
+ gap: 12px;
5111
+ }
5112
+
5113
+ .status-bar-center {
5114
+ order: 3;
5115
+ flex-basis: 100%;
5116
+ }
5117
+
5118
+ .main-content {
5119
+ margin-top: auto;
5120
+ }
5121
+
5122
+ .info-cards {
5123
+ grid-template-columns: 1fr;
5124
+ }
5125
+
5126
+ .table-header,
5127
+ .table-row {
5128
+ grid-template-columns: 1fr;
5129
+ }
4983
5130
  }
4984
- }
4985
- </style>
5131
+ </style>
4986
5132
  </head>
4987
5133
  <body>
4988
-
4989
- <!-- Status Bar (fixed, top) -->
4990
- <div class="status-bar">
4991
- <div class="status-bar-left">
4992
- <div class="sanctuary-logo"><span>\u25C6</span> SANCTUARY</div>
4993
- <div class="version">v${options.serverVersion}</div>
4994
- </div>
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>
5134
+ <!-- Status Bar -->
5135
+ <div class="status-bar">
5136
+ <div class="status-bar-left">
5137
+ <div class="logo-icon">\u25C6</div>
5138
+ <div class="logo-info">
5139
+ <div class="logo-title">SANCTUARY</div>
5140
+ <div class="logo-version">v${options.serverVersion}</div>
5141
+ </div>
4999
5142
  </div>
5000
- </div>
5001
- <div class="status-bar-right">
5002
- <div class="protections-indicator">
5003
- <span class="count" id="activeProtections">6</span>/6 protections
5143
+
5144
+ <div class="status-bar-center">
5145
+ <div id="sovereignty-badge" class="sovereignty-badge">
5146
+ <span>Sovereignty Health:</span>
5147
+ <span class="sovereignty-score" id="sovereignty-score">\u2014</span>
5148
+ <span>/ 100</span>
5149
+ </div>
5004
5150
  </div>
5005
- <div class="uptime">
5006
- <span id="uptimeText">\u2014</span>
5151
+
5152
+ <div class="status-bar-right">
5153
+ <div class="status-item">
5154
+ <strong id="protections-count">\u2014</strong>
5155
+ <span>Protections</span>
5156
+ </div>
5157
+ <div class="status-item">
5158
+ <strong id="uptime-value">\u2014</strong>
5159
+ <span>Uptime</span>
5160
+ </div>
5161
+ <div class="status-dot" id="connection-status"></div>
5162
+ <div id="pending-item-badge" class="pending-badge" style="display: none;">
5163
+ <span>\u23F3</span>
5164
+ <span id="pending-count">0</span>
5165
+ </div>
5007
5166
  </div>
5008
- <div class="status-dot" id="statusDot"></div>
5009
- <div class="pending-badge hidden" id="pendingBadge">0</div>
5010
5167
  </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>
5168
+
5169
+ <!-- Main Content -->
5170
+ <div class="main-content">
5171
+ <div class="grid">
5172
+ <!-- Row 1: Sovereignty Layers -->
5173
+ <div class="sovereignty-layers" id="sovereignty-layers">
5174
+ <div class="layer-card" data-layer="l1">
5175
+ <div class="layer-name">Layer 1</div>
5176
+ <div class="layer-title">Cognitive Sovereignty</div>
5177
+ <div class="layer-status"><span>\u25CF</span> <span id="l1-status">\u2014</span></div>
5178
+ <div class="layer-detail" id="l1-detail">Loading...</div>
5179
+ </div>
5180
+ <div class="layer-card" data-layer="l2">
5181
+ <div class="layer-name">Layer 2</div>
5182
+ <div class="layer-title">Operational Isolation</div>
5183
+ <div class="layer-status"><span>\u25CF</span> <span id="l2-status">\u2014</span></div>
5184
+ <div class="layer-detail" id="l2-detail">Loading...</div>
5185
+ </div>
5186
+ <div class="layer-card" data-layer="l3">
5187
+ <div class="layer-name">Layer 3</div>
5188
+ <div class="layer-title">Selective Disclosure</div>
5189
+ <div class="layer-status"><span>\u25CF</span> <span id="l3-status">\u2014</span></div>
5190
+ <div class="layer-detail" id="l3-detail">Loading...</div>
5191
+ </div>
5192
+ <div class="layer-card" data-layer="l4">
5193
+ <div class="layer-name">Layer 4</div>
5194
+ <div class="layer-title">Verifiable Reputation</div>
5195
+ <div class="layer-status"><span>\u25CF</span> <span id="l4-status">\u2014</span></div>
5196
+ <div class="layer-detail" id="l4-detail">Loading...</div>
5197
+ </div>
5198
+ </div>
5199
+
5200
+ <!-- Row 2: Info Cards -->
5201
+ <div class="info-cards">
5202
+ <div class="info-card">
5203
+ <div class="card-header">Identity</div>
5204
+ <div class="card-row">
5205
+ <span class="card-label">Primary</span>
5206
+ <span class="card-value" id="identity-label">\u2014</span>
5207
+ </div>
5208
+ <div class="card-row">
5209
+ <span class="card-label">DID</span>
5210
+ <span class="card-value truncated" id="identity-did" title="">\u2014</span>
5211
+ </div>
5212
+ <div class="card-row">
5213
+ <span class="card-label">Public Key</span>
5214
+ <span class="card-value truncated" id="identity-pubkey" title="">\u2014</span>
5215
+ </div>
5216
+ <div class="card-row">
5217
+ <span class="card-label">Type</span>
5218
+ <span class="identity-badge">Ed25519</span>
5219
+ </div>
5220
+ <div class="card-row">
5221
+ <span class="card-label">Created</span>
5222
+ <span class="card-value" id="identity-created">\u2014</span>
5223
+ </div>
5224
+ <div class="card-row">
5225
+ <span class="card-label">Identities</span>
5226
+ <span class="card-value" id="identity-count">\u2014</span>
5227
+ </div>
5228
+ </div>
5229
+
5230
+ <div class="info-card">
5231
+ <div class="card-header">Handshakes</div>
5232
+ <div class="card-row">
5233
+ <span class="card-label">Total</span>
5234
+ <span class="card-value" id="handshake-count">\u2014</span>
5235
+ </div>
5236
+ <div class="card-row">
5237
+ <span class="card-label">Latest Peer</span>
5238
+ <span class="card-value truncated" id="handshake-latest">\u2014</span>
5239
+ </div>
5240
+ <div class="card-row">
5241
+ <span class="card-label">Trust Tier</span>
5242
+ <span class="trust-tier-badge" id="handshake-tier">Unverified</span>
5243
+ </div>
5244
+ <div class="card-row">
5245
+ <span class="card-label">Timestamp</span>
5246
+ <span class="card-value" id="handshake-time">\u2014</span>
5247
+ </div>
5248
+ </div>
5249
+
5250
+ <div class="info-card">
5251
+ <div class="card-header">Reputation</div>
5252
+ <div class="card-row">
5253
+ <span class="card-label">Weighted Score</span>
5254
+ <span class="card-value" id="reputation-score">\u2014</span>
5255
+ </div>
5256
+ <div class="card-row">
5257
+ <span class="card-label">Attestations</span>
5258
+ <span class="card-value" id="reputation-attestations">\u2014</span>
5259
+ </div>
5260
+ <div class="card-row">
5261
+ <span class="card-label">Verified Sovereign</span>
5262
+ <span class="card-value" id="reputation-verified">\u2014</span>
5263
+ </div>
5264
+ <div class="card-row">
5265
+ <span class="card-label">Verified Degraded</span>
5266
+ <span class="card-value" id="reputation-degraded">\u2014</span>
5267
+ </div>
5268
+ <div class="card-row">
5269
+ <span class="card-label">Unverified</span>
5270
+ <span class="card-value" id="reputation-unverified">\u2014</span>
5271
+ </div>
5272
+ </div>
5273
+ </div>
5274
+
5275
+ <!-- Row 3: SHR & Activity -->
5276
+ <div class="main-panels">
5277
+ <div class="panel">
5278
+ <div class="panel-header">
5279
+ <div class="panel-title">Sovereignty Health Report</div>
5280
+ <button class="panel-action" id="copy-shr-btn">Copy JSON</button>
5281
+ </div>
5282
+ <div class="panel-content">
5283
+ <div class="shr-json" id="shr-viewer">
5284
+ <div class="empty-state">Loading SHR...</div>
5285
+ </div>
5286
+ </div>
5287
+ </div>
5288
+
5289
+ <div class="panel">
5290
+ <div class="panel-header">
5291
+ <div class="panel-title">Activity Feed</div>
5292
+ </div>
5293
+ <div class="panel-content">
5294
+ <div id="activity-feed" class="activity-feed">
5295
+ <div class="empty-state">Waiting for activity...</div>
5296
+ </div>
5297
+ </div>
5298
+ </div>
5299
+ </div>
5300
+
5301
+ <!-- Row 4: Handshake History -->
5302
+ <div class="handshake-table">
5303
+ <div class="table-header">
5304
+ <div>Counterparty</div>
5305
+ <div>Trust Tier</div>
5306
+ <div>Sovereignty</div>
5307
+ <div>Verified</div>
5308
+ <div>Completed</div>
5309
+ <div>Expires</div>
5310
+ </div>
5311
+ <div class="table-rows" id="handshake-table">
5312
+ <div class="table-empty">No handshakes completed yet</div>
5313
+ </div>
5314
+ </div>
5315
+
5316
+ <!-- Threat Panel -->
5317
+ <div class="threat-panel collapsed">
5318
+ <div class="threat-header">
5319
+ <div class="threat-title">Security Threats</div>
5320
+ <div class="threat-toggle">\u25B6</div>
5321
+ </div>
5322
+ <div class="threat-content" id="threat-alerts">
5323
+ <div class="empty-state">No threats detected</div>
5324
+ </div>
5025
5325
  </div>
5026
5326
  </div>
5027
5327
  </div>
5028
5328
 
5029
- <!-- Protection Status Sidebar -->
5030
- <div class="protection-sidebar" id="protectionSidebar">
5031
- <div class="sidebar-header">
5032
- <span>\u25C6</span> Protection Status
5033
- </div>
5034
- <div class="sidebar-content">
5035
- <div class="protection-card">
5036
- <div class="protection-card-icon">\u{1F510}</div>
5037
- <div class="protection-card-label">Encryption</div>
5038
- <div class="protection-card-status active" id="encryptionStatus">\u2713 Active</div>
5039
- <div class="protection-card-stat" id="encryptionStat">Ed25519</div>
5040
- </div>
5329
+ <!-- Pending Overlay -->
5330
+ <div class="pending-overlay" id="pending-overlay">
5331
+ <div class="pending-header">Pending Approvals</div>
5332
+ <div class="pending-items" id="pending-items"></div>
5333
+ </div>
5334
+
5335
+ <script>
5336
+ // Constants
5337
+ const AUTH_TOKEN = '${options.authToken || ""}' || sessionStorage.getItem('authToken') || '';
5338
+ const TIMEOUT_SECONDS = ${options.timeoutSeconds};
5339
+ const API_BASE = '';
5340
+
5341
+ // State
5342
+ let apiState = {
5343
+ sovereignty: null,
5344
+ identity: null,
5345
+ handshakes: [],
5346
+ shr: null,
5347
+ status: null,
5348
+ };
5349
+
5350
+ let pendingRequests = new Map();
5351
+ let activityLog = [];
5352
+ const maxActivityItems = 50;
5353
+
5354
+ // Helpers
5355
+ function esc(text) {
5356
+ if (!text) return '';
5357
+ const div = document.createElement('div');
5358
+ div.textContent = text;
5359
+ return div.innerHTML;
5360
+ }
5361
+
5362
+ function formatTime(isoString) {
5363
+ if (!isoString) return '\u2014';
5364
+ const date = new Date(isoString);
5365
+ return date.toLocaleString('en-US', {
5366
+ month: 'short',
5367
+ day: 'numeric',
5368
+ hour: '2-digit',
5369
+ minute: '2-digit',
5370
+ });
5371
+ }
5372
+
5373
+ function truncate(str, len = 16) {
5374
+ if (!str) return '\u2014';
5375
+ if (str.length <= len) return str;
5376
+ return str.slice(0, len) + '...';
5377
+ }
5378
+
5379
+ function calculateSovereigntyScore(shr) {
5380
+ if (!shr || !shr.layers) return 0;
5381
+ const layers = shr.layers;
5382
+ let score = 100;
5383
+
5384
+ if (layers.l1?.status === 'degraded') score -= 20;
5385
+ if (layers.l1?.status === 'inactive') score -= 35;
5386
+ if (layers.l2?.status === 'degraded') score -= 15;
5387
+ if (layers.l2?.status === 'inactive') score -= 25;
5388
+ if (layers.l3?.status === 'degraded') score -= 15;
5389
+ if (layers.l3?.status === 'inactive') score -= 25;
5390
+ if (layers.l4?.status === 'degraded') score -= 10;
5391
+ if (layers.l4?.status === 'inactive') score -= 20;
5392
+
5393
+ return Math.max(0, Math.min(100, score));
5394
+ }
5395
+
5396
+ async function fetchAPI(endpoint) {
5397
+ try {
5398
+ const response = await fetch(API_BASE + endpoint, {
5399
+ headers: {
5400
+ 'Authorization': 'Bearer ' + AUTH_TOKEN,
5401
+ },
5402
+ });
5403
+
5404
+ if (response.status === 401) {
5405
+ redirectToLogin();
5406
+ return null;
5407
+ }
5408
+
5409
+ if (!response.ok) {
5410
+ console.error('API Error:', response.status);
5411
+ return null;
5412
+ }
5413
+
5414
+ return await response.json();
5415
+ } catch (err) {
5416
+ console.error('Fetch error:', err);
5417
+ return null;
5418
+ }
5419
+ }
5420
+
5421
+ function redirectToLogin() {
5422
+ sessionStorage.removeItem('authToken');
5423
+ window.location.href = '/';
5424
+ }
5425
+
5426
+ // API Updates
5427
+ async function updateSovereignty() {
5428
+ const data = await fetchAPI('/api/sovereignty');
5429
+ if (!data) return;
5041
5430
 
5042
- <div class="protection-card">
5043
- <div class="protection-card-icon">\u2713</div>
5044
- <div class="protection-card-label">Approval Gate</div>
5045
- <div class="protection-card-status active" id="approvalStatus">\u2713 Active</div>
5046
- <div class="protection-card-stat" id="approvalStat">T1: 2 | T2: 3</div>
5047
- </div>
5431
+ apiState.sovereignty = data;
5048
5432
 
5049
- <div class="protection-card">
5050
- <div class="protection-card-icon">\u{1F3AF}</div>
5051
- <div class="protection-card-label">Context Gating</div>
5052
- <div class="protection-card-status active" id="contextStatus">\u2713 Active</div>
5053
- <div class="protection-card-stat" id="contextStat">12 filtered</div>
5054
- </div>
5433
+ const score = calculateSovereigntyScore(data.shr);
5434
+ const badge = document.getElementById('sovereignty-badge');
5435
+ const scoreEl = document.getElementById('sovereignty-score');
5055
5436
 
5056
- <div class="protection-card">
5057
- <div class="protection-card-icon">\u26A0</div>
5058
- <div class="protection-card-label">Injection Detection</div>
5059
- <div class="protection-card-status active" id="injectionStatus">\u2713 Active</div>
5060
- <div class="protection-card-stat" id="injectionStat">3 flags today</div>
5061
- </div>
5437
+ scoreEl.textContent = score;
5062
5438
 
5063
- <div class="protection-card">
5064
- <div class="protection-card-icon">\u{1F4CA}</div>
5065
- <div class="protection-card-label">Behavioral Baseline</div>
5066
- <div class="protection-card-status active" id="baselineStatus">\u2713 Active</div>
5067
- <div class="protection-card-stat" id="baselineStat">0 anomalies</div>
5068
- </div>
5439
+ badge.classList.remove('degraded', 'inactive');
5440
+ if (score < 70) badge.classList.add('degraded');
5441
+ if (score < 40) badge.classList.add('inactive');
5069
5442
 
5070
- <div class="protection-card">
5071
- <div class="protection-card-icon">\u{1F4CB}</div>
5072
- <div class="protection-card-label">Audit Trail</div>
5073
- <div class="protection-card-status active" id="auditStatus">\u2713 Active</div>
5074
- <div class="protection-card-stat" id="auditStat">284 entries</div>
5075
- </div>
5076
- </div>
5077
- </div>
5078
- </div>
5443
+ updateLayerCards(data.shr);
5444
+ }
5079
5445
 
5080
- <!-- Pending Approvals Overlay -->
5081
- <div class="pending-overlay" id="pendingOverlay">
5082
- <div class="pending-overlay-header">
5083
- <div class="pending-overlay-title">Pending Approvals</div>
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>
5095
- </div>
5096
- <div class="threat-content" id="threatContent">
5097
- <div class="threat-empty">No threats detected</div>
5098
- </div>
5099
- </div>
5446
+ function updateLayerCards(shr) {
5447
+ if (!shr || !shr.layers) return;
5100
5448
 
5101
- <script>
5102
- (function() {
5103
- 'use strict';
5449
+ const layers = shr.layers;
5104
5450
 
5105
- // \u2500\u2500 Configuration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5451
+ updateLayerCard('l1', layers.l1, layers.l1?.encryption || 'AES-256-GCM');
5452
+ updateLayerCard('l2', layers.l2, layers.l2?.isolation_type || 'Process-level');
5453
+ updateLayerCard('l3', layers.l3, layers.l3?.proof_system || 'Schnorr-Pedersen');
5454
+ updateLayerCard('l4', layers.l4, layers.l4?.reputation_mode || 'Weighted');
5455
+ }
5106
5456
 
5107
- const TIMEOUT_SECONDS = ${options.timeoutSeconds};
5108
- // AUTH_TOKEN: embedded token (for direct session access) or from sessionStorage (login page flow)
5109
- const EMBEDDED_TOKEN = ${options.authToken ? JSON.stringify(options.authToken) : "null"};
5110
- const AUTH_TOKEN = EMBEDDED_TOKEN || (function() { try { return sessionStorage.getItem('sanctuary_token'); } catch(_) { return null; } })();
5111
- const MAX_ACTIVITY_ITEMS = 100;
5112
- const MAX_THREAT_ITEMS = 20;
5457
+ function updateLayerCard(layer, layerData, detail) {
5458
+ if (!layerData) return;
5113
5459
 
5114
- // \u2500\u2500 State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5460
+ const card = document.querySelector(\`[data-layer="\${layer}"]\`);
5461
+ if (!card) return;
5115
5462
 
5116
- let SESSION_ID = null;
5117
- let evtSource = null;
5118
- let startTime = Date.now();
5119
- let activityCount = 0;
5120
- let threatCount = 0;
5121
- const pendingRequests = new Map();
5122
- const activityItems = [];
5123
- const threatItems = [];
5124
- let sovereigntyScore = 85;
5125
- let sessionRenewalTimer = null;
5463
+ const status = layerData.status || 'inactive';
5464
+ card.classList.remove('degraded', 'inactive');
5126
5465
 
5127
- // \u2500\u2500 Auth Helpers (SEC-012) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5466
+ if (status === 'degraded') {
5467
+ card.classList.add('degraded');
5468
+ } else if (status === 'inactive') {
5469
+ card.classList.add('inactive');
5470
+ }
5128
5471
 
5129
- function authHeaders() {
5130
- const h = { 'Content-Type': 'application/json' };
5131
- if (AUTH_TOKEN) h['Authorization'] = 'Bearer ' + AUTH_TOKEN;
5132
- return h;
5133
- }
5472
+ document.getElementById(\`\${layer}-status\`).textContent = status.toUpperCase();
5473
+ document.getElementById(\`\${layer}-detail\`).textContent = detail;
5474
+ }
5134
5475
 
5135
- function sessionQuery(url) {
5136
- if (!SESSION_ID) return url;
5137
- const sep = url.includes('?') ? '&' : '?';
5138
- return url + sep + 'session=' + SESSION_ID;
5139
- }
5476
+ async function updateIdentity() {
5477
+ const data = await fetchAPI('/api/identity');
5478
+ if (!data) return;
5140
5479
 
5141
- function setCookie(sessionId, maxAge) {
5142
- document.cookie = 'sanctuary_session=' + sessionId +
5143
- '; path=/; SameSite=Strict; max-age=' + maxAge;
5144
- }
5480
+ apiState.identity = data;
5145
5481
 
5146
- async function exchangeSession() {
5147
- if (!AUTH_TOKEN) return;
5148
- try {
5149
- const resp = await fetch('/auth/session', { method: 'POST', headers: authHeaders() });
5150
- if (resp.ok) {
5151
- const data = await resp.json();
5152
- SESSION_ID = data.session_id;
5153
- var ttl = data.expires_in_seconds || 300;
5154
- // Update cookie with new session
5155
- setCookie(SESSION_ID, ttl);
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');
5482
+ const primary = data.primary || {};
5483
+ document.getElementById('identity-label').textContent = primary.label || '\u2014';
5484
+ document.getElementById('identity-did').textContent = truncate(primary.did, 24);
5485
+ document.getElementById('identity-did').title = primary.did || '';
5486
+ document.getElementById('identity-pubkey').textContent = truncate(primary.publicKey, 24);
5487
+ document.getElementById('identity-pubkey').title = primary.publicKey || '';
5488
+ document.getElementById('identity-created').textContent = formatTime(primary.createdAt);
5489
+ document.getElementById('identity-count').textContent = data.identities?.length || '\u2014';
5223
5490
  }
5224
- }
5225
5491
 
5226
- // \u2500\u2500 Activity Feed \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5492
+ async function updateHandshakes() {
5493
+ const data = await fetchAPI('/api/handshakes');
5494
+ if (!data) return;
5227
5495
 
5228
- function addActivityItem(data) {
5229
- const {
5230
- timestamp,
5231
- tier,
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
- };
5496
+ apiState.handshakes = data.handshakes || [];
5249
5497
 
5250
- activityItems.unshift(item);
5251
- if (activityItems.length > MAX_ACTIVITY_ITEMS) {
5252
- activityItems.pop();
5498
+ document.getElementById('handshake-count').textContent = data.handshakes?.length || '0';
5499
+
5500
+ if (data.handshakes && data.handshakes.length > 0) {
5501
+ const latest = data.handshakes[0];
5502
+ document.getElementById('handshake-latest').textContent = truncate(latest.counterpartyId, 20);
5503
+ document.getElementById('handshake-latest').title = latest.counterpartyId || '';
5504
+ document.getElementById('handshake-tier').textContent = (latest.trustTier || 'Unverified').toUpperCase();
5505
+ document.getElementById('handshake-time').textContent = formatTime(latest.completedAt);
5506
+ } else {
5507
+ document.getElementById('handshake-latest').textContent = '\u2014';
5508
+ document.getElementById('handshake-tier').textContent = 'Unverified';
5509
+ document.getElementById('handshake-time').textContent = '\u2014';
5510
+ }
5511
+
5512
+ updateHandshakeTable(data.handshakes || []);
5253
5513
  }
5254
5514
 
5255
- renderActivityFeed();
5256
- }
5515
+ function updateHandshakeTable(handshakes) {
5516
+ const table = document.getElementById('handshake-table');
5257
5517
 
5258
- function renderActivityFeed() {
5259
- const list = document.getElementById('activityList');
5518
+ if (!handshakes || handshakes.length === 0) {
5519
+ table.innerHTML = '<div class="table-empty">No handshakes completed yet</div>';
5520
+ return;
5521
+ }
5260
5522
 
5261
- if (activityItems.length === 0) {
5262
- list.innerHTML = '<div class="activity-empty"><div class="activity-empty-icon">\u2192</div><div class="activity-empty-text">Waiting for activity...</div></div>';
5263
- return;
5523
+ table.innerHTML = handshakes
5524
+ .map(
5525
+ (hs) => \`
5526
+ <div class="table-row">
5527
+ <div class="table-cell strong">\${esc(truncate(hs.counterpartyId, 24))}</div>
5528
+ <div class="table-cell">\${esc(hs.trustTier || 'Unverified')}</div>
5529
+ <div class="table-cell">\${esc(hs.sovereigntyLevel || '\u2014')}</div>
5530
+ <div class="table-cell">\${hs.verified ? 'Yes' : 'No'}</div>
5531
+ <div class="table-cell">\${formatTime(hs.completedAt)}</div>
5532
+ <div class="table-cell">\${formatTime(hs.expiresAt)}</div>
5533
+ </div>
5534
+ \`
5535
+ )
5536
+ .join('');
5264
5537
  }
5265
5538
 
5266
- list.innerHTML = '';
5267
- for (const item of activityItems) {
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
- });
5539
+ async function updateSHR() {
5540
+ const data = await fetchAPI('/api/shr');
5541
+ if (!data) return;
5300
5542
 
5301
- list.appendChild(tr);
5543
+ apiState.shr = data;
5544
+ renderSHRViewer(data);
5302
5545
  }
5303
- }
5304
5546
 
5305
- // \u2500\u2500 Pending Approvals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5547
+ function renderSHRViewer(shr) {
5548
+ const viewer = document.getElementById('shr-viewer');
5306
5549
 
5307
- function addPendingRequest(data) {
5308
- const {
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
- };
5550
+ if (!shr) {
5551
+ viewer.innerHTML = '<div class="empty-state">No SHR available</div>';
5552
+ return;
5553
+ }
5326
5554
 
5327
- pendingRequests.set(request_id, pending);
5328
- updatePendingUI();
5329
- }
5555
+ let html = '';
5556
+
5557
+ // Implementation
5558
+ html += \`
5559
+ <div class="shr-section">
5560
+ <div class="shr-section-header">
5561
+ <div class="shr-toggle">\u25BC</div>
5562
+ <div>Implementation</div>
5563
+ </div>
5564
+ <div class="shr-section-content">
5565
+ <div class="shr-item">
5566
+ <div class="shr-key">sanctuary_version:</div>
5567
+ <div class="shr-value">\${esc(shr.implementation?.sanctuary_version || '\u2014')}</div>
5568
+ </div>
5569
+ <div class="shr-item">
5570
+ <div class="shr-key">node_version:</div>
5571
+ <div class="shr-value">\${esc(shr.implementation?.node_version || '\u2014')}</div>
5572
+ </div>
5573
+ <div class="shr-item">
5574
+ <div class="shr-key">generated_by:</div>
5575
+ <div class="shr-value">\${esc(shr.implementation?.generated_by || '\u2014')}</div>
5576
+ </div>
5577
+ </div>
5578
+ </div>
5579
+ \`;
5580
+
5581
+ // Metadata
5582
+ html += \`
5583
+ <div class="shr-section">
5584
+ <div class="shr-section-header">
5585
+ <div class="shr-toggle">\u25BC</div>
5586
+ <div>Metadata</div>
5587
+ </div>
5588
+ <div class="shr-section-content">
5589
+ <div class="shr-item">
5590
+ <div class="shr-key">instance_id:</div>
5591
+ <div class="shr-value">\${esc(truncate(shr.instance_id, 20))}</div>
5592
+ </div>
5593
+ <div class="shr-item">
5594
+ <div class="shr-key">generated_at:</div>
5595
+ <div class="shr-value">\${formatTime(shr.generated_at)}</div>
5596
+ </div>
5597
+ <div class="shr-item">
5598
+ <div class="shr-key">expires_at:</div>
5599
+ <div class="shr-value">\${formatTime(shr.expires_at)}</div>
5600
+ </div>
5601
+ </div>
5602
+ </div>
5603
+ \`;
5604
+
5605
+ // Layers
5606
+ if (shr.layers) {
5607
+ html += \`<div class="shr-section">
5608
+ <div class="shr-section-header">
5609
+ <div class="shr-toggle">\u25BC</div>
5610
+ <div>Layers</div>
5611
+ </div>
5612
+ <div class="shr-section-content">
5613
+ \`;
5614
+
5615
+ for (const [key, layer] of Object.entries(shr.layers)) {
5616
+ html += \`
5617
+ <div style="margin-bottom: 12px;">
5618
+ <div style="color: var(--blue); font-weight: 600; margin-bottom: 4px;">\${esc(key)}</div>
5619
+ <div style="padding-left: 12px;">
5620
+ \`;
5621
+
5622
+ for (const [lkey, lvalue] of Object.entries(layer || {})) {
5623
+ const displayValue =
5624
+ typeof lvalue === 'boolean'
5625
+ ? lvalue
5626
+ ? 'true'
5627
+ : 'false'
5628
+ : esc(String(lvalue));
5629
+ html += \`
5630
+ <div class="shr-item">
5631
+ <div class="shr-key">\${esc(lkey)}:</div>
5632
+ <div class="shr-value">\${displayValue}</div>
5633
+ </div>
5634
+ \`;
5635
+ }
5330
5636
 
5331
- function removePendingRequest(id) {
5332
- pendingRequests.delete(id);
5333
- updatePendingUI();
5334
- }
5637
+ html += \`
5638
+ </div>
5639
+ </div>
5640
+ \`;
5641
+ }
5335
5642
 
5336
- function updatePendingUI() {
5337
- const count = pendingRequests.size;
5338
- const badge = document.getElementById('pendingBadge');
5643
+ html += \`
5644
+ </div>
5645
+ </div>
5646
+ \`;
5647
+ }
5648
+
5649
+ // Capabilities
5650
+ if (shr.capabilities) {
5651
+ html += \`
5652
+ <div class="shr-section">
5653
+ <div class="shr-section-header">
5654
+ <div class="shr-toggle">\u25BC</div>
5655
+ <div>Capabilities</div>
5656
+ </div>
5657
+ <div class="shr-section-content">
5658
+ \`;
5659
+
5660
+ for (const [key, value] of Object.entries(shr.capabilities)) {
5661
+ const displayValue = value ? 'true' : 'false';
5662
+ html += \`
5663
+ <div class="shr-item">
5664
+ <div class="shr-key">\${esc(key)}:</div>
5665
+ <div class="shr-value">\${displayValue}</div>
5666
+ </div>
5667
+ \`;
5668
+ }
5339
5669
 
5340
- if (count > 0) {
5341
- badge.classList.remove('hidden');
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');
5670
+ html += \`
5671
+ </div>
5672
+ </div>
5673
+ \`;
5674
+ }
5675
+
5676
+ // Signature
5677
+ html += \`
5678
+ <div class="shr-section">
5679
+ <div class="shr-section-header">
5680
+ <div class="shr-toggle">\u25BC</div>
5681
+ <div>Signature</div>
5682
+ </div>
5683
+ <div class="shr-section-content">
5684
+ <div class="shr-item">
5685
+ <div class="shr-key">signed_by:</div>
5686
+ <div class="shr-value">\${esc(truncate(shr.signed_by, 20))}</div>
5687
+ </div>
5688
+ <div class="shr-item">
5689
+ <div class="shr-key">signature:</div>
5690
+ <div class="shr-value">\${esc(truncate(shr.signature, 32))}</div>
5691
+ </div>
5692
+ </div>
5693
+ </div>
5694
+ \`;
5695
+
5696
+ viewer.innerHTML = html;
5697
+
5698
+ // Add collapse functionality
5699
+ document.querySelectorAll('.shr-section-header').forEach((header) => {
5700
+ header.addEventListener('click', () => {
5701
+ header.closest('.shr-section').classList.toggle('collapsed');
5702
+ });
5703
+ });
5347
5704
  }
5348
5705
 
5349
- renderPendingList();
5350
- }
5351
-
5352
- function renderPendingList() {
5353
- const list = document.getElementById('pendingList');
5354
- list.innerHTML = '';
5706
+ async function updateStatus() {
5707
+ const data = await fetchAPI('/api/status');
5708
+ if (!data) return;
5355
5709
 
5356
- for (const [id, req] of pendingRequests) {
5357
- const item = document.createElement('div');
5358
- item.className = 'pending-item';
5710
+ apiState.status = data;
5359
5711
 
5360
- const tier = req.tier || 1;
5361
- const tierClass = 'tier' + tier;
5362
- const pct = Math.max(0, Math.min(100, (req.remaining / TIMEOUT_SECONDS) * 100));
5363
- const isUrgent = req.remaining <= 30;
5712
+ document.getElementById('protections-count').textContent = data.protectionsCount || '0';
5713
+ document.getElementById('uptime-value').textContent = formatUptime(data.uptime);
5364
5714
 
5365
- item.innerHTML =
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
- '';
5715
+ const connectionStatus = document.getElementById('connection-status');
5716
+ connectionStatus.classList.toggle('disconnected', !data.connected);
5717
+ }
5382
5718
 
5383
- list.appendChild(item);
5719
+ function formatUptime(seconds) {
5720
+ if (!seconds) return '\u2014';
5721
+ const hours = Math.floor(seconds / 3600);
5722
+ const minutes = Math.floor((seconds % 3600) / 60);
5723
+ if (hours > 0) return \`\${hours}h \${minutes}m\`;
5724
+ return \`\${minutes}m\`;
5384
5725
  }
5385
- }
5386
5726
 
5387
- window.handleApprove = function(id) {
5388
- fetch('/api/approve/' + id, { method: 'POST', headers: authHeaders() }).then(() => {
5389
- removePendingRequest(id);
5390
- }).catch(() => {});
5391
- };
5727
+ // SSE Setup
5728
+ function setupSSE() {
5729
+ const eventSource = new EventSource(API_BASE + '/api/events', {
5730
+ headers: {
5731
+ 'Authorization': 'Bearer ' + AUTH_TOKEN,
5732
+ },
5733
+ });
5392
5734
 
5393
- window.handleDeny = function(id) {
5394
- fetch('/api/deny/' + id, { method: 'POST', headers: authHeaders() }).then(() => {
5395
- removePendingRequest(id);
5396
- }).catch(() => {});
5397
- };
5735
+ eventSource.addEventListener('init', (e) => {
5736
+ console.log('Connected to SSE');
5737
+ });
5398
5738
 
5399
- // \u2500\u2500 Threats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5739
+ eventSource.addEventListener('sovereignty-update', () => {
5740
+ updateSovereignty();
5741
+ });
5400
5742
 
5401
- function addThreat(data) {
5402
- const {
5403
- timestamp,
5404
- severity,
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
- };
5743
+ eventSource.addEventListener('handshake-update', () => {
5744
+ updateHandshakes();
5745
+ });
5416
5746
 
5417
- threatItems.unshift(threat);
5418
- if (threatItems.length > MAX_THREAT_ITEMS) {
5419
- threatItems.pop();
5420
- }
5747
+ eventSource.addEventListener('tool-call', (e) => {
5748
+ const data = JSON.parse(e.data);
5749
+ addActivityItem({
5750
+ type: 'tool-call',
5751
+ title: 'Tool Call',
5752
+ content: data.toolName,
5753
+ timestamp: new Date().toISOString(),
5754
+ });
5755
+ });
5421
5756
 
5422
- if (threatCount > 0) {
5423
- document.getElementById('threatPanel').classList.remove('collapsed');
5424
- }
5757
+ eventSource.addEventListener('context-gate-decision', (e) => {
5758
+ const data = JSON.parse(e.data);
5759
+ addActivityItem({
5760
+ type: 'context-gate',
5761
+ title: 'Context Gate',
5762
+ content: data.decision,
5763
+ timestamp: new Date().toISOString(),
5764
+ });
5765
+ });
5425
5766
 
5426
- renderThreats();
5427
- }
5767
+ eventSource.addEventListener('injection-alert', (e) => {
5768
+ const data = JSON.parse(e.data);
5769
+ addActivityItem({
5770
+ type: 'injection',
5771
+ title: 'Injection Alert',
5772
+ content: data.pattern,
5773
+ timestamp: new Date().toISOString(),
5774
+ });
5775
+ addThreatAlert(data);
5776
+ });
5777
+
5778
+ eventSource.addEventListener('pending-request', (e) => {
5779
+ const data = JSON.parse(e.data);
5780
+ addPendingRequest(data);
5781
+ });
5428
5782
 
5429
- function renderThreats() {
5430
- const content = document.getElementById('threatContent');
5431
- const badge = document.getElementById('threatCount');
5783
+ eventSource.addEventListener('request-resolved', (e) => {
5784
+ const data = JSON.parse(e.data);
5785
+ removePendingRequest(data.requestId);
5786
+ });
5432
5787
 
5433
- if (threatItems.length === 0) {
5434
- content.innerHTML = '<div class="threat-empty">No threats detected</div>';
5435
- badge.textContent = '0';
5436
- return;
5788
+ eventSource.onerror = () => {
5789
+ console.error('SSE error');
5790
+ setTimeout(setupSSE, 5000);
5791
+ };
5437
5792
  }
5438
5793
 
5439
- badge.textContent = threatItems.length;
5440
- content.innerHTML = '';
5794
+ // Activity Feed
5795
+ function addActivityItem(item) {
5796
+ activityLog.unshift(item);
5797
+ if (activityLog.length > maxActivityItems) {
5798
+ activityLog.pop();
5799
+ }
5441
5800
 
5442
- for (const threat of threatItems) {
5443
- const div = document.createElement('div');
5444
- div.className = 'threat-item';
5445
- const time = new Date(threat.timestamp).toLocaleTimeString();
5446
- div.innerHTML =
5447
- '<div style="margin-bottom: 3px;">' +
5448
- '<span class="threat-item-type">' + esc(threat.type) + '</span>' +
5449
- '<span style="font-size: 10px; color: var(--text-secondary); margin-left: 6px;">' + esc(time) + '</span>' +
5450
- '</div>' +
5451
- '<div>' + esc(threat.details) + '</div>' +
5452
- '';
5453
- content.appendChild(div);
5454
- }
5455
- }
5801
+ const feed = document.getElementById('activity-feed');
5802
+ const html = \`
5803
+ <div class="activity-item \${item.type}">
5804
+ <div class="activity-type">\${esc(item.title)}</div>
5805
+ <div class="activity-content">\${esc(item.content)}</div>
5806
+ <div class="activity-time">\${formatTime(item.timestamp)}</div>
5807
+ </div>
5808
+ \`;
5456
5809
 
5457
- // \u2500\u2500 SSE Connection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5810
+ if (feed.querySelector('.empty-state')) {
5811
+ feed.innerHTML = '';
5812
+ }
5458
5813
 
5459
- function reconnectSSE() {
5460
- if (evtSource) evtSource.close();
5461
- connect();
5462
- }
5814
+ feed.insertAdjacentHTML('afterbegin', html);
5463
5815
 
5464
- function connect() {
5465
- evtSource = new EventSource(sessionQuery('/events'));
5816
+ if (feed.children.length > maxActivityItems) {
5817
+ feed.lastChild.remove();
5818
+ }
5819
+ }
5466
5820
 
5467
- evtSource.onopen = () => {
5468
- document.getElementById('statusDot').classList.remove('disconnected');
5469
- };
5821
+ // Pending Requests
5822
+ function addPendingRequest(request) {
5823
+ pendingRequests.set(request.requestId, {
5824
+ id: request.requestId,
5825
+ title: request.title,
5826
+ details: request.details,
5827
+ expiresAt: new Date(Date.now() + TIMEOUT_SECONDS * 1000),
5828
+ });
5470
5829
 
5471
- evtSource.onerror = () => {
5472
- document.getElementById('statusDot').classList.add('disconnected');
5473
- };
5830
+ updatePendingDisplay();
5831
+ }
5474
5832
 
5475
- evtSource.addEventListener('init', (e) => {
5476
- const data = JSON.parse(e.data);
5477
- if (data.baseline) {
5478
- updateBaseline(data.baseline);
5479
- }
5480
- if (data.policy) {
5481
- updatePolicy(data.policy);
5833
+ function removePendingRequest(requestId) {
5834
+ pendingRequests.delete(requestId);
5835
+ updatePendingDisplay();
5836
+ }
5837
+
5838
+ function updatePendingDisplay() {
5839
+ const badge = document.getElementById('pending-item-badge');
5840
+ const count = pendingRequests.size;
5841
+
5842
+ if (count > 0) {
5843
+ document.getElementById('pending-count').textContent = count;
5844
+ badge.style.display = 'flex';
5845
+ } else {
5846
+ badge.style.display = 'none';
5482
5847
  }
5483
- if (data.pending) {
5484
- data.pending.forEach(addPendingRequest);
5848
+
5849
+ const overlay = document.getElementById('pending-overlay');
5850
+ const items = document.getElementById('pending-items');
5851
+
5852
+ if (count === 0) {
5853
+ items.innerHTML = '';
5854
+ overlay.classList.remove('show');
5855
+ return;
5485
5856
  }
5486
- });
5487
5857
 
5488
- evtSource.addEventListener('pending-request', (e) => {
5489
- const data = JSON.parse(e.data);
5490
- addPendingRequest(data);
5491
- });
5858
+ let html = '';
5859
+ for (const req of pendingRequests.values()) {
5860
+ const remaining = Math.max(0, Math.floor((req.expiresAt - Date.now()) / 1000));
5861
+ html += \`
5862
+ <div class="pending-item">
5863
+ <div class="pending-title">\${esc(req.title)}</div>
5864
+ <div class="pending-countdown">Expires in \${remaining}s</div>
5865
+ <div class="pending-actions">
5866
+ <button class="pending-btn pending-approve" data-id="\${req.id}">Approve</button>
5867
+ <button class="pending-btn pending-deny" data-id="\${req.id}">Deny</button>
5868
+ </div>
5869
+ </div>
5870
+ \`;
5871
+ }
5492
5872
 
5493
- evtSource.addEventListener('request-resolved', (e) => {
5494
- const data = JSON.parse(e.data);
5495
- removePendingRequest(data.request_id);
5496
- });
5873
+ items.innerHTML = html;
5497
5874
 
5498
- evtSource.addEventListener('tool-call', (e) => {
5499
- const data = JSON.parse(e.data);
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 || ''
5875
+ document.querySelectorAll('.pending-approve').forEach((btn) => {
5876
+ btn.addEventListener('click', async () => {
5877
+ const id = btn.getAttribute('data-id');
5878
+ await fetchAPI(\`/api/approve/\${id}\`);
5879
+ });
5506
5880
  });
5507
- });
5508
5881
 
5509
- evtSource.addEventListener('context-gate-decision', (e) => {
5510
- const data = JSON.parse(e.data);
5511
- addActivityItem({
5512
- timestamp: data.timestamp,
5513
- tier: data.tier || 1,
5514
- tool: data.tool || 'unknown',
5515
- outcome: data.outcome || 'gated',
5516
- detail: data.fields_filtered ? 'Filtered ' + data.fields_filtered + ' fields' : data.reason || '',
5517
- isContextGated: true
5882
+ document.querySelectorAll('.pending-deny').forEach((btn) => {
5883
+ btn.addEventListener('click', async () => {
5884
+ const id = btn.getAttribute('data-id');
5885
+ await fetchAPI(\`/api/deny/\${id}\`);
5886
+ });
5518
5887
  });
5519
- });
5888
+ }
5520
5889
 
5521
- evtSource.addEventListener('injection-alert', (e) => {
5522
- const data = JSON.parse(e.data);
5523
- addActivityItem({
5524
- timestamp: data.timestamp,
5525
- tier: data.tier || 2,
5526
- tool: data.tool || 'unknown',
5527
- outcome: data.allowed ? 'allowed' : 'denied',
5528
- detail: data.signal || 'Injection detected',
5529
- hasInjection: true
5530
- });
5531
- addThreat({
5532
- timestamp: data.timestamp,
5533
- severity: data.severity || 'medium',
5534
- type: 'Injection Alert',
5535
- details: data.signal || 'Suspicious pattern detected'
5536
- });
5537
- });
5890
+ // Threat Panel
5891
+ function addThreatAlert(alert) {
5892
+ const panel = document.querySelector('.threat-panel');
5893
+ const content = document.getElementById('threat-alerts');
5538
5894
 
5539
- evtSource.addEventListener('protection-status', (e) => {
5540
- const data = JSON.parse(e.data);
5541
- updateProtectionStatus(data);
5542
- });
5895
+ if (content.querySelector('.empty-state')) {
5896
+ content.innerHTML = '';
5897
+ }
5543
5898
 
5544
- evtSource.addEventListener('audit-entry', (e) => {
5545
- const data = JSON.parse(e.data);
5546
- // Audit entries don't show in activity by default, but we could add them
5547
- });
5899
+ panel.classList.remove('collapsed');
5548
5900
 
5549
- evtSource.addEventListener('baseline-update', (e) => {
5550
- const data = JSON.parse(e.data);
5551
- updateBaseline(data);
5552
- });
5553
- }
5901
+ const html = \`
5902
+ <div class="threat-alert">
5903
+ <div class="threat-type">\${esc(alert.type || 'Injection Alert')}</div>
5904
+ <div class="threat-message">\${esc(alert.message || alert.pattern || '\u2014')}</div>
5905
+ </div>
5906
+ \`;
5554
5907
 
5555
- function updateBaseline(baseline) {
5556
- if (!baseline) return;
5557
- // Update baseline-derived stats if needed
5558
- }
5908
+ content.insertAdjacentHTML('afterbegin', html);
5559
5909
 
5560
- function updatePolicy(policy) {
5561
- if (!policy) return;
5562
- // Update policy-derived stats
5563
- if (policy.approval_channel) {
5564
- // Policy info updated
5910
+ const alerts = content.querySelectorAll('.threat-alert');
5911
+ if (alerts.length > 10) {
5912
+ alerts[alerts.length - 1].remove();
5913
+ }
5565
5914
  }
5566
- }
5567
5915
 
5568
- function updateProtectionStatus(status) {
5569
- if (status.sovereignty_score !== undefined) {
5570
- updateSovereigntyScore(status.sovereignty_score);
5571
- }
5572
- if (status.active_protections !== undefined) {
5573
- document.getElementById('activeProtections').textContent = status.active_protections;
5574
- }
5575
- // Update individual protection cards
5576
- if (status.encryption !== undefined) {
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';
5590
- }
5591
- if (status.injection_detection !== undefined) {
5592
- const el = document.getElementById('injectionStatus');
5593
- el.className = 'protection-card-status ' + (status.injection_detection ? 'active' : 'inactive');
5594
- el.textContent = status.injection_detection ? '\u2713 Active' : '\u2717 Inactive';
5595
- }
5596
- if (status.baseline !== undefined) {
5597
- const el = document.getElementById('baselineStatus');
5598
- el.className = 'protection-card-status ' + (status.baseline ? 'active' : 'inactive');
5599
- el.textContent = status.baseline ? '\u2713 Active' : '\u2717 Inactive';
5600
- }
5601
- if (status.audit_trail !== undefined) {
5602
- const el = document.getElementById('auditStatus');
5603
- el.className = 'protection-card-status ' + (status.audit_trail ? 'active' : 'inactive');
5604
- el.textContent = status.audit_trail ? '\u2713 Active' : '\u2717 Inactive';
5605
- }
5606
- }
5916
+ // Threat Panel Toggle
5917
+ document.querySelector('.threat-header').addEventListener('click', () => {
5918
+ document.querySelector('.threat-panel').classList.toggle('collapsed');
5919
+ });
5607
5920
 
5608
- // \u2500\u2500 Initialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5921
+ // SHR Copy Button
5922
+ document.getElementById('copy-shr-btn').addEventListener('click', async () => {
5923
+ if (!apiState.shr) return;
5609
5924
 
5610
- (async function init() {
5611
- await exchangeSession();
5612
- // Clean legacy ?token= from URL
5613
- if (window.location.search.includes('token=')) {
5614
- window.history.replaceState({}, '', window.location.pathname);
5615
- }
5616
- connect();
5925
+ const json = JSON.stringify(apiState.shr, null, 2);
5926
+ try {
5927
+ await navigator.clipboard.writeText(json);
5928
+ const btn = document.getElementById('copy-shr-btn');
5929
+ const original = btn.textContent;
5930
+ btn.textContent = 'Copied!';
5931
+ setTimeout(() => {
5932
+ btn.textContent = original;
5933
+ }, 2000);
5934
+ } catch (err) {
5935
+ console.error('Copy failed:', err);
5936
+ }
5937
+ });
5617
5938
 
5618
- // Start uptime ticker
5619
- setInterval(updateUptime, 1000);
5620
- updateUptime();
5939
+ // Pending Overlay Toggle
5940
+ document.getElementById('pending-item-badge').addEventListener('click', () => {
5941
+ document.getElementById('pending-overlay').classList.toggle('show');
5942
+ });
5621
5943
 
5622
- // Pending request countdown timer
5623
- setInterval(() => {
5624
- for (const [id, req] of pendingRequests) {
5625
- req.remaining = Math.max(0, req.remaining - 1);
5626
- const el = document.getElementById('timer-' + id);
5627
- if (el) {
5628
- el.textContent = req.remaining + 's';
5629
- }
5944
+ // Initialize
5945
+ async function initialize() {
5946
+ if (!AUTH_TOKEN) {
5947
+ redirectToLogin();
5948
+ return;
5630
5949
  }
5631
- }, 1000);
5632
5950
 
5633
- // Load initial status
5634
- try {
5635
- const resp = await fetch('/api/status', { headers: authHeaders() });
5636
- if (resp.ok) {
5637
- const status = await resp.json();
5638
- if (status.baseline) updateBaseline(status.baseline);
5639
- if (status.policy) updatePolicy(status.policy);
5640
- }
5641
- } catch (e) {
5642
- // Ignore
5643
- }
5644
- })();
5951
+ // Initial data fetch
5952
+ await Promise.all([
5953
+ updateSovereignty(),
5954
+ updateIdentity(),
5955
+ updateHandshakes(),
5956
+ updateSHR(),
5957
+ updateStatus(),
5958
+ ]);
5645
5959
 
5646
- })();
5647
- </script>
5960
+ // Setup SSE for real-time updates
5961
+ setupSSE();
5648
5962
 
5963
+ // Refresh status periodically
5964
+ setInterval(updateStatus, 30000);
5965
+ }
5966
+
5967
+ // Start
5968
+ initialize();
5969
+ </script>
5649
5970
  </body>
5650
5971
  </html>`;
5651
5972
  }
@@ -7428,15 +7749,6 @@ function generateSHR(identityId, opts) {
7428
7749
  mitigation: "TEE attestation planned for a future release"
7429
7750
  });
7430
7751
  }
7431
- if (config.disclosure.proof_system === "commitment-only") {
7432
- degradations.push({
7433
- layer: "l3",
7434
- code: "COMMITMENT_ONLY",
7435
- severity: "info",
7436
- description: "Commitment schemes only (no ZK proofs)",
7437
- mitigation: "ZK proof support planned for future release"
7438
- });
7439
- }
7440
7752
  const body = {
7441
7753
  shr_version: "1.0",
7442
7754
  implementation: {
@@ -7462,9 +7774,9 @@ function generateSHR(identityId, opts) {
7462
7774
  attestation_available: config.execution.attestation
7463
7775
  },
7464
7776
  l3: {
7465
- status: config.disclosure.proof_system === "commitment-only" ? "degraded" : "active",
7777
+ status: "active",
7466
7778
  proof_system: config.disclosure.proof_system,
7467
- selective_disclosure: config.disclosure.proof_system !== "commitment-only"
7779
+ selective_disclosure: true
7468
7780
  },
7469
7781
  l4: {
7470
7782
  status: "active",
@@ -7677,7 +7989,7 @@ function extractAuthorizationSignals(body) {
7677
7989
  behavioral_baseline_active: false,
7678
7990
  // Would need explicit field in SHR v1.1
7679
7991
  identity_verified: l1.identity_type === "ed25519" || l1.identity_type !== "none",
7680
- zero_knowledge_capable: l3.status === "active" && l3.proof_system !== "commitment-only",
7992
+ zero_knowledge_capable: l3.status === "active",
7681
7993
  selective_disclosure_active: l3.selective_disclosure,
7682
7994
  reputation_portable: l4.reputation_portable,
7683
7995
  handshake_capable: body.capabilities.handshake
@@ -7755,14 +8067,6 @@ function generateAuthorizationConstraints(body, _degradations) {
7755
8067
  priority: "high"
7756
8068
  });
7757
8069
  }
7758
- if (layers.l3.proof_system === "commitment-only") {
7759
- constraints.push({
7760
- type: "restricted_scope",
7761
- description: "No zero-knowledge proofs available \u2014 entire state context may be visible",
7762
- rationale: "Proof system is commitment-only (no ZK)",
7763
- priority: "medium"
7764
- });
7765
- }
7766
8070
  if (layers.l4.status === "degraded") {
7767
8071
  constraints.push({
7768
8072
  type: "known_agents_only",
@@ -8094,6 +8398,154 @@ function deriveTrustTier(level) {
8094
8398
  }
8095
8399
  }
8096
8400
 
8401
+ // src/handshake/attestation.ts
8402
+ init_encoding();
8403
+ init_encoding();
8404
+ var ATTESTATION_VERSION = "1.0";
8405
+ function deriveTrustTier2(level) {
8406
+ switch (level) {
8407
+ case "full":
8408
+ return "verified-sovereign";
8409
+ case "degraded":
8410
+ return "verified-degraded";
8411
+ default:
8412
+ return "unverified";
8413
+ }
8414
+ }
8415
+ function generateAttestation(opts) {
8416
+ const {
8417
+ attesterSHR,
8418
+ subjectSHR,
8419
+ verificationResult,
8420
+ mutual = false,
8421
+ identityManager,
8422
+ masterKey,
8423
+ identityId
8424
+ } = opts;
8425
+ const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
8426
+ if (!identity) {
8427
+ return { error: "No identity available for signing attestation" };
8428
+ }
8429
+ const now = /* @__PURE__ */ new Date();
8430
+ const attesterExpiry = new Date(attesterSHR.body.expires_at);
8431
+ const subjectExpiry = new Date(subjectSHR.body.expires_at);
8432
+ const earliestExpiry = attesterExpiry < subjectExpiry ? attesterExpiry : subjectExpiry;
8433
+ const sovereigntyLevel = verificationResult.valid ? verificationResult.sovereignty_level : "unverified";
8434
+ const body = {
8435
+ attestation_version: ATTESTATION_VERSION,
8436
+ attester_id: attesterSHR.body.instance_id,
8437
+ subject_id: subjectSHR.body.instance_id,
8438
+ attester_shr: attesterSHR,
8439
+ subject_shr: subjectSHR,
8440
+ verification: {
8441
+ subject_shr_valid: verificationResult.valid,
8442
+ subject_sovereignty_level: sovereigntyLevel,
8443
+ subject_trust_tier: deriveTrustTier2(sovereigntyLevel),
8444
+ mutual,
8445
+ errors: verificationResult.errors,
8446
+ warnings: verificationResult.warnings
8447
+ },
8448
+ attested_at: now.toISOString(),
8449
+ expires_at: earliestExpiry.toISOString()
8450
+ };
8451
+ const canonical = JSON.stringify(deepSortKeys(body));
8452
+ const payload = stringToBytes(canonical);
8453
+ const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
8454
+ const signatureBytes = sign(
8455
+ payload,
8456
+ identity.encrypted_private_key,
8457
+ encryptionKey
8458
+ );
8459
+ const summary = generateSummary(body);
8460
+ return {
8461
+ body,
8462
+ signed_by: identity.public_key,
8463
+ signature: toBase64url(signatureBytes),
8464
+ summary
8465
+ };
8466
+ }
8467
+ function layerLine(label, status) {
8468
+ const icon = status === "active" ? "\u2713" : status === "degraded" ? "~" : "x";
8469
+ return ` ${icon} ${label}: ${status}`;
8470
+ }
8471
+ function generateSummary(body) {
8472
+ const v = body.verification;
8473
+ const sLayers = body.subject_shr.body.layers;
8474
+ const aLayers = body.attester_shr.body.layers;
8475
+ const tierLabel = v.subject_trust_tier === "verified-sovereign" ? "Verified Sovereign" : v.subject_trust_tier === "verified-degraded" ? "Verified (Degraded)" : "Unverified";
8476
+ const lines = [
8477
+ `--- Sovereignty Attestation ---`,
8478
+ ``,
8479
+ `Attester: ${body.attester_id.slice(0, 16)}...`,
8480
+ `Subject: ${body.subject_id.slice(0, 16)}...`,
8481
+ `Result: ${tierLabel}`,
8482
+ ``,
8483
+ `Subject Sovereignty Posture:`,
8484
+ layerLine("L1 Cognitive Sovereignty", sLayers.l1.status),
8485
+ layerLine("L2 Operational Isolation", sLayers.l2.status),
8486
+ layerLine("L3 Selective Disclosure", sLayers.l3.status),
8487
+ layerLine("L4 Verifiable Reputation", sLayers.l4.status),
8488
+ ``,
8489
+ `Attester Sovereignty Posture:`,
8490
+ layerLine("L1 Cognitive Sovereignty", aLayers.l1.status),
8491
+ layerLine("L2 Operational Isolation", aLayers.l2.status),
8492
+ layerLine("L3 Selective Disclosure", aLayers.l3.status),
8493
+ layerLine("L4 Verifiable Reputation", aLayers.l4.status),
8494
+ ``,
8495
+ `Mutual: ${v.mutual ? "Yes" : "One-sided"}`,
8496
+ `Attested: ${body.attested_at}`,
8497
+ `Expires: ${body.expires_at}`,
8498
+ `Signature: ${body.attestation_version} / Ed25519`
8499
+ ];
8500
+ if (v.warnings.length > 0) {
8501
+ lines.push(``, `Warnings: ${v.warnings.join("; ")}`);
8502
+ }
8503
+ if (v.errors.length > 0) {
8504
+ lines.push(``, `Errors: ${v.errors.join("; ")}`);
8505
+ }
8506
+ lines.push(``, `--- Verify: compare signed_by against attester's known public key ---`);
8507
+ return lines.join("\n");
8508
+ }
8509
+ function verifyAttestation(attestation, now) {
8510
+ const errors = [];
8511
+ const currentTime = now ?? /* @__PURE__ */ new Date();
8512
+ if (attestation.body.attestation_version !== ATTESTATION_VERSION) {
8513
+ errors.push(
8514
+ `Unsupported attestation version: ${attestation.body.attestation_version}`
8515
+ );
8516
+ }
8517
+ if (!attestation.body.attester_id || !attestation.body.subject_id) {
8518
+ errors.push("Missing attester_id or subject_id");
8519
+ }
8520
+ if (!attestation.body.attester_shr || !attestation.body.subject_shr) {
8521
+ errors.push("Missing attester or subject SHR");
8522
+ }
8523
+ const expired = new Date(attestation.body.expires_at) <= currentTime;
8524
+ if (expired) {
8525
+ errors.push("Attestation has expired");
8526
+ }
8527
+ try {
8528
+ const publicKey = fromBase64url(attestation.signed_by);
8529
+ const canonical = JSON.stringify(deepSortKeys(attestation.body));
8530
+ const payload = stringToBytes(canonical);
8531
+ const signatureBytes = fromBase64url(attestation.signature);
8532
+ const signatureValid = verify(payload, signatureBytes, publicKey);
8533
+ if (!signatureValid) {
8534
+ errors.push("Attestation signature is invalid");
8535
+ }
8536
+ } catch (e) {
8537
+ errors.push(`Signature verification error: ${e.message}`);
8538
+ }
8539
+ return {
8540
+ valid: errors.length === 0,
8541
+ errors,
8542
+ attester_id: attestation.body.attester_id ?? "unknown",
8543
+ subject_id: attestation.body.subject_id ?? "unknown",
8544
+ trust_tier: errors.length === 0 ? attestation.body.verification.subject_trust_tier : "unverified",
8545
+ expired
8546
+ };
8547
+ }
8548
+
8097
8549
  // src/handshake/tools.ts
8098
8550
  function createHandshakeTools(config, identityManager, masterKey, auditLog) {
8099
8551
  const sessions = /* @__PURE__ */ new Map();
@@ -8279,6 +8731,103 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog) {
8279
8731
  result: session.result ?? null
8280
8732
  });
8281
8733
  }
8734
+ },
8735
+ // ─── Streamlined Exchange ─────────────────────────────────────────
8736
+ {
8737
+ name: "sanctuary/handshake_exchange",
8738
+ 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).",
8739
+ inputSchema: {
8740
+ type: "object",
8741
+ properties: {
8742
+ counterparty_shr: {
8743
+ type: "object",
8744
+ description: "The counterparty's signed SHR (SignedSHR object with body, signed_by, signature)."
8745
+ },
8746
+ identity_id: {
8747
+ type: "string",
8748
+ description: "Identity to use for the exchange. Defaults to primary identity."
8749
+ }
8750
+ },
8751
+ required: ["counterparty_shr"]
8752
+ },
8753
+ handler: async (args) => {
8754
+ const counterpartySHR = args.counterparty_shr;
8755
+ const ourSHR = generateSHR(args.identity_id, shrOpts);
8756
+ if (typeof ourSHR === "string") {
8757
+ return toolResult({ error: ourSHR });
8758
+ }
8759
+ const verificationResult = verifySHR(counterpartySHR);
8760
+ const attestation = generateAttestation({
8761
+ attesterSHR: ourSHR,
8762
+ subjectSHR: counterpartySHR,
8763
+ verificationResult,
8764
+ mutual: false,
8765
+ identityManager,
8766
+ masterKey,
8767
+ identityId: args.identity_id
8768
+ });
8769
+ if ("error" in attestation) {
8770
+ auditLog.append("l4", "handshake_exchange", ourSHR.body.instance_id, void 0, "failure");
8771
+ return toolResult({ error: attestation.error });
8772
+ }
8773
+ if (verificationResult.valid) {
8774
+ const sovereigntyLevel = verificationResult.sovereignty_level;
8775
+ const trustTier = sovereigntyLevel === "full" ? "verified-sovereign" : sovereigntyLevel === "degraded" ? "verified-degraded" : "unverified";
8776
+ handshakeResults.set(verificationResult.counterparty_id, {
8777
+ counterparty_id: verificationResult.counterparty_id,
8778
+ counterparty_shr: counterpartySHR,
8779
+ verified: true,
8780
+ sovereignty_level: sovereigntyLevel,
8781
+ trust_tier: trustTier,
8782
+ completed_at: (/* @__PURE__ */ new Date()).toISOString(),
8783
+ expires_at: verificationResult.expires_at,
8784
+ errors: []
8785
+ });
8786
+ }
8787
+ auditLog.append("l4", "handshake_exchange", ourSHR.body.instance_id);
8788
+ return toolResult({
8789
+ attestation,
8790
+ our_shr: ourSHR,
8791
+ verification: {
8792
+ counterparty_valid: verificationResult.valid,
8793
+ counterparty_sovereignty: verificationResult.sovereignty_level,
8794
+ counterparty_id: verificationResult.counterparty_id,
8795
+ errors: verificationResult.errors,
8796
+ warnings: verificationResult.warnings
8797
+ },
8798
+ 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.",
8799
+ _content_trust: "external"
8800
+ });
8801
+ }
8802
+ },
8803
+ {
8804
+ name: "sanctuary/handshake_verify_attestation",
8805
+ description: "Verify a signed attestation artifact from another agent. Checks the Ed25519 signature, temporal validity, and structural integrity.",
8806
+ inputSchema: {
8807
+ type: "object",
8808
+ properties: {
8809
+ attestation: {
8810
+ type: "object",
8811
+ description: "The SignedAttestation object to verify (body, signed_by, signature, summary)."
8812
+ }
8813
+ },
8814
+ required: ["attestation"]
8815
+ },
8816
+ handler: async (args) => {
8817
+ const attestation = args.attestation;
8818
+ const result = verifyAttestation(attestation);
8819
+ auditLog.append(
8820
+ "l4",
8821
+ "handshake_verify_attestation",
8822
+ result.attester_id,
8823
+ void 0,
8824
+ result.valid ? "success" : "failure"
8825
+ );
8826
+ return toolResult({
8827
+ ...result,
8828
+ _content_trust: "external"
8829
+ });
8830
+ }
8282
8831
  }
8283
8832
  ];
8284
8833
  return { tools, handshakeResults };
@@ -11890,11 +12439,6 @@ async function createSanctuaryServer(options) {
11890
12439
  degradations.push(
11891
12440
  "L2 isolation is process-level only; no TEE available"
11892
12441
  );
11893
- if (config.disclosure.proof_system === "commitment-only") {
11894
- degradations.push(
11895
- "L3 proofs are commitment-based only; ZK proofs not yet available"
11896
- );
11897
- }
11898
12442
  return toolResult({
11899
12443
  attestation: {
11900
12444
  environment_type: config.execution.environment,
@@ -11920,7 +12464,7 @@ async function createSanctuaryServer(options) {
11920
12464
  l1_state_encrypted: true,
11921
12465
  l2_execution_isolated: false,
11922
12466
  l2_isolation_type: "process-level",
11923
- l3_proofs_available: config.disclosure.proof_system !== "commitment-only",
12467
+ l3_proofs_available: true,
11924
12468
  l4_reputation_active: true,
11925
12469
  overall_level: "mvs",
11926
12470
  degradations
@@ -11943,14 +12487,6 @@ async function createSanctuaryServer(options) {
11943
12487
  severity: "warning",
11944
12488
  mitigation: "TEE support planned for a future release"
11945
12489
  });
11946
- if (config.disclosure.proof_system === "commitment-only") {
11947
- degradations.push({
11948
- layer: "l3",
11949
- description: "Commitment schemes only (no ZK proofs)",
11950
- severity: "info",
11951
- mitigation: "ZK proof support planned for v0.2.0"
11952
- });
11953
- }
11954
12490
  return toolResult({
11955
12491
  status: degradations.some((d) => d.severity === "critical") ? "compromised" : degradations.some((d) => d.severity === "warning") ? "degraded" : "healthy",
11956
12492
  storage_bytes: storageSizeBytes,
@@ -11969,7 +12505,7 @@ async function createSanctuaryServer(options) {
11969
12505
  last_attestation: (/* @__PURE__ */ new Date()).toISOString()
11970
12506
  },
11971
12507
  l3: {
11972
- status: config.disclosure.proof_system === "commitment-only" ? "degraded" : "active",
12508
+ status: "active",
11973
12509
  proof_system: config.disclosure.proof_system,
11974
12510
  circuits_loaded: 0,
11975
12511
  proofs_generated_total: 0
@@ -12249,6 +12785,6 @@ async function createSanctuaryServer(options) {
12249
12785
  return { server, config };
12250
12786
  }
12251
12787
 
12252
- 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 };
12788
+ 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 };
12253
12789
  //# sourceMappingURL=index.js.map
12254
12790
  //# sourceMappingURL=index.js.map