@joshuaswarren/openclaw-engram 9.1.23 → 9.1.24
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/README.md +22 -1
- package/dist/access-cli.js +1 -1
- package/dist/admin-console/public/app.js +222 -0
- package/dist/admin-console/public/index.html +72 -0
- package/dist/{chunk-S6X3YWYF.js → chunk-XKT3IT22.js} +424 -13
- package/dist/{chunk-S6X3YWYF.js.map → chunk-XKT3IT22.js.map} +1 -1
- package/dist/index.js +125 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -409,7 +409,7 @@ openclaw engram access http-serve --token "$OPENCLAW_ENGRAM_ACCESS_TOKEN"
|
|
|
409
409
|
|
|
410
410
|
Key endpoints: `GET /engram/v1/health`, `POST /engram/v1/recall`, `POST /engram/v1/memories`, `GET /engram/v1/entities/:name`, and more. Full reference in [API docs](docs/api.md).
|
|
411
411
|
|
|
412
|
-
The HTTP server also hosts a lightweight operator UI at `http://127.0.0.1:4318/engram/ui/` for memory browsing, recall inspection, governance review, and entity exploration.
|
|
412
|
+
The HTTP server also hosts a lightweight operator UI at `http://127.0.0.1:4318/engram/ui/` for memory browsing, recall inspection, governance review, trust-zone promotion, and entity exploration.
|
|
413
413
|
|
|
414
414
|
### MCP Tools
|
|
415
415
|
|
|
@@ -473,8 +473,29 @@ openclaw engram semantic-consolidate --dry-run # Preview without changes
|
|
|
473
473
|
# Access layer
|
|
474
474
|
openclaw engram access http-serve --token "$TOKEN" # Start HTTP API
|
|
475
475
|
openclaw engram access mcp-serve # Start stdio MCP server
|
|
476
|
+
|
|
477
|
+
# Trust-zone demos
|
|
478
|
+
openclaw engram trust-zone-demo-seed --dry-run # Preview the opt-in buyer demo dataset
|
|
479
|
+
openclaw engram trust-zone-demo-seed # Explicitly seed the demo dataset
|
|
480
|
+
openclaw engram trust-zone-promote --record-id <id> --target-zone working --reason "Operator review"
|
|
476
481
|
```
|
|
477
482
|
|
|
483
|
+
### Trust-zone demo workflow
|
|
484
|
+
|
|
485
|
+
Trust zones now ship with a dedicated admin-console view plus an explicit demo seeding path for buyer-facing walkthroughs.
|
|
486
|
+
|
|
487
|
+
- **Never automatic** — Engram does not seed sample trust-zone records on install, startup, or feature enablement.
|
|
488
|
+
- **Explicit only** — demo records appear only after you run `openclaw engram trust-zone-demo-seed` or trigger the matching admin-console action.
|
|
489
|
+
- **Buyer-friendly story** — the trust-zone view surfaces provenance strength, promotion readiness, corroboration requirements, and operator promotion actions in one place.
|
|
490
|
+
|
|
491
|
+
The seeded scenario is `enterprise-buyer-v1`, which creates a small, opinionated dataset covering:
|
|
492
|
+
|
|
493
|
+
- quarantine records that are ready for review
|
|
494
|
+
- working records that are blocked on missing provenance
|
|
495
|
+
- working records that still need corroboration
|
|
496
|
+
- working records with independent corroboration support
|
|
497
|
+
- a trusted operator policy record
|
|
498
|
+
|
|
478
499
|
See the [full CLI reference](docs/api.md#cli-commands) for all commands.
|
|
479
500
|
|
|
480
501
|
## Configuration
|
package/dist/access-cli.js
CHANGED
|
@@ -5,6 +5,11 @@ const browserState = {
|
|
|
5
5
|
offset: 0,
|
|
6
6
|
total: 0,
|
|
7
7
|
};
|
|
8
|
+
const trustZoneState = {
|
|
9
|
+
limit: 12,
|
|
10
|
+
offset: 0,
|
|
11
|
+
total: 0,
|
|
12
|
+
};
|
|
8
13
|
|
|
9
14
|
function $(id) {
|
|
10
15
|
return document.getElementById(id);
|
|
@@ -109,12 +114,43 @@ function readMemoryPageSize() {
|
|
|
109
114
|
return Number.parseInt($("memoryPageSize")?.value || String(browserState.limit || 25), 10) || 25;
|
|
110
115
|
}
|
|
111
116
|
|
|
117
|
+
function readTrustZonePageSize() {
|
|
118
|
+
return Number.parseInt($("trustZonePageSize")?.value || String(trustZoneState.limit || 12), 10) || 12;
|
|
119
|
+
}
|
|
120
|
+
|
|
112
121
|
function stepMemoryPage(direction) {
|
|
113
122
|
const pageSize = readMemoryPageSize();
|
|
114
123
|
browserState.limit = pageSize;
|
|
115
124
|
browserState.offset = Math.max(0, browserState.offset + direction * pageSize);
|
|
116
125
|
}
|
|
117
126
|
|
|
127
|
+
function syncTrustZoneControls() {
|
|
128
|
+
const prevButton = $("trustZonePrevButton");
|
|
129
|
+
const nextButton = $("trustZoneNextButton");
|
|
130
|
+
if (prevButton) prevButton.disabled = trustZoneState.offset <= 0;
|
|
131
|
+
if (nextButton) nextButton.disabled = trustZoneState.offset + trustZoneState.limit >= trustZoneState.total;
|
|
132
|
+
|
|
133
|
+
const pageStatus = $("trustZonePageStatus");
|
|
134
|
+
if (!pageStatus) return;
|
|
135
|
+
if (trustZoneState.total === 0) {
|
|
136
|
+
pageStatus.textContent = "No results";
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const pageOffset = Math.min(
|
|
140
|
+
trustZoneState.offset,
|
|
141
|
+
Math.max(0, trustZoneState.total - 1),
|
|
142
|
+
);
|
|
143
|
+
const start = pageOffset + 1;
|
|
144
|
+
const end = Math.min(pageOffset + trustZoneState.limit, trustZoneState.total);
|
|
145
|
+
pageStatus.textContent = `${start}-${end} of ${trustZoneState.total}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function stepTrustZonePage(direction) {
|
|
149
|
+
const pageSize = readTrustZonePageSize();
|
|
150
|
+
trustZoneState.limit = pageSize;
|
|
151
|
+
trustZoneState.offset = Math.max(0, trustZoneState.offset + direction * pageSize);
|
|
152
|
+
}
|
|
153
|
+
|
|
118
154
|
function renderMemoryList(memories) {
|
|
119
155
|
const list = $("memoryList");
|
|
120
156
|
if (!list) return;
|
|
@@ -254,6 +290,86 @@ function renderEntityList(entities) {
|
|
|
254
290
|
});
|
|
255
291
|
}
|
|
256
292
|
|
|
293
|
+
function renderTrustZoneList(records) {
|
|
294
|
+
const list = $("trustZoneList");
|
|
295
|
+
if (!list) return;
|
|
296
|
+
if (!Array.isArray(records) || records.length === 0) {
|
|
297
|
+
renderEmptyState(list, "No trust-zone records matched.");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
clearChildren(list);
|
|
301
|
+
records.forEach((record) => {
|
|
302
|
+
const article = createItem();
|
|
303
|
+
const meta = document.createElement("div");
|
|
304
|
+
meta.className = "meta";
|
|
305
|
+
appendPill(meta, record.zone);
|
|
306
|
+
appendPill(meta, record.kind);
|
|
307
|
+
appendPill(meta, record.sourceClass);
|
|
308
|
+
appendPill(meta, record.anchored ? "anchored" : "unanchored");
|
|
309
|
+
if (record.trustScore) {
|
|
310
|
+
appendPill(meta, `trust ${record.trustScore.total} (${record.trustScore.band})`);
|
|
311
|
+
}
|
|
312
|
+
article.appendChild(meta);
|
|
313
|
+
|
|
314
|
+
const heading = document.createElement("h3");
|
|
315
|
+
heading.style.marginTop = "10px";
|
|
316
|
+
heading.textContent = record.recordId;
|
|
317
|
+
article.appendChild(heading);
|
|
318
|
+
|
|
319
|
+
const pathText = document.createElement("div");
|
|
320
|
+
pathText.className = "status";
|
|
321
|
+
pathText.textContent = `${record.recordedAt} · ${record.filePath}`;
|
|
322
|
+
article.appendChild(pathText);
|
|
323
|
+
|
|
324
|
+
const preview = document.createElement("p");
|
|
325
|
+
preview.textContent = record.summary;
|
|
326
|
+
article.appendChild(preview);
|
|
327
|
+
|
|
328
|
+
const readiness = document.createElement("div");
|
|
329
|
+
readiness.className = "status";
|
|
330
|
+
if (record.nextPromotionTarget) {
|
|
331
|
+
readiness.textContent = record.nextPromotionAllowed
|
|
332
|
+
? `Ready for promotion to ${record.nextPromotionTarget}.`
|
|
333
|
+
: `Blocked on ${record.nextPromotionTarget}: ${(record.nextPromotionReasons || []).join("; ") || "operator review required"}`;
|
|
334
|
+
} else {
|
|
335
|
+
readiness.textContent = "No further promotion path.";
|
|
336
|
+
}
|
|
337
|
+
article.appendChild(readiness);
|
|
338
|
+
|
|
339
|
+
const toolbar = document.createElement("div");
|
|
340
|
+
toolbar.className = "toolbar";
|
|
341
|
+
toolbar.style.marginTop = "12px";
|
|
342
|
+
|
|
343
|
+
const inspectButton = document.createElement("button");
|
|
344
|
+
inspectButton.className = "secondary";
|
|
345
|
+
inspectButton.textContent = "Inspect";
|
|
346
|
+
inspectButton.addEventListener("click", () => {
|
|
347
|
+
$("trustZoneDetail").textContent = JSON.stringify(record, null, 2);
|
|
348
|
+
setStatus("trustZoneDetailStatus", `Loaded ${record.recordId}.`, "ok");
|
|
349
|
+
});
|
|
350
|
+
toolbar.appendChild(inspectButton);
|
|
351
|
+
|
|
352
|
+
if (record.nextPromotionTarget) {
|
|
353
|
+
const previewButton = document.createElement("button");
|
|
354
|
+
previewButton.className = "secondary";
|
|
355
|
+
previewButton.textContent = `Preview → ${record.nextPromotionTarget}`;
|
|
356
|
+
previewButton.addEventListener("click", () => void promoteTrustZone(record.recordId, record.nextPromotionTarget, true));
|
|
357
|
+
toolbar.appendChild(previewButton);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (record.nextPromotionTarget && record.nextPromotionAllowed) {
|
|
361
|
+
const promoteButton = document.createElement("button");
|
|
362
|
+
promoteButton.className = "accent";
|
|
363
|
+
promoteButton.textContent = `Promote → ${record.nextPromotionTarget}`;
|
|
364
|
+
promoteButton.addEventListener("click", () => void promoteTrustZone(record.recordId, record.nextPromotionTarget, false));
|
|
365
|
+
toolbar.appendChild(promoteButton);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
article.appendChild(toolbar);
|
|
369
|
+
list.appendChild(article);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
257
373
|
function renderQuality(response) {
|
|
258
374
|
const summary = $("qualitySummary");
|
|
259
375
|
if (!summary) return;
|
|
@@ -418,6 +534,99 @@ async function loadMaintenance() {
|
|
|
418
534
|
setStatus("maintenanceStatus", "Maintenance summary loaded.", "ok");
|
|
419
535
|
}
|
|
420
536
|
|
|
537
|
+
async function loadTrustZones(resetOffset = false) {
|
|
538
|
+
if (resetOffset) trustZoneState.offset = 0;
|
|
539
|
+
trustZoneState.limit = readTrustZonePageSize();
|
|
540
|
+
setStatus("trustZoneStatus", "Loading trust-zone state...");
|
|
541
|
+
const params = new URLSearchParams();
|
|
542
|
+
const query = $("trustZoneQuery")?.value?.trim();
|
|
543
|
+
const zone = $("trustZoneZone")?.value?.trim();
|
|
544
|
+
const sourceClass = $("trustZoneSourceClass")?.value?.trim();
|
|
545
|
+
if (query) params.set("q", query);
|
|
546
|
+
if (zone) params.set("zone", zone);
|
|
547
|
+
if (sourceClass) params.set("sourceClass", sourceClass);
|
|
548
|
+
params.set("limit", String(trustZoneState.limit));
|
|
549
|
+
params.set("offset", String(trustZoneState.offset));
|
|
550
|
+
|
|
551
|
+
const [statusResponse, browseResponse] = await Promise.all([
|
|
552
|
+
fetchJson("/engram/v1/trust-zones/status"),
|
|
553
|
+
fetchJson(`/engram/v1/trust-zones/records?${params.toString()}`),
|
|
554
|
+
]);
|
|
555
|
+
trustZoneState.total = browseResponse.total || 0;
|
|
556
|
+
const maxOffset = trustZoneState.total > 0
|
|
557
|
+
? Math.floor((trustZoneState.total - 1) / trustZoneState.limit) * trustZoneState.limit
|
|
558
|
+
: 0;
|
|
559
|
+
if (!resetOffset && trustZoneState.offset > maxOffset) {
|
|
560
|
+
trustZoneState.offset = maxOffset;
|
|
561
|
+
return loadTrustZones(false);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
renderTrustZoneList(browseResponse.records);
|
|
565
|
+
syncTrustZoneControls();
|
|
566
|
+
const byZone = statusResponse?.status?.records?.byZone || {};
|
|
567
|
+
const zoneSummary = ["quarantine", "working", "trusted"]
|
|
568
|
+
.filter((name) => typeof byZone[name] === "number")
|
|
569
|
+
.map((name) => `${name} ${byZone[name]}`)
|
|
570
|
+
.join(" · ");
|
|
571
|
+
setStatus(
|
|
572
|
+
"trustZoneStatus",
|
|
573
|
+
`Loaded ${browseResponse.count} of ${browseResponse.total} trust-zone records.${zoneSummary ? ` ${zoneSummary}.` : ""}`,
|
|
574
|
+
"ok",
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async function promoteTrustZone(recordId, targetZone, dryRun) {
|
|
579
|
+
if (!recordId || !targetZone) return;
|
|
580
|
+
setStatus("trustZoneDetailStatus", `${dryRun ? "Previewing" : "Applying"} ${targetZone} promotion for ${recordId}...`);
|
|
581
|
+
const response = await fetchJson("/engram/v1/trust-zones/promote", {
|
|
582
|
+
method: "POST",
|
|
583
|
+
body: JSON.stringify({
|
|
584
|
+
recordId,
|
|
585
|
+
targetZone,
|
|
586
|
+
promotionReason: dryRun
|
|
587
|
+
? `Previewed in Engram admin console for ${recordId}.`
|
|
588
|
+
: `Promoted in Engram admin console for ${recordId}.`,
|
|
589
|
+
dryRun,
|
|
590
|
+
}),
|
|
591
|
+
});
|
|
592
|
+
$("trustZoneSeedResult").textContent = JSON.stringify(response, null, 2);
|
|
593
|
+
$("trustZoneDetail").textContent = JSON.stringify(response.record, null, 2);
|
|
594
|
+
await loadTrustZones(false);
|
|
595
|
+
setStatus(
|
|
596
|
+
"trustZoneDetailStatus",
|
|
597
|
+
dryRun ? `Previewed ${targetZone} promotion for ${recordId}.` : `Applied ${targetZone} promotion for ${recordId}.`,
|
|
598
|
+
"ok",
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
async function seedTrustZoneDemo(dryRun) {
|
|
603
|
+
if (!dryRun && typeof window.confirm === "function") {
|
|
604
|
+
const confirmed = window.confirm(
|
|
605
|
+
"Seed the explicit trust-zone demo dataset into the current namespace? This is opt-in demo data for buyer-facing walkthroughs.",
|
|
606
|
+
);
|
|
607
|
+
if (!confirmed) return;
|
|
608
|
+
}
|
|
609
|
+
setStatus("trustZoneStatus", dryRun ? "Previewing trust-zone demo seed..." : "Seeding trust-zone demo dataset...");
|
|
610
|
+
const response = await fetchJson("/engram/v1/trust-zones/demo-seed", {
|
|
611
|
+
method: "POST",
|
|
612
|
+
body: JSON.stringify({
|
|
613
|
+
scenario: "enterprise-buyer-v1",
|
|
614
|
+
dryRun,
|
|
615
|
+
}),
|
|
616
|
+
});
|
|
617
|
+
$("trustZoneSeedResult").textContent = JSON.stringify(response, null, 2);
|
|
618
|
+
if (!dryRun) {
|
|
619
|
+
await loadTrustZones(true);
|
|
620
|
+
}
|
|
621
|
+
setStatus(
|
|
622
|
+
"trustZoneStatus",
|
|
623
|
+
dryRun
|
|
624
|
+
? `Previewed ${response.records.length} trust-zone demo records.`
|
|
625
|
+
: `Seeded ${response.recordsWritten} trust-zone demo records into ${response.namespace}.`,
|
|
626
|
+
"ok",
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
|
|
421
630
|
async function connectAndBootstrap() {
|
|
422
631
|
const input = $("tokenInput");
|
|
423
632
|
const token = input?.value?.trim() || readToken();
|
|
@@ -433,6 +642,7 @@ async function connectAndBootstrap() {
|
|
|
433
642
|
setStatus("authStatus", "Connected to Engram access API.", "ok");
|
|
434
643
|
await Promise.allSettled([
|
|
435
644
|
loadMemoryBrowser(true),
|
|
645
|
+
loadTrustZones(true),
|
|
436
646
|
loadReviewQueue(),
|
|
437
647
|
loadEntities(),
|
|
438
648
|
loadQuality(),
|
|
@@ -485,6 +695,17 @@ function bootstrap() {
|
|
|
485
695
|
void loadMemoryBrowser(false);
|
|
486
696
|
});
|
|
487
697
|
$("runRecallButton")?.addEventListener("click", () => void runRecallDebugger());
|
|
698
|
+
$("refreshTrustZonesButton")?.addEventListener("click", () => void loadTrustZones(true));
|
|
699
|
+
$("trustZonePrevButton")?.addEventListener("click", () => {
|
|
700
|
+
stepTrustZonePage(-1);
|
|
701
|
+
void loadTrustZones(false);
|
|
702
|
+
});
|
|
703
|
+
$("trustZoneNextButton")?.addEventListener("click", () => {
|
|
704
|
+
stepTrustZonePage(1);
|
|
705
|
+
void loadTrustZones(false);
|
|
706
|
+
});
|
|
707
|
+
$("previewTrustZoneSeedButton")?.addEventListener("click", () => void seedTrustZoneDemo(true));
|
|
708
|
+
$("seedTrustZoneDemoButton")?.addEventListener("click", () => void seedTrustZoneDemo(false));
|
|
488
709
|
$("refreshQueueButton")?.addEventListener("click", () => void loadReviewQueue());
|
|
489
710
|
$("searchEntitiesButton")?.addEventListener("click", () => void loadEntities());
|
|
490
711
|
$("copyMemoryPathButton")?.addEventListener("click", copyMemoryPath);
|
|
@@ -493,6 +714,7 @@ function bootstrap() {
|
|
|
493
714
|
void connectAndBootstrap();
|
|
494
715
|
} else {
|
|
495
716
|
syncBrowserControls();
|
|
717
|
+
syncTrustZoneControls();
|
|
496
718
|
}
|
|
497
719
|
}
|
|
498
720
|
|
|
@@ -194,6 +194,15 @@
|
|
|
194
194
|
font-size: 0.84rem;
|
|
195
195
|
color: var(--muted);
|
|
196
196
|
}
|
|
197
|
+
.callout {
|
|
198
|
+
border: 1px dashed var(--line);
|
|
199
|
+
border-radius: 14px;
|
|
200
|
+
padding: 12px 14px;
|
|
201
|
+
background: rgba(255, 255, 255, 0.62);
|
|
202
|
+
color: var(--muted);
|
|
203
|
+
font-size: 0.88rem;
|
|
204
|
+
line-height: 1.45;
|
|
205
|
+
}
|
|
197
206
|
.status.ok {
|
|
198
207
|
color: var(--ok);
|
|
199
208
|
}
|
|
@@ -360,6 +369,69 @@
|
|
|
360
369
|
</section>
|
|
361
370
|
</div>
|
|
362
371
|
|
|
372
|
+
<div class="grid" style="margin-top: 16px;">
|
|
373
|
+
<section class="card">
|
|
374
|
+
<h2>Trust Zones</h2>
|
|
375
|
+
<div class="callout">
|
|
376
|
+
Demo data is never seeded automatically. Use the explicit seed action below only when you want a buyer-facing trust-zone walkthrough dataset.
|
|
377
|
+
</div>
|
|
378
|
+
<div class="toolbar" style="margin-top: 12px;">
|
|
379
|
+
<label style="flex: 1;">
|
|
380
|
+
Query
|
|
381
|
+
<input id="trustZoneQuery" type="text" placeholder="Search summaries, tags, metadata, or entity refs" />
|
|
382
|
+
</label>
|
|
383
|
+
<label>
|
|
384
|
+
Zone
|
|
385
|
+
<select id="trustZoneZone">
|
|
386
|
+
<option value="">All</option>
|
|
387
|
+
<option value="quarantine">quarantine</option>
|
|
388
|
+
<option value="working">working</option>
|
|
389
|
+
<option value="trusted">trusted</option>
|
|
390
|
+
</select>
|
|
391
|
+
</label>
|
|
392
|
+
<label>
|
|
393
|
+
Source
|
|
394
|
+
<select id="trustZoneSourceClass">
|
|
395
|
+
<option value="">All</option>
|
|
396
|
+
<option value="tool_output">tool_output</option>
|
|
397
|
+
<option value="web_content">web_content</option>
|
|
398
|
+
<option value="subagent_trace">subagent_trace</option>
|
|
399
|
+
<option value="system_memory">system_memory</option>
|
|
400
|
+
<option value="user_input">user_input</option>
|
|
401
|
+
<option value="manual">manual</option>
|
|
402
|
+
</select>
|
|
403
|
+
</label>
|
|
404
|
+
<label>
|
|
405
|
+
Page Size
|
|
406
|
+
<select id="trustZonePageSize">
|
|
407
|
+
<option value="8">8</option>
|
|
408
|
+
<option value="12" selected>12</option>
|
|
409
|
+
<option value="20">20</option>
|
|
410
|
+
</select>
|
|
411
|
+
</label>
|
|
412
|
+
<button id="refreshTrustZonesButton">Refresh</button>
|
|
413
|
+
<button class="secondary" id="previewTrustZoneSeedButton">Preview Demo Seed</button>
|
|
414
|
+
<button class="accent" id="seedTrustZoneDemoButton">Seed Demo Dataset</button>
|
|
415
|
+
</div>
|
|
416
|
+
<div class="toolbar pager" style="margin-top: 12px;">
|
|
417
|
+
<div class="status" id="trustZoneStatus">No trust-zone data loaded yet.</div>
|
|
418
|
+
<div class="toolbar">
|
|
419
|
+
<button class="secondary" id="trustZonePrevButton">Previous</button>
|
|
420
|
+
<div class="status" id="trustZonePageStatus">Page 1</div>
|
|
421
|
+
<button class="secondary" id="trustZoneNextButton">Next</button>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
<div class="list" id="trustZoneList"></div>
|
|
425
|
+
</section>
|
|
426
|
+
|
|
427
|
+
<section class="card">
|
|
428
|
+
<h2>Trust Zone Detail</h2>
|
|
429
|
+
<div class="status" id="trustZoneDetailStatus">Select a trust-zone record to inspect raw lineage and promotion state.</div>
|
|
430
|
+
<div class="mono-box" style="margin-top: 12px;"><pre id="trustZoneDetail">Trust-zone detail will appear here.</pre></div>
|
|
431
|
+
<div class="mono-box" style="margin-top: 12px;"><pre id="trustZoneSeedResult">Demo seed previews and promotion results will appear here.</pre></div>
|
|
432
|
+
</section>
|
|
433
|
+
</div>
|
|
434
|
+
|
|
363
435
|
<div class="grid" style="margin-top: 16px;">
|
|
364
436
|
<section class="card">
|
|
365
437
|
<h2>Review Queue</h2>
|