15607 lines
1.0 MiB
15607 lines
1.0 MiB
{% extends "ewoooc_base.html" %}
|
||
|
||
{% block title %}市場情報|EwoooC{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.market-intel-status {
|
||
display: grid;
|
||
gap: 1rem;
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.market-intel-panel {
|
||
box-sizing: border-box;
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
border: 1px solid var(--momo-border, #d8c8aa);
|
||
border-radius: 8px;
|
||
background:
|
||
radial-gradient(circle at 1px 1px, rgba(120, 83, 44, 0.16) 1px, transparent 1.35px),
|
||
var(--momo-paper, #f8f2e7);
|
||
background-size: 10px 10px, auto;
|
||
box-shadow: var(--momo-shadow-sm, 0 8px 18px rgba(72, 49, 28, 0.08));
|
||
padding: 1.25rem;
|
||
}
|
||
|
||
.market-intel-title {
|
||
color: var(--momo-ink, #30251b);
|
||
font-size: 1.35rem;
|
||
font-weight: 800;
|
||
margin: 0;
|
||
}
|
||
|
||
.market-intel-muted {
|
||
color: var(--momo-muted, #756a5b);
|
||
margin: 0;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.market-intel-flags {
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
|
||
margin-top: 1rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-flag {
|
||
min-width: 0;
|
||
border-left: 3px solid var(--momo-accent, #c8752d);
|
||
background: rgba(255, 250, 241, 0.84);
|
||
padding: 0.8rem 0.9rem;
|
||
}
|
||
|
||
.market-intel-flag span {
|
||
color: var(--momo-muted, #756a5b);
|
||
display: block;
|
||
font-size: 0.78rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.market-intel-flag strong {
|
||
color: var(--momo-ink, #30251b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.market-intel-preview-head {
|
||
align-items: center;
|
||
display: flex;
|
||
gap: 0.75rem;
|
||
justify-content: space-between;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.market-intel-preview-title {
|
||
color: var(--momo-ink, #30251b);
|
||
font-size: 1rem;
|
||
font-weight: 800;
|
||
margin: 0;
|
||
}
|
||
|
||
.market-intel-icon-button {
|
||
align-items: center;
|
||
background: rgba(255, 250, 241, 0.9);
|
||
border: 1px solid var(--momo-border, #d8c8aa);
|
||
border-radius: 8px;
|
||
color: var(--momo-ink, #30251b);
|
||
display: inline-flex;
|
||
height: 2.25rem;
|
||
justify-content: center;
|
||
width: 2.25rem;
|
||
}
|
||
|
||
.market-intel-icon-button:hover {
|
||
background: rgba(201, 117, 45, 0.12);
|
||
color: var(--momo-accent-700, #8f4530);
|
||
}
|
||
|
||
.market-intel-preview-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.market-intel-pill {
|
||
min-width: 0;
|
||
background: rgba(255, 250, 241, 0.82);
|
||
border: 1px solid rgba(120, 83, 44, 0.14);
|
||
border-radius: 8px;
|
||
color: var(--momo-muted, #756a5b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.78rem;
|
||
font-weight: 700;
|
||
overflow-wrap: anywhere;
|
||
padding: 0.35rem 0.5rem;
|
||
}
|
||
|
||
.market-intel-empty {
|
||
background: rgba(255, 250, 241, 0.72);
|
||
border: 1px dashed rgba(120, 83, 44, 0.28);
|
||
border-radius: 8px;
|
||
color: var(--momo-muted, #756a5b);
|
||
padding: 1rem;
|
||
}
|
||
|
||
.market-intel-candidate-list {
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-candidate {
|
||
min-width: 0;
|
||
background: rgba(255, 250, 241, 0.82);
|
||
border: 1px solid rgba(120, 83, 44, 0.14);
|
||
border-left: 3px solid var(--momo-accent, #c8752d);
|
||
border-radius: 8px;
|
||
padding: 0.8rem 0.9rem;
|
||
}
|
||
|
||
.market-intel-candidate a {
|
||
color: var(--momo-ink, #30251b);
|
||
font-weight: 800;
|
||
text-decoration: none;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.market-intel-candidate small {
|
||
color: var(--momo-muted, #756a5b);
|
||
display: block;
|
||
margin-top: 0.35rem;
|
||
}
|
||
|
||
.market-intel-operation-list {
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-operation {
|
||
min-width: 0;
|
||
background: rgba(255, 250, 241, 0.82);
|
||
border: 1px solid rgba(120, 83, 44, 0.14);
|
||
border-left: 3px solid var(--momo-accent, #c8752d);
|
||
border-radius: 8px;
|
||
padding: 0.8rem 0.9rem;
|
||
}
|
||
|
||
.market-intel-operation strong {
|
||
color: var(--momo-ink, #30251b);
|
||
display: block;
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.92rem;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.market-intel-operation small {
|
||
color: var(--momo-muted, #756a5b);
|
||
display: block;
|
||
margin-top: 0.35rem;
|
||
}
|
||
|
||
.market-intel-check-list {
|
||
display: grid;
|
||
gap: 0.6rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-deploy-grid {
|
||
display: grid;
|
||
gap: 0.9rem;
|
||
grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
|
||
margin-top: 1rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-deploy-section-title {
|
||
color: var(--momo-muted, #756a5b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.78rem;
|
||
font-weight: 800;
|
||
margin: 0 0 0.55rem;
|
||
}
|
||
|
||
.market-intel-check {
|
||
align-items: center;
|
||
background: rgba(255, 250, 241, 0.82);
|
||
border: 1px solid rgba(120, 83, 44, 0.14);
|
||
border-radius: 8px;
|
||
color: var(--momo-muted, #756a5b);
|
||
display: flex;
|
||
gap: 0.65rem;
|
||
justify-content: space-between;
|
||
min-width: 0;
|
||
padding: 0.65rem 0.75rem;
|
||
}
|
||
|
||
.market-intel-check div {
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-check strong {
|
||
color: var(--momo-ink, #30251b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.84rem;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.market-intel-check small {
|
||
color: var(--momo-muted, #756a5b);
|
||
display: block;
|
||
line-height: 1.45;
|
||
margin-top: 0.25rem;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.market-intel-check span {
|
||
color: var(--momo-muted, #756a5b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.78rem;
|
||
font-weight: 800;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.market-intel-control-row {
|
||
align-items: stretch;
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
min-width: 0;
|
||
}
|
||
|
||
.market-intel-control-actions {
|
||
align-content: flex-start;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.75rem;
|
||
justify-content: flex-end;
|
||
min-width: 0;
|
||
max-width: min(34rem, 100%);
|
||
}
|
||
|
||
.market-intel-json-input {
|
||
background: rgba(255, 250, 241, 0.9);
|
||
border: 1px solid var(--momo-border, #d8c8aa);
|
||
border-radius: 8px;
|
||
color: var(--momo-ink, #30251b);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 0.82rem;
|
||
line-height: 1.55;
|
||
min-width: 0;
|
||
outline: none;
|
||
padding: 0.75rem;
|
||
resize: vertical;
|
||
width: 100%;
|
||
}
|
||
|
||
.market-intel-json-input:focus {
|
||
border-color: var(--momo-accent, #c8752d);
|
||
box-shadow: 0 0 0 3px rgba(201, 117, 45, 0.14);
|
||
}
|
||
|
||
@media (max-width: 760px) {
|
||
.market-intel-preview-head,
|
||
.market-intel-check,
|
||
.market-intel-control-row {
|
||
align-items: flex-start;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.market-intel-control-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.market-intel-control-actions {
|
||
justify-content: flex-start;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.market-intel-panel {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.market-intel-check span {
|
||
max-width: 100%;
|
||
overflow-wrap: anywhere;
|
||
white-space: normal;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block ewooo_content %}
|
||
<section class="market-intel-status">
|
||
<div class="market-intel-panel">
|
||
<p class="market-intel-muted momo-mono mb-2">MARKET INTEL / {{ status.phase }}</p>
|
||
<h1 class="market-intel-title">市場情報尚未啟用</h1>
|
||
<p class="market-intel-muted mt-2">目前只載入安全骨架;爬蟲、正式寫入與排程都尚未掛載。</p>
|
||
|
||
<div class="market-intel-flags">
|
||
<div class="market-intel-flag">
|
||
<span>模組開關</span>
|
||
<strong>{{ 'ON' if status.enabled else 'OFF' }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>爬蟲開關</span>
|
||
<strong>{{ 'ON' if status.crawler_enabled else 'OFF' }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>資料寫入</span>
|
||
<strong>{{ 'ON' if status.write_enabled else 'OFF' }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>排程掛載</span>
|
||
<strong>{{ 'ON' if status.scheduler_attached else 'OFF' }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>DB 寫入許可</span>
|
||
<strong>{{ 'ON' if status.database_write_allowed else 'OFF' }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>已註冊 Adapter</span>
|
||
<strong>{{ adapter_count|default(0) }}</strong>
|
||
</div>
|
||
<div class="market-intel-flag">
|
||
<span>手動 Fetch</span>
|
||
<strong>{{ 'ON' if manual_fetch_allowed|default(false) else 'OFF' }}</strong>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-preview>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">CANDIDATE PREVIEW / SAFE</p>
|
||
<h2 class="market-intel-preview-title">候選預覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理候選預覽" data-market-intel-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-preview-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-preview-body>
|
||
<div class="market-intel-empty">讀取候選預覽中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-writer>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">SEED WRITER / DRY RUN</p>
|
||
<h2 class="market-intel-preview-title">平台種子寫入預覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理寫入預覽" data-market-intel-writer-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-writer-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-writer-body>
|
||
<div class="market-intel-empty">讀取寫入預覽中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-cli>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">SEED CLI / TRANSACTION PREVIEW</p>
|
||
<h2 class="market-intel-preview-title">Seed CLI 交易預覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理交易預覽" data-market-intel-cli-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-cli-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-cli-body>
|
||
<div class="market-intel-empty">讀取交易預覽中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-db-probe>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">DB SCHEMA / READ ONLY PROBE</p>
|
||
<h2 class="market-intel-preview-title">正式 DB Schema 探針</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 DB 探針" data-market-intel-db-probe-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-db-probe-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-db-probe-body>
|
||
<div class="market-intel-empty">讀取 DB 探針中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-seed-diff>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">PLATFORM SEED / READ ONLY DIFF</p>
|
||
<h2 class="market-intel-preview-title">平台 Seed DB 差異探針</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 Seed 差異" data-market-intel-seed-diff-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-seed-diff-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-seed-diff-body>
|
||
<div class="market-intel-empty">讀取 Seed 差異探針中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-legacy-bridge>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">LEGACY SOURCE / BRIDGE PREVIEW</p>
|
||
<h2 class="market-intel-preview-title">既有資料橋接預覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理橋接預覽" data-market-intel-legacy-bridge-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-legacy-bridge-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-legacy-bridge-body>
|
||
<div class="market-intel-empty">讀取既有資料橋接預覽中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-readiness>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / READINESS PREVIEW</p>
|
||
<h2 class="market-intel-preview-title">MCP 整合就緒度</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP 就緒度" data-market-intel-mcp-readiness-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-readiness-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-readiness-body>
|
||
<div class="market-intel-empty">讀取 MCP 整合就緒度中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-preflight>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / EXTERNAL DEPLOY PREFLIGHT</p>
|
||
<h2 class="market-intel-preview-title">外部 MCP 部署預檢</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP 部署預檢" data-market-intel-mcp-preflight-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-preflight-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-preflight-body>
|
||
<div class="market-intel-empty">讀取 MCP 部署預檢中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-activation>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / ACTIVATION RUNBOOK</p>
|
||
<h2 class="market-intel-preview-title">MCP 啟用 Runbook</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP 啟用 Runbook" data-market-intel-mcp-activation-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-activation-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-activation-body>
|
||
<div class="market-intel-empty">讀取 MCP 啟用 Runbook 中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-gate>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / FETCH GATE</p>
|
||
<h2 class="market-intel-preview-title">人工 Fetch 安全閘門</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理人工 Fetch 安全閘門" data-market-intel-mcp-fetch-gate-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-gate-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-gate-body>
|
||
<div class="market-intel-empty">讀取人工 Fetch 安全閘門中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-completion>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / COMPLETION AUDIT</p>
|
||
<h2 class="market-intel-preview-title">MCP 完整度稽核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP 完整度稽核" data-market-intel-mcp-completion-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-completion-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-completion-body>
|
||
<div class="market-intel-empty">讀取 MCP 完整度稽核中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-activation-evidence>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / ACTIVATION EVIDENCE</p>
|
||
<h2 class="market-intel-preview-title">MCP 啟用證據審核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP 啟用證據審核" data-market-intel-mcp-activation-evidence-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-activation-evidence-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-activation-evidence-body>
|
||
<div class="market-intel-empty">讀取 MCP 啟用證據審核中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="7" spellcheck="false" data-market-intel-mcp-activation-evidence-input placeholder="redacted MCP activation evidence JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP 啟用證據 JSON" data-market-intel-mcp-activation-evidence-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-runtime-smoke>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / RUNTIME SMOKE RECEIPT</p>
|
||
<h2 class="market-intel-preview-title">MCP Runtime Smoke 收據</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Runtime Smoke 收據" data-market-intel-mcp-runtime-smoke-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-runtime-smoke-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-runtime-smoke-body>
|
||
<div class="market-intel-empty">讀取 MCP Runtime Smoke 收據中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="7" spellcheck="false" data-market-intel-mcp-runtime-smoke-input placeholder="read-only readiness receipt JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Runtime Smoke 收據 JSON" data-market-intel-mcp-runtime-smoke-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-runtime-promotion>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / RUNTIME PROMOTION</p>
|
||
<h2 class="market-intel-preview-title">MCP Runtime Promotion 審核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Runtime Promotion 審核" data-market-intel-mcp-runtime-promotion-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-runtime-promotion-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-runtime-promotion-body>
|
||
<div class="market-intel-empty">讀取 MCP Runtime Promotion 審核中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="8" spellcheck="false" data-market-intel-mcp-runtime-promotion-input placeholder="activation evidence and runtime receipt JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Runtime Promotion JSON" data-market-intel-mcp-runtime-promotion-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-manual-fetch-handoff>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / MANUAL FETCH HANDOFF</p>
|
||
<h2 class="market-intel-preview-title">MCP Manual Fetch Handoff 審核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Manual Fetch Handoff" data-market-intel-mcp-manual-fetch-handoff-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-manual-fetch-handoff-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-manual-fetch-handoff-body>
|
||
<div class="market-intel-empty">讀取 MCP Manual Fetch Handoff 審核中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="8" spellcheck="false" data-market-intel-mcp-manual-fetch-handoff-input placeholder="promotion package and operator acknowledgement JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Manual Fetch Handoff JSON" data-market-intel-mcp-manual-fetch-handoff-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-target-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / FETCH TARGET REVIEW</p>
|
||
<h2 class="market-intel-preview-title">MCP Fetch Target Review 審核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Fetch Target Review" data-market-intel-mcp-fetch-target-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-target-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-target-review-body>
|
||
<div class="market-intel-empty">讀取 MCP Fetch Target Review 審核中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-target-review-input placeholder="handoff and target review JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Fetch Target Review JSON" data-market-intel-mcp-fetch-target-review-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-run-package>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / FETCH RUN PACKAGE</p>
|
||
<h2 class="market-intel-preview-title">MCP Fetch Run Package 審核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Fetch Run Package" data-market-intel-mcp-fetch-run-package-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-run-package-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-package-body>
|
||
<div class="market-intel-empty">讀取 MCP Fetch Run Package 審核中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-run-package-input placeholder="target review and operator run controls JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Fetch Run Package JSON" data-market-intel-mcp-fetch-run-package-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-run-readiness>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / FETCH RUN READINESS</p>
|
||
<h2 class="market-intel-preview-title">MCP Fetch Run Readiness 審核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Fetch Run Readiness" data-market-intel-mcp-fetch-run-readiness-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-run-readiness-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-readiness-body>
|
||
<div class="market-intel-empty">讀取 MCP Fetch Run Readiness 審核中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-run-readiness-input placeholder="run package and operator readiness JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Fetch Run Readiness JSON" data-market-intel-mcp-fetch-run-readiness-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-run-receipt>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / FETCH RUN RECEIPT</p>
|
||
<h2 class="market-intel-preview-title">MCP Fetch Run Receipt 審核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Fetch Run Receipt" data-market-intel-mcp-fetch-run-receipt-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-run-receipt-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-receipt-body>
|
||
<div class="market-intel-empty">讀取 MCP Fetch Run Receipt 審核中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-run-receipt-input placeholder="run readiness and manual fetch receipt JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Fetch Run Receipt JSON" data-market-intel-mcp-fetch-run-receipt-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-result-parser-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / FETCH RESULT PARSER</p>
|
||
<h2 class="market-intel-preview-title">MCP Fetch Result Parser 審核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Fetch Result Parser" data-market-intel-mcp-fetch-result-parser-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-result-parser-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-result-parser-review-body>
|
||
<div class="market-intel-empty">讀取 MCP Fetch Result Parser 審核中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-result-parser-review-input placeholder="run receipt and parser result JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Fetch Result Parser JSON" data-market-intel-mcp-fetch-result-parser-review-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-handoff-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / FETCH CANDIDATE HANDOFF</p>
|
||
<h2 class="market-intel-preview-title">MCP Fetch Candidate Handoff 審核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Fetch Candidate Handoff" data-market-intel-mcp-fetch-candidate-handoff-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-handoff-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-handoff-review-body>
|
||
<div class="market-intel-empty">讀取 MCP Fetch Candidate Handoff 審核中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-handoff-review-input placeholder="parser review and candidate handoff JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Fetch Candidate Handoff JSON" data-market-intel-mcp-fetch-candidate-handoff-review-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / FETCH CANDIDATE QUEUE</p>
|
||
<h2 class="market-intel-preview-title">MCP Fetch Candidate Queue 審核</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Fetch Candidate Queue" data-market-intel-mcp-fetch-candidate-queue-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-review-body>
|
||
<div class="market-intel-empty">讀取 MCP Fetch Candidate Queue 審核中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-review-input placeholder="handoff review and candidate queue review JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Fetch Candidate Queue JSON" data-market-intel-mcp-fetch-candidate-queue-review-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-preflight>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / QUEUE WRITER PREFLIGHT</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer Preflight</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Candidate Queue Writer Preflight" data-market-intel-mcp-fetch-candidate-queue-writer-preflight-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-preflight-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-preflight-body>
|
||
<div class="market-intel-empty">讀取 MCP Candidate Queue Writer Preflight 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-preflight-input placeholder="candidate queue review and writer preflight JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Candidate Queue Writer Preflight JSON" data-market-intel-mcp-fetch-candidate-queue-writer-preflight-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-cli-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / WRITER CLI REVIEW</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer CLI Review</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Writer CLI Review" data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-body>
|
||
<div class="market-intel-empty">讀取 MCP Writer CLI Review 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-input placeholder="writer preflight and CLI review JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Writer CLI Review JSON" data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / WRITER RUN PACKAGE REVIEW</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer Run Package Review</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Writer Run Package Review" data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-body>
|
||
<div class="market-intel-empty">讀取 MCP Writer Run Package Review 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-input placeholder="writer CLI review and run package review JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Writer Run Package Review JSON" data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / WRITER RUN READINESS</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer Run Readiness</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Writer Run Readiness" data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-body>
|
||
<div class="market-intel-empty">讀取 MCP Writer Run Readiness 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-input placeholder="writer run package review and readiness JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Writer Run Readiness JSON" data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / WRITER RUN RECEIPT</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer Run Receipt Review</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Writer Run Receipt Review" data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-body>
|
||
<div class="market-intel-empty">讀取 MCP Writer Run Receipt Review 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-input placeholder="writer run readiness and receipt JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Writer Run Receipt Review JSON" data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / WRITER RUN CLOSEOUT</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer Run Closeout Review</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Writer Run Closeout Review" data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-body>
|
||
<div class="market-intel-empty">讀取 MCP Writer Run Closeout Review 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-input placeholder="writer run receipt review and closeout JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Writer Run Closeout Review JSON" data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / POST-CLOSEOUT INVENTORY</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer Post-Closeout Inventory Review</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Writer Post-Closeout Inventory Review" data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-body>
|
||
<div class="market-intel-empty">讀取 MCP Writer Post-Closeout Inventory Review 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-input placeholder="writer run closeout review and operator inventory JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Writer Post-Closeout Inventory Review JSON" data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / REVIEW HANDOFF</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer Review Handoff</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Writer Review Handoff" data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-body>
|
||
<div class="market-intel-empty">讀取 MCP Writer Review Handoff 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-input placeholder="writer post-closeout inventory review and handoff JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Writer Review Handoff JSON" data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / REVIEW INVENTORY</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer Review Inventory</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Writer Review Inventory" data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-body>
|
||
<div class="market-intel-empty">讀取 MCP Writer Review Inventory 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-input placeholder="writer review handoff and operator inventory JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Writer Review Inventory JSON" data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / REVIEW DECISION</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer Review Decision</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Writer Review Decision" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-body>
|
||
<div class="market-intel-empty">讀取 MCP Writer Review Decision 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-input placeholder="writer review inventory and operator decision JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Writer Review Decision JSON" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / DECISION APPROVAL</p>
|
||
<h2 class="market-intel-preview-title">MCP Candidate Queue Writer Review Decision Approval</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Writer Review Decision Approval" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-body>
|
||
<div class="market-intel-empty">讀取 MCP Writer Review Decision Approval 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-input placeholder="writer review decision and operator approval JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Writer Review Decision Approval JSON" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MCP / APPROVAL PREFLIGHT</p>
|
||
<h2 class="market-intel-preview-title">MCP Decision Approval Writer Preflight</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 MCP Decision Approval Writer Preflight" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-body>
|
||
<div class="market-intel-empty">讀取 MCP Decision Approval Writer Preflight 中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="9" spellcheck="false" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-input placeholder="approval result and writer preflight JSON"></textarea>
|
||
<div class="market-intel-control-actions">
|
||
<button class="market-intel-icon-button" type="button" title="審核 MCP Decision Approval Writer Preflight JSON" data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-review>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-manual-sample>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MANUAL SAMPLE / FETCH PLAN</p>
|
||
<h2 class="market-intel-preview-title">人工樣本 Fetch 計畫</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理人工樣本 Fetch 計畫" data-market-intel-manual-sample-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-manual-sample-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-manual-sample-body>
|
||
<div class="market-intel-empty">讀取人工樣本 Fetch 計畫中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-sample-acceptance>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MANUAL SAMPLE / ACCEPTANCE</p>
|
||
<h2 class="market-intel-preview-title">樣本結果驗收契約</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理樣本結果驗收契約" data-market-intel-sample-acceptance-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-sample-acceptance-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-body>
|
||
<div class="market-intel-empty">讀取樣本結果驗收契約中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-sample-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MANUAL SAMPLE / REVIEW</p>
|
||
<h2 class="market-intel-preview-title">樣本結果審核預覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理樣本結果審核預覽" data-market-intel-sample-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-sample-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-sample-review-body>
|
||
<div class="market-intel-empty">讀取樣本結果審核預覽中...</div>
|
||
</div>
|
||
<div class="market-intel-control-row mt-3">
|
||
<textarea class="market-intel-json-input" rows="5" spellcheck="false" data-market-intel-sample-review-input placeholder="sample result JSON"></textarea>
|
||
<div class="market-intel-control-actions" data-market-intel-sample-review-actions-rail>
|
||
<button class="market-intel-icon-button" type="button" title="審核 sample result JSON" data-market-intel-sample-review-evaluate>
|
||
<i class="fas fa-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生候選活動 handoff" data-market-intel-sample-candidate-handoff>
|
||
<i class="fas fa-arrow-right" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生候選審核 queue 草案" data-market-intel-sample-candidate-queue-draft>
|
||
<i class="fas fa-clipboard-list" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue 寫入送審 gate" data-market-intel-sample-candidate-queue-approval>
|
||
<i class="fas fa-shield-halved" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue transaction preview" data-market-intel-sample-candidate-queue-transaction>
|
||
<i class="fas fa-code" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue writer CLI gate" data-market-intel-sample-candidate-queue-writer>
|
||
<i class="fas fa-terminal" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue writer preflight" data-market-intel-sample-candidate-queue-preflight>
|
||
<i class="fas fa-database" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue writer post-write smoke" data-market-intel-sample-candidate-queue-postwrite-smoke>
|
||
<i class="fas fa-search" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue writer operator drill" data-market-intel-sample-candidate-queue-operator-drill>
|
||
<i class="fas fa-list-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue writer run package" data-market-intel-sample-candidate-queue-run-package>
|
||
<i class="fas fa-box-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue writer run readiness" data-market-intel-sample-candidate-queue-run-readiness>
|
||
<i class="fas fa-clipboard-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue writer run receipt" data-market-intel-sample-candidate-queue-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue writer run closeout" data-market-intel-sample-candidate-queue-run-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review handoff" data-market-intel-sample-candidate-queue-review-handoff>
|
||
<i class="fas fa-handshake" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review inventory" data-market-intel-sample-candidate-queue-review-inventory>
|
||
<i class="fas fa-eye" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review decision 草案" data-market-intel-sample-candidate-queue-review-decision>
|
||
<i class="fas fa-scale-balanced" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision 批准 gate" data-market-intel-sample-candidate-queue-review-decision-approval>
|
||
<i class="fas fa-shield-halved" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review decision transaction preview" data-market-intel-sample-candidate-queue-review-decision-transaction>
|
||
<i class="fas fa-code-branch" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision writer preflight" data-market-intel-sample-candidate-queue-review-decision-preflight>
|
||
<i class="fas fa-database" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision writer post-write smoke" data-market-intel-sample-candidate-queue-review-decision-postwrite-smoke>
|
||
<i class="fas fa-search" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review decision writer operator drill" data-market-intel-sample-candidate-queue-review-decision-operator-drill>
|
||
<i class="fas fa-list-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review decision writer run package" data-market-intel-sample-candidate-queue-review-decision-run-package>
|
||
<i class="fas fa-box-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision writer run readiness" data-market-intel-sample-candidate-queue-review-decision-run-readiness>
|
||
<i class="fas fa-clipboard-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision writer CLI gate" data-market-intel-sample-candidate-queue-review-decision-writer>
|
||
<i class="fas fa-terminal" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review decision writer run receipt" data-market-intel-sample-candidate-queue-review-decision-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review decision writer run closeout" data-market-intel-sample-candidate-queue-review-decision-run-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review decision post-closeout inventory" data-market-intel-sample-candidate-queue-review-decision-post-closeout-inventory>
|
||
<i class="fas fa-clipboard-list" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="封存 queue review completion archive" data-market-intel-sample-candidate-queue-review-completion-archive>
|
||
<i class="fas fa-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review archive summary" data-market-intel-sample-candidate-queue-review-archive-summary>
|
||
<i class="fas fa-file-lines" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary preflight" data-market-intel-sample-candidate-queue-review-ai-summary-preflight>
|
||
<i class="fas fa-robot" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary run package" data-market-intel-sample-candidate-queue-review-ai-summary-run-package>
|
||
<i class="fas fa-box-open" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="驗收 queue review AI summary output receipt" data-market-intel-sample-candidate-queue-review-ai-summary-output-receipt>
|
||
<i class="fas fa-file-circle-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary persistence preflight" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-preflight>
|
||
<i class="fas fa-file-pen" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary persistence transaction preview" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-transaction>
|
||
<i class="fas fa-code-branch" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary persistence writer preflight" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-writer-preflight>
|
||
<i class="fas fa-database" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary persistence run package" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-package>
|
||
<i class="fas fa-box-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary persistence run readiness" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-readiness>
|
||
<i class="fas fa-clipboard-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review AI summary persistence run receipt" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review AI summary persistence run closeout" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-gate>
|
||
<i class="fas fa-paper-plane" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary Telegram dispatch run package" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-package>
|
||
<i class="fas fa-envelope-open-text" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch run readiness" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-readiness>
|
||
<i class="fas fa-user-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review AI summary Telegram dispatch run receipt" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review AI summary Telegram dispatch closeout" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="封存 queue review AI summary Telegram dispatch archive" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-archive>
|
||
<i class="fas fa-folder-tree" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch archive summary" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-archive-summary>
|
||
<i class="fas fa-file-lines" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch report input" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-input>
|
||
<i class="fas fa-chart-simple" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary Telegram dispatch report run package" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-package>
|
||
<i class="fas fa-box-open" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch report run readiness" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-readiness>
|
||
<i class="fas fa-clipboard-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review AI summary Telegram dispatch report run receipt" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review AI summary Telegram dispatch report closeout" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="封存 queue review AI summary Telegram dispatch report archive" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-archive>
|
||
<i class="fas fa-box-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch report archive summary" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-archive-summary>
|
||
<i class="fas fa-file-signature" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch report catalog handoff" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-handoff>
|
||
<i class="fas fa-table-list" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch report catalog index" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-index>
|
||
<i class="fas fa-list-ol" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch report catalog write preflight" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-write-preflight>
|
||
<i class="fas fa-file-shield" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch report catalog record write gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-write>
|
||
<i class="fas fa-database" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="產生 queue review AI summary Telegram dispatch report catalog record run package" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-package>
|
||
<i class="fas fa-box" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="檢查 queue review AI summary Telegram dispatch report catalog record run readiness" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-readiness>
|
||
<i class="fas fa-clipboard-check" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review AI summary Telegram dispatch report catalog record run receipt" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-receipt>
|
||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="審核 queue review AI summary Telegram dispatch report catalog record commit gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-commit>
|
||
<i class="fas fa-lock" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review AI summary Telegram dispatch report catalog record closeout gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-closeout>
|
||
<i class="fas fa-flag-checkered" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="封存 queue review AI summary Telegram dispatch report catalog record archive gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-archive>
|
||
<i class="fas fa-box-archive" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="整理 queue review AI summary Telegram dispatch report catalog record archive summary gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-archive-summary>
|
||
<i class="fas fa-file-signature" aria-hidden="true"></i>
|
||
</button>
|
||
<button class="market-intel-icon-button" type="button" title="收尾 queue review AI summary Telegram dispatch report catalog record final closeout gate" data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-final-closeout>
|
||
<i class="fas fa-circle-check" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-scheduler>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">SCHEDULER / ATTACH PLAN</p>
|
||
<h2 class="market-intel-preview-title">排程掛載計畫</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理排程掛載計畫" data-market-intel-scheduler-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-scheduler-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-scheduler-body>
|
||
<div class="market-intel-empty">讀取排程掛載計畫中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-match-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MATCH REVIEW / PLAN</p>
|
||
<h2 class="market-intel-preview-title">商品比對審核計畫</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理商品比對審核計畫" data-market-intel-match-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-match-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-match-review-body>
|
||
<div class="market-intel-empty">讀取商品比對審核計畫中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-opportunity>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">OPPORTUNITY / THREAT PLAN</p>
|
||
<h2 class="market-intel-preview-title">市場機會與威脅計畫</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理市場機會與威脅計畫" data-market-intel-opportunity-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-opportunity-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-opportunity-body>
|
||
<div class="market-intel-empty">讀取市場機會與威脅計畫中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-opportunity-scoring>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">OPPORTUNITY / SCORING MODEL</p>
|
||
<h2 class="market-intel-preview-title">機會威脅分數模型</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理機會威脅分數模型" data-market-intel-opportunity-scoring-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-opportunity-scoring-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-opportunity-scoring-body>
|
||
<div class="market-intel-empty">讀取機會威脅分數模型中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-opportunity-evidence>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">OPPORTUNITY / EVIDENCE BUNDLE</p>
|
||
<h2 class="market-intel-preview-title">機會威脅證據包</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理機會威脅證據包" data-market-intel-opportunity-evidence-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-opportunity-evidence-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-opportunity-evidence-body>
|
||
<div class="market-intel-empty">讀取機會威脅證據包中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-opportunity-alert>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">OPPORTUNITY / ALERT CANDIDATE</p>
|
||
<h2 class="market-intel-preview-title">機會威脅告警候選</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理機會威脅告警候選" data-market-intel-opportunity-alert-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-opportunity-alert-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-body>
|
||
<div class="market-intel-empty">讀取機會威脅告警候選中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-migration>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MIGRATION / BLUEPRINT</p>
|
||
<h2 class="market-intel-preview-title">Schema migration 草案</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 migration 草案" data-market-intel-migration-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-migration-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-migration-body>
|
||
<div class="market-intel-empty">讀取 migration 草案中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-migration-drill>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MIGRATION / APPLY DRILL</p>
|
||
<h2 class="market-intel-preview-title">Migration 套用演練</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理 migration 套用演練" data-market-intel-migration-drill-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-migration-drill-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-migration-drill-body>
|
||
<div class="market-intel-empty">讀取 migration 套用演練中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-catalog-review>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MIGRATION / CATALOG REVIEW</p>
|
||
<h2 class="market-intel-preview-title">正式 DB catalog 判讀</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理正式 DB catalog 判讀" data-market-intel-catalog-review-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-catalog-review-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-catalog-review-body>
|
||
<div class="market-intel-empty">讀取正式 DB catalog 判讀中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-live-smoke>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">MIGRATION / LIVE SMOKE</p>
|
||
<h2 class="market-intel-preview-title">正式 DB 只讀 smoke</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理正式 DB 只讀 smoke" data-market-intel-live-smoke-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-live-smoke-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-live-smoke-body>
|
||
<div class="market-intel-empty">讀取正式 DB 只讀 smoke 中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-live-inventory>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">DB INVENTORY / READ ONLY</p>
|
||
<h2 class="market-intel-preview-title">正式 DB 庫存總覽</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理正式 DB 庫存總覽" data-market-intel-live-inventory-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-live-inventory-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-live-inventory-body>
|
||
<div class="market-intel-empty">讀取正式 DB 庫存總覽中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-approval>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">WRITE APPROVAL / RUNBOOK</p>
|
||
<h2 class="market-intel-preview-title">正式寫入批准檢查</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理批准檢查" data-market-intel-approval-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-approval-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-approval-body>
|
||
<div class="market-intel-empty">讀取批准檢查中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="market-intel-panel" data-market-intel-deploy>
|
||
<div class="market-intel-preview-head">
|
||
<div>
|
||
<p class="market-intel-muted momo-mono mb-1">DEPLOYMENT / READINESS</p>
|
||
<h2 class="market-intel-preview-title">推版準備檢查</h2>
|
||
</div>
|
||
<button class="market-intel-icon-button" type="button" title="重新整理推版準備" data-market-intel-deploy-refresh>
|
||
<i class="fas fa-rotate-right" aria-hidden="true"></i>
|
||
</button>
|
||
</div>
|
||
<div class="market-intel-preview-meta" data-market-intel-deploy-meta>
|
||
<span class="market-intel-pill">loading</span>
|
||
</div>
|
||
<div data-market-intel-deploy-body>
|
||
<div class="market-intel-empty">讀取推版準備中...</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
(function () {
|
||
const root = document.querySelector('[data-market-intel-preview]');
|
||
const writerRoot = document.querySelector('[data-market-intel-writer]');
|
||
const cliRoot = document.querySelector('[data-market-intel-cli]');
|
||
const dbProbeRoot = document.querySelector('[data-market-intel-db-probe]');
|
||
const seedDiffRoot = document.querySelector('[data-market-intel-seed-diff]');
|
||
const legacyBridgeRoot = document.querySelector('[data-market-intel-legacy-bridge]');
|
||
const mcpReadinessRoot = document.querySelector('[data-market-intel-mcp-readiness]');
|
||
const mcpPreflightRoot = document.querySelector('[data-market-intel-mcp-preflight]');
|
||
const mcpActivationRoot = document.querySelector('[data-market-intel-mcp-activation]');
|
||
const mcpFetchGateRoot = document.querySelector('[data-market-intel-mcp-fetch-gate]');
|
||
const mcpCompletionRoot = document.querySelector('[data-market-intel-mcp-completion]');
|
||
const mcpActivationEvidenceRoot = document.querySelector('[data-market-intel-mcp-activation-evidence]');
|
||
const mcpRuntimeSmokeRoot = document.querySelector('[data-market-intel-mcp-runtime-smoke]');
|
||
const mcpRuntimePromotionRoot = document.querySelector('[data-market-intel-mcp-runtime-promotion]');
|
||
const mcpManualFetchHandoffRoot = document.querySelector('[data-market-intel-mcp-manual-fetch-handoff]');
|
||
const mcpFetchTargetReviewRoot = document.querySelector('[data-market-intel-mcp-fetch-target-review]');
|
||
const mcpFetchRunPackageRoot = document.querySelector('[data-market-intel-mcp-fetch-run-package]');
|
||
const mcpFetchRunReadinessRoot = document.querySelector('[data-market-intel-mcp-fetch-run-readiness]');
|
||
const mcpFetchRunReceiptRoot = document.querySelector('[data-market-intel-mcp-fetch-run-receipt]');
|
||
const mcpFetchResultParserReviewRoot = document.querySelector('[data-market-intel-mcp-fetch-result-parser-review]');
|
||
const mcpFetchCandidateHandoffReviewRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-handoff-review]');
|
||
const mcpFetchCandidateQueueReviewRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-review]');
|
||
const mcpFetchCandidateQueueWriterPreflightRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-preflight]');
|
||
const mcpFetchCandidateQueueWriterCliReviewRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-cli-review]');
|
||
const mcpFetchCandidateQueueWriterRunPackageReviewRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review]');
|
||
const mcpFetchCandidateQueueWriterRunReadinessRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness]');
|
||
const mcpFetchCandidateQueueWriterRunReceiptReviewRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review]');
|
||
const mcpFetchCandidateQueueWriterRunCloseoutReviewRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review]');
|
||
const mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review]');
|
||
const mcpFetchCandidateQueueWriterReviewHandoffRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff]');
|
||
const mcpFetchCandidateQueueWriterReviewInventoryRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory]');
|
||
const mcpFetchCandidateQueueWriterReviewDecisionRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision]');
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval]');
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot = document.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight]');
|
||
const manualSampleRoot = document.querySelector('[data-market-intel-manual-sample]');
|
||
const sampleAcceptanceRoot = document.querySelector('[data-market-intel-sample-acceptance]');
|
||
const sampleReviewRoot = document.querySelector('[data-market-intel-sample-review]');
|
||
const schedulerRoot = document.querySelector('[data-market-intel-scheduler]');
|
||
const matchReviewRoot = document.querySelector('[data-market-intel-match-review]');
|
||
const opportunityRoot = document.querySelector('[data-market-intel-opportunity]');
|
||
const opportunityScoringRoot = document.querySelector('[data-market-intel-opportunity-scoring]');
|
||
const opportunityEvidenceRoot = document.querySelector('[data-market-intel-opportunity-evidence]');
|
||
const opportunityAlertRoot = document.querySelector('[data-market-intel-opportunity-alert]');
|
||
const migrationRoot = document.querySelector('[data-market-intel-migration]');
|
||
const migrationDrillRoot = document.querySelector('[data-market-intel-migration-drill]');
|
||
const catalogReviewRoot = document.querySelector('[data-market-intel-catalog-review]');
|
||
const liveSmokeRoot = document.querySelector('[data-market-intel-live-smoke]');
|
||
const liveInventoryRoot = document.querySelector('[data-market-intel-live-inventory]');
|
||
const approvalRoot = document.querySelector('[data-market-intel-approval]');
|
||
const deployRoot = document.querySelector('[data-market-intel-deploy]');
|
||
if (!root && !writerRoot && !cliRoot && !dbProbeRoot && !seedDiffRoot && !legacyBridgeRoot && !mcpReadinessRoot && !mcpPreflightRoot && !mcpActivationRoot && !mcpFetchGateRoot && !mcpCompletionRoot && !mcpActivationEvidenceRoot && !mcpRuntimeSmokeRoot && !mcpRuntimePromotionRoot && !mcpManualFetchHandoffRoot && !mcpFetchTargetReviewRoot && !mcpFetchRunPackageRoot && !mcpFetchRunReadinessRoot && !mcpFetchRunReceiptRoot && !mcpFetchResultParserReviewRoot && !mcpFetchCandidateHandoffReviewRoot && !mcpFetchCandidateQueueReviewRoot && !mcpFetchCandidateQueueWriterPreflightRoot && !mcpFetchCandidateQueueWriterCliReviewRoot && !mcpFetchCandidateQueueWriterRunPackageReviewRoot && !mcpFetchCandidateQueueWriterRunReadinessRoot && !mcpFetchCandidateQueueWriterRunReceiptReviewRoot && !mcpFetchCandidateQueueWriterRunCloseoutReviewRoot && !mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot && !mcpFetchCandidateQueueWriterReviewHandoffRoot && !mcpFetchCandidateQueueWriterReviewInventoryRoot && !mcpFetchCandidateQueueWriterReviewDecisionRoot && !mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot && !mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot && !manualSampleRoot && !sampleAcceptanceRoot && !sampleReviewRoot && !schedulerRoot && !matchReviewRoot && !opportunityRoot && !opportunityScoringRoot && !opportunityEvidenceRoot && !opportunityAlertRoot && !migrationRoot && !migrationDrillRoot && !catalogReviewRoot && !liveSmokeRoot && !liveInventoryRoot && !approvalRoot && !deployRoot) return;
|
||
|
||
const meta = root ? root.querySelector('[data-market-intel-preview-meta]') : null;
|
||
const body = root ? root.querySelector('[data-market-intel-preview-body]') : null;
|
||
const refresh = root ? root.querySelector('[data-market-intel-refresh]') : null;
|
||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
|
||
const endpoint = "{{ url_for('market_intel.market_intel_candidate_preview') }}?fetch=false&limit=20";
|
||
const writerMeta = writerRoot ? writerRoot.querySelector('[data-market-intel-writer-meta]') : null;
|
||
const writerBody = writerRoot ? writerRoot.querySelector('[data-market-intel-writer-body]') : null;
|
||
const writerRefresh = writerRoot ? writerRoot.querySelector('[data-market-intel-writer-refresh]') : null;
|
||
const writerEndpoint = "{{ url_for('market_intel.market_intel_platform_seed_writer_plan') }}";
|
||
const cliMeta = cliRoot ? cliRoot.querySelector('[data-market-intel-cli-meta]') : null;
|
||
const cliBody = cliRoot ? cliRoot.querySelector('[data-market-intel-cli-body]') : null;
|
||
const cliRefresh = cliRoot ? cliRoot.querySelector('[data-market-intel-cli-refresh]') : null;
|
||
const cliEndpoint = "{{ url_for('market_intel.market_intel_seed_writer_cli_status') }}";
|
||
const dbProbeMeta = dbProbeRoot ? dbProbeRoot.querySelector('[data-market-intel-db-probe-meta]') : null;
|
||
const dbProbeBody = dbProbeRoot ? dbProbeRoot.querySelector('[data-market-intel-db-probe-body]') : null;
|
||
const dbProbeRefresh = dbProbeRoot ? dbProbeRoot.querySelector('[data-market-intel-db-probe-refresh]') : null;
|
||
const dbProbeEndpoint = "{{ url_for('market_intel.market_intel_schema_db_probe') }}?execute=false";
|
||
const seedDiffMeta = seedDiffRoot ? seedDiffRoot.querySelector('[data-market-intel-seed-diff-meta]') : null;
|
||
const seedDiffBody = seedDiffRoot ? seedDiffRoot.querySelector('[data-market-intel-seed-diff-body]') : null;
|
||
const seedDiffRefresh = seedDiffRoot ? seedDiffRoot.querySelector('[data-market-intel-seed-diff-refresh]') : null;
|
||
const seedDiffEndpoint = "{{ url_for('market_intel.market_intel_platform_seed_db_diff') }}?execute=false";
|
||
const legacyBridgeMeta = legacyBridgeRoot ? legacyBridgeRoot.querySelector('[data-market-intel-legacy-bridge-meta]') : null;
|
||
const legacyBridgeBody = legacyBridgeRoot ? legacyBridgeRoot.querySelector('[data-market-intel-legacy-bridge-body]') : null;
|
||
const legacyBridgeRefresh = legacyBridgeRoot ? legacyBridgeRoot.querySelector('[data-market-intel-legacy-bridge-refresh]') : null;
|
||
const legacyBridgeEndpoint = "{{ url_for('market_intel.market_intel_legacy_source_bridge') }}?execute=false&limit=5";
|
||
const mcpReadinessMeta = mcpReadinessRoot ? mcpReadinessRoot.querySelector('[data-market-intel-mcp-readiness-meta]') : null;
|
||
const mcpReadinessBody = mcpReadinessRoot ? mcpReadinessRoot.querySelector('[data-market-intel-mcp-readiness-body]') : null;
|
||
const mcpReadinessRefresh = mcpReadinessRoot ? mcpReadinessRoot.querySelector('[data-market-intel-mcp-readiness-refresh]') : null;
|
||
const mcpReadinessEndpoint = "{{ url_for('market_intel.market_intel_mcp_readiness') }}?execute=false&timeout=3";
|
||
const mcpPreflightMeta = mcpPreflightRoot ? mcpPreflightRoot.querySelector('[data-market-intel-mcp-preflight-meta]') : null;
|
||
const mcpPreflightBody = mcpPreflightRoot ? mcpPreflightRoot.querySelector('[data-market-intel-mcp-preflight-body]') : null;
|
||
const mcpPreflightRefresh = mcpPreflightRoot ? mcpPreflightRoot.querySelector('[data-market-intel-mcp-preflight-refresh]') : null;
|
||
const mcpPreflightEndpoint = "{{ url_for('market_intel.market_intel_mcp_deploy_preflight') }}";
|
||
const mcpActivationMeta = mcpActivationRoot ? mcpActivationRoot.querySelector('[data-market-intel-mcp-activation-meta]') : null;
|
||
const mcpActivationBody = mcpActivationRoot ? mcpActivationRoot.querySelector('[data-market-intel-mcp-activation-body]') : null;
|
||
const mcpActivationRefresh = mcpActivationRoot ? mcpActivationRoot.querySelector('[data-market-intel-mcp-activation-refresh]') : null;
|
||
const mcpActivationEndpoint = "{{ url_for('market_intel.market_intel_mcp_activation_runbook') }}";
|
||
const mcpFetchGateMeta = mcpFetchGateRoot ? mcpFetchGateRoot.querySelector('[data-market-intel-mcp-fetch-gate-meta]') : null;
|
||
const mcpFetchGateBody = mcpFetchGateRoot ? mcpFetchGateRoot.querySelector('[data-market-intel-mcp-fetch-gate-body]') : null;
|
||
const mcpFetchGateRefresh = mcpFetchGateRoot ? mcpFetchGateRoot.querySelector('[data-market-intel-mcp-fetch-gate-refresh]') : null;
|
||
const mcpFetchGateEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_gate') }}?fetch=false&execute=false";
|
||
const mcpCompletionMeta = mcpCompletionRoot ? mcpCompletionRoot.querySelector('[data-market-intel-mcp-completion-meta]') : null;
|
||
const mcpCompletionBody = mcpCompletionRoot ? mcpCompletionRoot.querySelector('[data-market-intel-mcp-completion-body]') : null;
|
||
const mcpCompletionRefresh = mcpCompletionRoot ? mcpCompletionRoot.querySelector('[data-market-intel-mcp-completion-refresh]') : null;
|
||
const mcpCompletionEndpoint = "{{ url_for('market_intel.market_intel_mcp_completion_audit') }}";
|
||
const mcpActivationEvidenceMeta = mcpActivationEvidenceRoot ? mcpActivationEvidenceRoot.querySelector('[data-market-intel-mcp-activation-evidence-meta]') : null;
|
||
const mcpActivationEvidenceBody = mcpActivationEvidenceRoot ? mcpActivationEvidenceRoot.querySelector('[data-market-intel-mcp-activation-evidence-body]') : null;
|
||
const mcpActivationEvidenceInput = mcpActivationEvidenceRoot ? mcpActivationEvidenceRoot.querySelector('[data-market-intel-mcp-activation-evidence-input]') : null;
|
||
const mcpActivationEvidenceReview = mcpActivationEvidenceRoot ? mcpActivationEvidenceRoot.querySelector('[data-market-intel-mcp-activation-evidence-review]') : null;
|
||
const mcpActivationEvidenceRefresh = mcpActivationEvidenceRoot ? mcpActivationEvidenceRoot.querySelector('[data-market-intel-mcp-activation-evidence-refresh]') : null;
|
||
const mcpActivationEvidenceEndpoint = "{{ url_for('market_intel.market_intel_mcp_activation_evidence') }}";
|
||
const mcpRuntimeSmokeMeta = mcpRuntimeSmokeRoot ? mcpRuntimeSmokeRoot.querySelector('[data-market-intel-mcp-runtime-smoke-meta]') : null;
|
||
const mcpRuntimeSmokeBody = mcpRuntimeSmokeRoot ? mcpRuntimeSmokeRoot.querySelector('[data-market-intel-mcp-runtime-smoke-body]') : null;
|
||
const mcpRuntimeSmokeInput = mcpRuntimeSmokeRoot ? mcpRuntimeSmokeRoot.querySelector('[data-market-intel-mcp-runtime-smoke-input]') : null;
|
||
const mcpRuntimeSmokeReview = mcpRuntimeSmokeRoot ? mcpRuntimeSmokeRoot.querySelector('[data-market-intel-mcp-runtime-smoke-review]') : null;
|
||
const mcpRuntimeSmokeRefresh = mcpRuntimeSmokeRoot ? mcpRuntimeSmokeRoot.querySelector('[data-market-intel-mcp-runtime-smoke-refresh]') : null;
|
||
const mcpRuntimeSmokeEndpoint = "{{ url_for('market_intel.market_intel_mcp_runtime_smoke_receipt') }}";
|
||
const mcpRuntimePromotionMeta = mcpRuntimePromotionRoot ? mcpRuntimePromotionRoot.querySelector('[data-market-intel-mcp-runtime-promotion-meta]') : null;
|
||
const mcpRuntimePromotionBody = mcpRuntimePromotionRoot ? mcpRuntimePromotionRoot.querySelector('[data-market-intel-mcp-runtime-promotion-body]') : null;
|
||
const mcpRuntimePromotionInput = mcpRuntimePromotionRoot ? mcpRuntimePromotionRoot.querySelector('[data-market-intel-mcp-runtime-promotion-input]') : null;
|
||
const mcpRuntimePromotionReview = mcpRuntimePromotionRoot ? mcpRuntimePromotionRoot.querySelector('[data-market-intel-mcp-runtime-promotion-review]') : null;
|
||
const mcpRuntimePromotionRefresh = mcpRuntimePromotionRoot ? mcpRuntimePromotionRoot.querySelector('[data-market-intel-mcp-runtime-promotion-refresh]') : null;
|
||
const mcpRuntimePromotionEndpoint = "{{ url_for('market_intel.market_intel_mcp_runtime_promotion') }}";
|
||
const mcpManualFetchHandoffMeta = mcpManualFetchHandoffRoot ? mcpManualFetchHandoffRoot.querySelector('[data-market-intel-mcp-manual-fetch-handoff-meta]') : null;
|
||
const mcpManualFetchHandoffBody = mcpManualFetchHandoffRoot ? mcpManualFetchHandoffRoot.querySelector('[data-market-intel-mcp-manual-fetch-handoff-body]') : null;
|
||
const mcpManualFetchHandoffInput = mcpManualFetchHandoffRoot ? mcpManualFetchHandoffRoot.querySelector('[data-market-intel-mcp-manual-fetch-handoff-input]') : null;
|
||
const mcpManualFetchHandoffReview = mcpManualFetchHandoffRoot ? mcpManualFetchHandoffRoot.querySelector('[data-market-intel-mcp-manual-fetch-handoff-review]') : null;
|
||
const mcpManualFetchHandoffRefresh = mcpManualFetchHandoffRoot ? mcpManualFetchHandoffRoot.querySelector('[data-market-intel-mcp-manual-fetch-handoff-refresh]') : null;
|
||
const mcpManualFetchHandoffEndpoint = "{{ url_for('market_intel.market_intel_mcp_manual_fetch_handoff') }}";
|
||
const mcpFetchTargetReviewMeta = mcpFetchTargetReviewRoot ? mcpFetchTargetReviewRoot.querySelector('[data-market-intel-mcp-fetch-target-review-meta]') : null;
|
||
const mcpFetchTargetReviewBody = mcpFetchTargetReviewRoot ? mcpFetchTargetReviewRoot.querySelector('[data-market-intel-mcp-fetch-target-review-body]') : null;
|
||
const mcpFetchTargetReviewInput = mcpFetchTargetReviewRoot ? mcpFetchTargetReviewRoot.querySelector('[data-market-intel-mcp-fetch-target-review-input]') : null;
|
||
const mcpFetchTargetReviewReview = mcpFetchTargetReviewRoot ? mcpFetchTargetReviewRoot.querySelector('[data-market-intel-mcp-fetch-target-review-review]') : null;
|
||
const mcpFetchTargetReviewRefresh = mcpFetchTargetReviewRoot ? mcpFetchTargetReviewRoot.querySelector('[data-market-intel-mcp-fetch-target-review-refresh]') : null;
|
||
const mcpFetchTargetReviewEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_target_review') }}";
|
||
const mcpFetchRunPackageMeta = mcpFetchRunPackageRoot ? mcpFetchRunPackageRoot.querySelector('[data-market-intel-mcp-fetch-run-package-meta]') : null;
|
||
const mcpFetchRunPackageBody = mcpFetchRunPackageRoot ? mcpFetchRunPackageRoot.querySelector('[data-market-intel-mcp-fetch-run-package-body]') : null;
|
||
const mcpFetchRunPackageInput = mcpFetchRunPackageRoot ? mcpFetchRunPackageRoot.querySelector('[data-market-intel-mcp-fetch-run-package-input]') : null;
|
||
const mcpFetchRunPackageReview = mcpFetchRunPackageRoot ? mcpFetchRunPackageRoot.querySelector('[data-market-intel-mcp-fetch-run-package-review]') : null;
|
||
const mcpFetchRunPackageRefresh = mcpFetchRunPackageRoot ? mcpFetchRunPackageRoot.querySelector('[data-market-intel-mcp-fetch-run-package-refresh]') : null;
|
||
const mcpFetchRunPackageEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_run_package') }}";
|
||
const mcpFetchRunReadinessMeta = mcpFetchRunReadinessRoot ? mcpFetchRunReadinessRoot.querySelector('[data-market-intel-mcp-fetch-run-readiness-meta]') : null;
|
||
const mcpFetchRunReadinessBody = mcpFetchRunReadinessRoot ? mcpFetchRunReadinessRoot.querySelector('[data-market-intel-mcp-fetch-run-readiness-body]') : null;
|
||
const mcpFetchRunReadinessInput = mcpFetchRunReadinessRoot ? mcpFetchRunReadinessRoot.querySelector('[data-market-intel-mcp-fetch-run-readiness-input]') : null;
|
||
const mcpFetchRunReadinessReview = mcpFetchRunReadinessRoot ? mcpFetchRunReadinessRoot.querySelector('[data-market-intel-mcp-fetch-run-readiness-review]') : null;
|
||
const mcpFetchRunReadinessRefresh = mcpFetchRunReadinessRoot ? mcpFetchRunReadinessRoot.querySelector('[data-market-intel-mcp-fetch-run-readiness-refresh]') : null;
|
||
const mcpFetchRunReadinessEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_run_readiness') }}";
|
||
const mcpFetchRunReceiptMeta = mcpFetchRunReceiptRoot ? mcpFetchRunReceiptRoot.querySelector('[data-market-intel-mcp-fetch-run-receipt-meta]') : null;
|
||
const mcpFetchRunReceiptBody = mcpFetchRunReceiptRoot ? mcpFetchRunReceiptRoot.querySelector('[data-market-intel-mcp-fetch-run-receipt-body]') : null;
|
||
const mcpFetchRunReceiptInput = mcpFetchRunReceiptRoot ? mcpFetchRunReceiptRoot.querySelector('[data-market-intel-mcp-fetch-run-receipt-input]') : null;
|
||
const mcpFetchRunReceiptReview = mcpFetchRunReceiptRoot ? mcpFetchRunReceiptRoot.querySelector('[data-market-intel-mcp-fetch-run-receipt-review]') : null;
|
||
const mcpFetchRunReceiptRefresh = mcpFetchRunReceiptRoot ? mcpFetchRunReceiptRoot.querySelector('[data-market-intel-mcp-fetch-run-receipt-refresh]') : null;
|
||
const mcpFetchRunReceiptEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_run_receipt') }}";
|
||
const mcpFetchResultParserReviewMeta = mcpFetchResultParserReviewRoot ? mcpFetchResultParserReviewRoot.querySelector('[data-market-intel-mcp-fetch-result-parser-review-meta]') : null;
|
||
const mcpFetchResultParserReviewBody = mcpFetchResultParserReviewRoot ? mcpFetchResultParserReviewRoot.querySelector('[data-market-intel-mcp-fetch-result-parser-review-body]') : null;
|
||
const mcpFetchResultParserReviewInput = mcpFetchResultParserReviewRoot ? mcpFetchResultParserReviewRoot.querySelector('[data-market-intel-mcp-fetch-result-parser-review-input]') : null;
|
||
const mcpFetchResultParserReviewReview = mcpFetchResultParserReviewRoot ? mcpFetchResultParserReviewRoot.querySelector('[data-market-intel-mcp-fetch-result-parser-review-review]') : null;
|
||
const mcpFetchResultParserReviewRefresh = mcpFetchResultParserReviewRoot ? mcpFetchResultParserReviewRoot.querySelector('[data-market-intel-mcp-fetch-result-parser-review-refresh]') : null;
|
||
const mcpFetchResultParserReviewEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_result_parser_review') }}";
|
||
const mcpFetchCandidateHandoffReviewMeta = mcpFetchCandidateHandoffReviewRoot ? mcpFetchCandidateHandoffReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-handoff-review-meta]') : null;
|
||
const mcpFetchCandidateHandoffReviewBody = mcpFetchCandidateHandoffReviewRoot ? mcpFetchCandidateHandoffReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-handoff-review-body]') : null;
|
||
const mcpFetchCandidateHandoffReviewInput = mcpFetchCandidateHandoffReviewRoot ? mcpFetchCandidateHandoffReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-handoff-review-input]') : null;
|
||
const mcpFetchCandidateHandoffReviewReview = mcpFetchCandidateHandoffReviewRoot ? mcpFetchCandidateHandoffReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-handoff-review-review]') : null;
|
||
const mcpFetchCandidateHandoffReviewRefresh = mcpFetchCandidateHandoffReviewRoot ? mcpFetchCandidateHandoffReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-handoff-review-refresh]') : null;
|
||
const mcpFetchCandidateHandoffReviewEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_handoff_review') }}";
|
||
const mcpFetchCandidateQueueReviewMeta = mcpFetchCandidateQueueReviewRoot ? mcpFetchCandidateQueueReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-review-meta]') : null;
|
||
const mcpFetchCandidateQueueReviewBody = mcpFetchCandidateQueueReviewRoot ? mcpFetchCandidateQueueReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-review-body]') : null;
|
||
const mcpFetchCandidateQueueReviewInput = mcpFetchCandidateQueueReviewRoot ? mcpFetchCandidateQueueReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-review-input]') : null;
|
||
const mcpFetchCandidateQueueReviewReview = mcpFetchCandidateQueueReviewRoot ? mcpFetchCandidateQueueReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-review-review]') : null;
|
||
const mcpFetchCandidateQueueReviewRefresh = mcpFetchCandidateQueueReviewRoot ? mcpFetchCandidateQueueReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-review-refresh]') : null;
|
||
const mcpFetchCandidateQueueReviewEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_review') }}";
|
||
const mcpFetchCandidateQueueWriterPreflightMeta = mcpFetchCandidateQueueWriterPreflightRoot ? mcpFetchCandidateQueueWriterPreflightRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-preflight-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterPreflightBody = mcpFetchCandidateQueueWriterPreflightRoot ? mcpFetchCandidateQueueWriterPreflightRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-preflight-body]') : null;
|
||
const mcpFetchCandidateQueueWriterPreflightInput = mcpFetchCandidateQueueWriterPreflightRoot ? mcpFetchCandidateQueueWriterPreflightRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-preflight-input]') : null;
|
||
const mcpFetchCandidateQueueWriterPreflightReview = mcpFetchCandidateQueueWriterPreflightRoot ? mcpFetchCandidateQueueWriterPreflightRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-preflight-review]') : null;
|
||
const mcpFetchCandidateQueueWriterPreflightRefresh = mcpFetchCandidateQueueWriterPreflightRoot ? mcpFetchCandidateQueueWriterPreflightRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-preflight-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterPreflightEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_preflight') }}";
|
||
const mcpFetchCandidateQueueWriterCliReviewMeta = mcpFetchCandidateQueueWriterCliReviewRoot ? mcpFetchCandidateQueueWriterCliReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterCliReviewBody = mcpFetchCandidateQueueWriterCliReviewRoot ? mcpFetchCandidateQueueWriterCliReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-body]') : null;
|
||
const mcpFetchCandidateQueueWriterCliReviewInput = mcpFetchCandidateQueueWriterCliReviewRoot ? mcpFetchCandidateQueueWriterCliReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-input]') : null;
|
||
const mcpFetchCandidateQueueWriterCliReviewReview = mcpFetchCandidateQueueWriterCliReviewRoot ? mcpFetchCandidateQueueWriterCliReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-review]') : null;
|
||
const mcpFetchCandidateQueueWriterCliReviewRefresh = mcpFetchCandidateQueueWriterCliReviewRoot ? mcpFetchCandidateQueueWriterCliReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterCliReviewEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_cli_review') }}";
|
||
const mcpFetchCandidateQueueWriterRunPackageReviewMeta = mcpFetchCandidateQueueWriterRunPackageReviewRoot ? mcpFetchCandidateQueueWriterRunPackageReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterRunPackageReviewBody = mcpFetchCandidateQueueWriterRunPackageReviewRoot ? mcpFetchCandidateQueueWriterRunPackageReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-body]') : null;
|
||
const mcpFetchCandidateQueueWriterRunPackageReviewInput = mcpFetchCandidateQueueWriterRunPackageReviewRoot ? mcpFetchCandidateQueueWriterRunPackageReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-input]') : null;
|
||
const mcpFetchCandidateQueueWriterRunPackageReviewReview = mcpFetchCandidateQueueWriterRunPackageReviewRoot ? mcpFetchCandidateQueueWriterRunPackageReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-review]') : null;
|
||
const mcpFetchCandidateQueueWriterRunPackageReviewRefresh = mcpFetchCandidateQueueWriterRunPackageReviewRoot ? mcpFetchCandidateQueueWriterRunPackageReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterRunPackageReviewEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_run_package_review') }}";
|
||
const mcpFetchCandidateQueueWriterRunReadinessMeta = mcpFetchCandidateQueueWriterRunReadinessRoot ? mcpFetchCandidateQueueWriterRunReadinessRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterRunReadinessBody = mcpFetchCandidateQueueWriterRunReadinessRoot ? mcpFetchCandidateQueueWriterRunReadinessRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-body]') : null;
|
||
const mcpFetchCandidateQueueWriterRunReadinessInput = mcpFetchCandidateQueueWriterRunReadinessRoot ? mcpFetchCandidateQueueWriterRunReadinessRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-input]') : null;
|
||
const mcpFetchCandidateQueueWriterRunReadinessReview = mcpFetchCandidateQueueWriterRunReadinessRoot ? mcpFetchCandidateQueueWriterRunReadinessRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-review]') : null;
|
||
const mcpFetchCandidateQueueWriterRunReadinessRefresh = mcpFetchCandidateQueueWriterRunReadinessRoot ? mcpFetchCandidateQueueWriterRunReadinessRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterRunReadinessEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_run_readiness') }}";
|
||
const mcpFetchCandidateQueueWriterRunReceiptReviewMeta = mcpFetchCandidateQueueWriterRunReceiptReviewRoot ? mcpFetchCandidateQueueWriterRunReceiptReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterRunReceiptReviewBody = mcpFetchCandidateQueueWriterRunReceiptReviewRoot ? mcpFetchCandidateQueueWriterRunReceiptReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-body]') : null;
|
||
const mcpFetchCandidateQueueWriterRunReceiptReviewInput = mcpFetchCandidateQueueWriterRunReceiptReviewRoot ? mcpFetchCandidateQueueWriterRunReceiptReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-input]') : null;
|
||
const mcpFetchCandidateQueueWriterRunReceiptReviewReview = mcpFetchCandidateQueueWriterRunReceiptReviewRoot ? mcpFetchCandidateQueueWriterRunReceiptReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-review]') : null;
|
||
const mcpFetchCandidateQueueWriterRunReceiptReviewRefresh = mcpFetchCandidateQueueWriterRunReceiptReviewRoot ? mcpFetchCandidateQueueWriterRunReceiptReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterRunReceiptReviewEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_run_receipt_review') }}";
|
||
const mcpFetchCandidateQueueWriterRunCloseoutReviewMeta = mcpFetchCandidateQueueWriterRunCloseoutReviewRoot ? mcpFetchCandidateQueueWriterRunCloseoutReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterRunCloseoutReviewBody = mcpFetchCandidateQueueWriterRunCloseoutReviewRoot ? mcpFetchCandidateQueueWriterRunCloseoutReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-body]') : null;
|
||
const mcpFetchCandidateQueueWriterRunCloseoutReviewInput = mcpFetchCandidateQueueWriterRunCloseoutReviewRoot ? mcpFetchCandidateQueueWriterRunCloseoutReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-input]') : null;
|
||
const mcpFetchCandidateQueueWriterRunCloseoutReviewReview = mcpFetchCandidateQueueWriterRunCloseoutReviewRoot ? mcpFetchCandidateQueueWriterRunCloseoutReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-review]') : null;
|
||
const mcpFetchCandidateQueueWriterRunCloseoutReviewRefresh = mcpFetchCandidateQueueWriterRunCloseoutReviewRoot ? mcpFetchCandidateQueueWriterRunCloseoutReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterRunCloseoutReviewEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_run_closeout_review') }}";
|
||
const mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewMeta = mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot ? mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody = mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot ? mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-body]') : null;
|
||
const mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewInput = mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot ? mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-input]') : null;
|
||
const mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewReview = mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot ? mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-review]') : null;
|
||
const mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRefresh = mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot ? mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_post_closeout_inventory_review') }}";
|
||
const mcpFetchCandidateQueueWriterReviewHandoffMeta = mcpFetchCandidateQueueWriterReviewHandoffRoot ? mcpFetchCandidateQueueWriterReviewHandoffRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewHandoffBody = mcpFetchCandidateQueueWriterReviewHandoffRoot ? mcpFetchCandidateQueueWriterReviewHandoffRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-body]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewHandoffInput = mcpFetchCandidateQueueWriterReviewHandoffRoot ? mcpFetchCandidateQueueWriterReviewHandoffRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-input]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewHandoffReview = mcpFetchCandidateQueueWriterReviewHandoffRoot ? mcpFetchCandidateQueueWriterReviewHandoffRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-review]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewHandoffRefresh = mcpFetchCandidateQueueWriterReviewHandoffRoot ? mcpFetchCandidateQueueWriterReviewHandoffRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewHandoffEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_review_handoff') }}";
|
||
const mcpFetchCandidateQueueWriterReviewInventoryMeta = mcpFetchCandidateQueueWriterReviewInventoryRoot ? mcpFetchCandidateQueueWriterReviewInventoryRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewInventoryBody = mcpFetchCandidateQueueWriterReviewInventoryRoot ? mcpFetchCandidateQueueWriterReviewInventoryRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-body]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewInventoryInput = mcpFetchCandidateQueueWriterReviewInventoryRoot ? mcpFetchCandidateQueueWriterReviewInventoryRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-input]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewInventoryReview = mcpFetchCandidateQueueWriterReviewInventoryRoot ? mcpFetchCandidateQueueWriterReviewInventoryRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-review]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewInventoryRefresh = mcpFetchCandidateQueueWriterReviewInventoryRoot ? mcpFetchCandidateQueueWriterReviewInventoryRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewInventoryEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_review_inventory') }}";
|
||
const mcpFetchCandidateQueueWriterReviewDecisionMeta = mcpFetchCandidateQueueWriterReviewDecisionRoot ? mcpFetchCandidateQueueWriterReviewDecisionRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionBody = mcpFetchCandidateQueueWriterReviewDecisionRoot ? mcpFetchCandidateQueueWriterReviewDecisionRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-body]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionInput = mcpFetchCandidateQueueWriterReviewDecisionRoot ? mcpFetchCandidateQueueWriterReviewDecisionRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-input]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionReview = mcpFetchCandidateQueueWriterReviewDecisionRoot ? mcpFetchCandidateQueueWriterReviewDecisionRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-review]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionRefresh = mcpFetchCandidateQueueWriterReviewDecisionRoot ? mcpFetchCandidateQueueWriterReviewDecisionRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_review_decision') }}";
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalMeta = mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot ? mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalBody = mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot ? mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-body]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalInput = mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot ? mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-input]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalReview = mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot ? mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-review]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalRefresh = mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot ? mcpFetchCandidateQueueWriterReviewDecisionApprovalRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_review_decision_approval') }}";
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightMeta = mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot ? mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-meta]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody = mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot ? mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-body]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightInput = mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot ? mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-input]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightReview = mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot ? mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-review]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRefresh = mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot ? mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRoot.querySelector('[data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-refresh]') : null;
|
||
const mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight') }}";
|
||
const manualSampleMeta = manualSampleRoot ? manualSampleRoot.querySelector('[data-market-intel-manual-sample-meta]') : null;
|
||
const manualSampleBody = manualSampleRoot ? manualSampleRoot.querySelector('[data-market-intel-manual-sample-body]') : null;
|
||
const manualSampleRefresh = manualSampleRoot ? manualSampleRoot.querySelector('[data-market-intel-manual-sample-refresh]') : null;
|
||
const manualSampleEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_plan') }}";
|
||
const sampleAcceptanceMeta = sampleAcceptanceRoot ? sampleAcceptanceRoot.querySelector('[data-market-intel-sample-acceptance-meta]') : null;
|
||
const sampleAcceptanceBody = sampleAcceptanceRoot ? sampleAcceptanceRoot.querySelector('[data-market-intel-sample-acceptance-body]') : null;
|
||
const sampleAcceptanceRefresh = sampleAcceptanceRoot ? sampleAcceptanceRoot.querySelector('[data-market-intel-sample-acceptance-refresh]') : null;
|
||
const sampleAcceptanceEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_acceptance') }}";
|
||
const sampleReviewMeta = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-meta]') : null;
|
||
const sampleReviewBody = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-body]') : null;
|
||
const sampleReviewRefresh = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-refresh]') : null;
|
||
const sampleReviewInput = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-input]') : null;
|
||
const sampleReviewEvaluate = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-review-evaluate]') : null;
|
||
const sampleCandidateHandoff = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-handoff]') : null;
|
||
const sampleCandidateQueueDraft = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-draft]') : null;
|
||
const sampleCandidateQueueApproval = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-approval]') : null;
|
||
const sampleCandidateQueueTransaction = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-transaction]') : null;
|
||
const sampleCandidateQueueWriter = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-writer]') : null;
|
||
const sampleCandidateQueuePreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-preflight]') : null;
|
||
const sampleCandidateQueuePostwriteSmoke = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-postwrite-smoke]') : null;
|
||
const sampleCandidateQueueOperatorDrill = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-operator-drill]') : null;
|
||
const sampleCandidateQueueRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-run-package]') : null;
|
||
const sampleCandidateQueueRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-run-readiness]') : null;
|
||
const sampleCandidateQueueRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-run-receipt]') : null;
|
||
const sampleCandidateQueueRunCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-run-closeout]') : null;
|
||
const sampleCandidateQueueReviewHandoff = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-handoff]') : null;
|
||
const sampleCandidateQueueReviewInventory = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-inventory]') : null;
|
||
const sampleCandidateQueueReviewDecision = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision]') : null;
|
||
const sampleCandidateQueueReviewDecisionApproval = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-approval]') : null;
|
||
const sampleCandidateQueueReviewDecisionTransaction = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-transaction]') : null;
|
||
const sampleCandidateQueueReviewDecisionPreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-preflight]') : null;
|
||
const sampleCandidateQueueReviewDecisionPostwriteSmoke = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-postwrite-smoke]') : null;
|
||
const sampleCandidateQueueReviewDecisionOperatorDrill = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-operator-drill]') : null;
|
||
const sampleCandidateQueueReviewDecisionRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-run-package]') : null;
|
||
const sampleCandidateQueueReviewDecisionRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-run-readiness]') : null;
|
||
const sampleCandidateQueueReviewDecisionWriter = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-writer]') : null;
|
||
const sampleCandidateQueueReviewDecisionRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-run-receipt]') : null;
|
||
const sampleCandidateQueueReviewDecisionRunCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-run-closeout]') : null;
|
||
const sampleCandidateQueueReviewDecisionPostCloseoutInventory = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-decision-post-closeout-inventory]') : null;
|
||
const sampleCandidateQueueReviewCompletionArchive = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-completion-archive]') : null;
|
||
const sampleCandidateQueueReviewArchiveSummary = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-archive-summary]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-preflight]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-run-package]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryOutputReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-output-receipt]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistencePreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-preflight]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTransaction = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-transaction]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceWriterPreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-writer-preflight]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-package]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-readiness]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-receipt]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-run-closeout]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-gate]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-package]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-readiness]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-run-receipt]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-closeout]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-archive]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-archive-summary]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-input]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-package]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-readiness]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-run-receipt]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-closeout]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-archive]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-archive-summary]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-handoff]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-index]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-write-preflight]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-write]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-package]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-readiness]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-run-receipt]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-commit]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-closeout]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-archive]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-archive-summary]') : null;
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout = sampleReviewRoot ? sampleReviewRoot.querySelector('[data-market-intel-sample-candidate-queue-review-ai-summary-persistence-telegram-dispatch-report-catalog-record-final-closeout]') : null;
|
||
const sampleReviewEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_review') }}";
|
||
const sampleReviewEvaluateEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_review_evaluate') }}";
|
||
const sampleCandidateHandoffEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_handoff') }}";
|
||
const sampleCandidateQueueDraftEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_draft') }}";
|
||
const sampleCandidateQueueApprovalEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_approval') }}";
|
||
const sampleCandidateQueueTransactionEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_transaction') }}";
|
||
const sampleCandidateQueueWriterEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_status') }}";
|
||
const sampleCandidateQueuePreflightEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_preflight') }}";
|
||
const sampleCandidateQueuePostwriteSmokeEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_postwrite_smoke') }}";
|
||
const sampleCandidateQueueOperatorDrillEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_operator_drill') }}";
|
||
const sampleCandidateQueueRunPackageEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_run_package') }}";
|
||
const sampleCandidateQueueRunReadinessEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_run_readiness') }}";
|
||
const sampleCandidateQueueRunReceiptEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_run_receipt') }}";
|
||
const sampleCandidateQueueRunCloseoutEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_writer_run_closeout') }}";
|
||
const sampleCandidateQueueReviewHandoffEndpoint = "{{ url_for('market_intel.market_intel_manual_sample_candidate_queue_review_handoff') }}";
|
||
const sampleCandidateQueueReviewInventoryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_inventory') }}";
|
||
const sampleCandidateQueueReviewDecisionEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision') }}";
|
||
const sampleCandidateQueueReviewDecisionApprovalEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_approval') }}";
|
||
const sampleCandidateQueueReviewDecisionTransactionEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_transaction') }}";
|
||
const sampleCandidateQueueReviewDecisionPreflightEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_preflight') }}";
|
||
const sampleCandidateQueueReviewDecisionPostwriteSmokeEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_postwrite_smoke') }}";
|
||
const sampleCandidateQueueReviewDecisionOperatorDrillEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_operator_drill') }}";
|
||
const sampleCandidateQueueReviewDecisionRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_run_package') }}";
|
||
const sampleCandidateQueueReviewDecisionRunReadinessEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_run_readiness') }}";
|
||
const sampleCandidateQueueReviewDecisionWriterEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_status') }}";
|
||
const sampleCandidateQueueReviewDecisionRunReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_run_receipt') }}";
|
||
const sampleCandidateQueueReviewDecisionRunCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_writer_run_closeout') }}";
|
||
const sampleCandidateQueueReviewDecisionPostCloseoutInventoryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_decision_post_closeout_inventory') }}";
|
||
const sampleCandidateQueueReviewCompletionArchiveEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_completion_archive') }}";
|
||
const sampleCandidateQueueReviewArchiveSummaryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_archive_summary') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPreflightEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_preflight') }}";
|
||
const sampleCandidateQueueReviewAiSummaryRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_run_package') }}";
|
||
const sampleCandidateQueueReviewAiSummaryOutputReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_output_receipt') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistencePreflightEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_preflight') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTransactionEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_transaction') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceWriterPreflightEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_writer_preflight') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_run_package') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunReadinessEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_run_readiness') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_run_receipt') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceRunCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_run_closeout') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGateEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_package') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadinessEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_readiness') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_receipt') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_closeout') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummaryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive_summary') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInputEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_input') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_package') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadinessEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_readiness') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_receipt') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_closeout') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummaryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive_summary') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoffEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_handoff') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndexEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_index') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflightEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_write_preflight') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWriteEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_write') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackageEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_package') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadinessEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_readiness') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceiptEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_receipt') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommitEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_commit') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_closeout') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_archive') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummaryEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_archive_summary') }}";
|
||
const sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseoutEndpoint = "{{ url_for('market_intel_review.market_intel_manual_sample_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_final_closeout') }}";
|
||
const schedulerMeta = schedulerRoot ? schedulerRoot.querySelector('[data-market-intel-scheduler-meta]') : null;
|
||
const schedulerBody = schedulerRoot ? schedulerRoot.querySelector('[data-market-intel-scheduler-body]') : null;
|
||
const schedulerRefresh = schedulerRoot ? schedulerRoot.querySelector('[data-market-intel-scheduler-refresh]') : null;
|
||
const schedulerEndpoint = "{{ url_for('market_intel.market_intel_scheduler_plan') }}";
|
||
const matchReviewMeta = matchReviewRoot ? matchReviewRoot.querySelector('[data-market-intel-match-review-meta]') : null;
|
||
const matchReviewBody = matchReviewRoot ? matchReviewRoot.querySelector('[data-market-intel-match-review-body]') : null;
|
||
const matchReviewRefresh = matchReviewRoot ? matchReviewRoot.querySelector('[data-market-intel-match-review-refresh]') : null;
|
||
const matchReviewEndpoint = "{{ url_for('market_intel.market_intel_match_review_plan') }}";
|
||
const opportunityMeta = opportunityRoot ? opportunityRoot.querySelector('[data-market-intel-opportunity-meta]') : null;
|
||
const opportunityBody = opportunityRoot ? opportunityRoot.querySelector('[data-market-intel-opportunity-body]') : null;
|
||
const opportunityRefresh = opportunityRoot ? opportunityRoot.querySelector('[data-market-intel-opportunity-refresh]') : null;
|
||
const opportunityEndpoint = "{{ url_for('market_intel.market_intel_opportunity_plan') }}";
|
||
const opportunityScoringMeta = opportunityScoringRoot ? opportunityScoringRoot.querySelector('[data-market-intel-opportunity-scoring-meta]') : null;
|
||
const opportunityScoringBody = opportunityScoringRoot ? opportunityScoringRoot.querySelector('[data-market-intel-opportunity-scoring-body]') : null;
|
||
const opportunityScoringRefresh = opportunityScoringRoot ? opportunityScoringRoot.querySelector('[data-market-intel-opportunity-scoring-refresh]') : null;
|
||
const opportunityScoringEndpoint = "{{ url_for('market_intel.market_intel_opportunity_scoring_plan') }}";
|
||
const opportunityEvidenceMeta = opportunityEvidenceRoot ? opportunityEvidenceRoot.querySelector('[data-market-intel-opportunity-evidence-meta]') : null;
|
||
const opportunityEvidenceBody = opportunityEvidenceRoot ? opportunityEvidenceRoot.querySelector('[data-market-intel-opportunity-evidence-body]') : null;
|
||
const opportunityEvidenceRefresh = opportunityEvidenceRoot ? opportunityEvidenceRoot.querySelector('[data-market-intel-opportunity-evidence-refresh]') : null;
|
||
const opportunityEvidenceEndpoint = "{{ url_for('market_intel.market_intel_opportunity_evidence_plan') }}";
|
||
const opportunityAlertMeta = opportunityAlertRoot ? opportunityAlertRoot.querySelector('[data-market-intel-opportunity-alert-meta]') : null;
|
||
const opportunityAlertBody = opportunityAlertRoot ? opportunityAlertRoot.querySelector('[data-market-intel-opportunity-alert-body]') : null;
|
||
const opportunityAlertRefresh = opportunityAlertRoot ? opportunityAlertRoot.querySelector('[data-market-intel-opportunity-alert-refresh]') : null;
|
||
const opportunityAlertEndpoint = "{{ url_for('market_intel.market_intel_opportunity_alert_plan') }}";
|
||
const migrationMeta = migrationRoot ? migrationRoot.querySelector('[data-market-intel-migration-meta]') : null;
|
||
const migrationBody = migrationRoot ? migrationRoot.querySelector('[data-market-intel-migration-body]') : null;
|
||
const migrationRefresh = migrationRoot ? migrationRoot.querySelector('[data-market-intel-migration-refresh]') : null;
|
||
const migrationEndpoint = "{{ url_for('market_intel.market_intel_migration_blueprint') }}";
|
||
const migrationDrillMeta = migrationDrillRoot ? migrationDrillRoot.querySelector('[data-market-intel-migration-drill-meta]') : null;
|
||
const migrationDrillBody = migrationDrillRoot ? migrationDrillRoot.querySelector('[data-market-intel-migration-drill-body]') : null;
|
||
const migrationDrillRefresh = migrationDrillRoot ? migrationDrillRoot.querySelector('[data-market-intel-migration-drill-refresh]') : null;
|
||
const migrationDrillEndpoint = "{{ url_for('market_intel.market_intel_migration_apply_drill') }}?execute=false";
|
||
const catalogReviewMeta = catalogReviewRoot ? catalogReviewRoot.querySelector('[data-market-intel-catalog-review-meta]') : null;
|
||
const catalogReviewBody = catalogReviewRoot ? catalogReviewRoot.querySelector('[data-market-intel-catalog-review-body]') : null;
|
||
const catalogReviewRefresh = catalogReviewRoot ? catalogReviewRoot.querySelector('[data-market-intel-catalog-review-refresh]') : null;
|
||
const catalogReviewEndpoint = "{{ url_for('market_intel.market_intel_migration_catalog_review') }}?execute=false";
|
||
const liveSmokeMeta = liveSmokeRoot ? liveSmokeRoot.querySelector('[data-market-intel-live-smoke-meta]') : null;
|
||
const liveSmokeBody = liveSmokeRoot ? liveSmokeRoot.querySelector('[data-market-intel-live-smoke-body]') : null;
|
||
const liveSmokeRefresh = liveSmokeRoot ? liveSmokeRoot.querySelector('[data-market-intel-live-smoke-refresh]') : null;
|
||
const liveSmokeEndpoint = "{{ url_for('market_intel.market_intel_migration_live_smoke') }}?execute=false";
|
||
const liveInventoryMeta = liveInventoryRoot ? liveInventoryRoot.querySelector('[data-market-intel-live-inventory-meta]') : null;
|
||
const liveInventoryBody = liveInventoryRoot ? liveInventoryRoot.querySelector('[data-market-intel-live-inventory-body]') : null;
|
||
const liveInventoryRefresh = liveInventoryRoot ? liveInventoryRoot.querySelector('[data-market-intel-live-inventory-refresh]') : null;
|
||
const liveInventoryEndpoint = "{{ url_for('market_intel.market_intel_live_db_inventory') }}?execute=false";
|
||
const approvalMeta = approvalRoot ? approvalRoot.querySelector('[data-market-intel-approval-meta]') : null;
|
||
const approvalBody = approvalRoot ? approvalRoot.querySelector('[data-market-intel-approval-body]') : null;
|
||
const approvalRefresh = approvalRoot ? approvalRoot.querySelector('[data-market-intel-approval-refresh]') : null;
|
||
const approvalEndpoint = "{{ url_for('market_intel.market_intel_write_approval_runbook') }}";
|
||
const deployMeta = deployRoot ? deployRoot.querySelector('[data-market-intel-deploy-meta]') : null;
|
||
const deployBody = deployRoot ? deployRoot.querySelector('[data-market-intel-deploy-body]') : null;
|
||
const deployRefresh = deployRoot ? deployRoot.querySelector('[data-market-intel-deploy-refresh]') : null;
|
||
const deployEndpoint = "{{ url_for('market_intel.market_intel_deployment_readiness') }}";
|
||
|
||
const escapeHtml = value => String(value == null ? '' : value)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
|
||
const renderMeta = data => {
|
||
meta.innerHTML = [
|
||
`candidates=${data.candidate_count || 0}`,
|
||
`fetch=${data.fetch_requested ? 'true' : 'false'}`,
|
||
`manual=${data.manual_fetch_allowed ? 'on' : 'off'}`,
|
||
`db_write=${data.database_write_allowed ? 'on' : 'off'}`,
|
||
`scheduler=${data.scheduler_attached ? 'on' : 'off'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderBody = data => {
|
||
if (!data.candidates || data.candidates.length === 0) {
|
||
const status = (data.run_statuses || []).map(item => `${item.platform_code}:${item.status}`).join(' / ');
|
||
body.innerHTML = `<div class="market-intel-empty">目前沒有候選連結。${status ? `狀態:${escapeHtml(status)}` : ''}</div>`;
|
||
return;
|
||
}
|
||
|
||
body.innerHTML = `<div class="market-intel-candidate-list">${
|
||
data.candidates.map(item => `
|
||
<article class="market-intel-candidate">
|
||
<a href="${escapeHtml(item.href)}" target="_blank" rel="noopener noreferrer">${escapeHtml(item.text || item.href)}</a>
|
||
<small>${escapeHtml(item.platform_code)} / ${escapeHtml(item.confidence_band)} / score=${escapeHtml(item.score)}</small>
|
||
</article>
|
||
`).join('')
|
||
}</div>`;
|
||
};
|
||
|
||
const loadPreview = async () => {
|
||
if (!meta || !body) return;
|
||
body.innerHTML = '<div class="market-intel-empty">讀取候選預覽中...</div>';
|
||
try {
|
||
const response = await fetch(endpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMeta(data);
|
||
renderBody(data);
|
||
} catch (error) {
|
||
meta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
body.innerHTML = `<div class="market-intel-empty">候選預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderWriterMeta = data => {
|
||
writerMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`operations=${data.operation_count || 0}`,
|
||
`schema=${data.schema_smoke && data.schema_smoke.passed ? 'pass' : 'fail'}`,
|
||
`writes=${data.writes_executed ? 'executed' : 'blocked'}`,
|
||
`db_write=${data.database_write_allowed ? 'on' : 'off'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderWriterBody = data => {
|
||
const reasons = (data.blocked_reasons || []).join(' / ');
|
||
const operations = (data.operations || []).slice(0, 6);
|
||
if (operations.length === 0) {
|
||
writerBody.innerHTML = `<div class="market-intel-empty">沒有 upsert 預覽。${reasons ? `阻擋:${escapeHtml(reasons)}` : ''}</div>`;
|
||
return;
|
||
}
|
||
|
||
writerBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">正式寫入仍被阻擋。${reasons ? `阻擋:${escapeHtml(reasons)}` : ''}</div>
|
||
<div class="market-intel-operation-list">${
|
||
operations.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.lookup && item.lookup.code ? item.lookup.code : item.table)}</strong>
|
||
<small>${escapeHtml(item.operation)} / ${escapeHtml(item.table)} / ${escapeHtml(item.write_status)}</small>
|
||
</article>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadWriter = async () => {
|
||
if (!writerMeta || !writerBody) return;
|
||
writerBody.innerHTML = '<div class="market-intel-empty">讀取寫入預覽中...</div>';
|
||
try {
|
||
const response = await fetch(writerEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderWriterMeta(data);
|
||
renderWriterBody(data);
|
||
} catch (error) {
|
||
writerMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
writerBody.innerHTML = `<div class="market-intel-empty">寫入預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCliMeta = data => {
|
||
const preview = data.transaction_preview || {};
|
||
cliMeta.innerHTML = [
|
||
`mode=${preview.mode || data.mode || 'unknown'}`,
|
||
`exit=${data.exit_code == null ? 'n/a' : data.exit_code}`,
|
||
`statements=${preview.statement_count || 0}`,
|
||
`session=${data.database_session_created ? 'yes' : 'no'}`,
|
||
`commit=${data.database_commit_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderCliBody = data => {
|
||
const preview = data.transaction_preview || {};
|
||
const statements = (preview.statements || []).slice(0, 6);
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
if (!statements.length) {
|
||
cliBody.innerHTML = `<div class="market-intel-empty">沒有交易預覽。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>`;
|
||
return;
|
||
}
|
||
|
||
cliBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">只產生 transaction preview;沒有 DB session、沒有 transaction、沒有 commit。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">${
|
||
statements.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.idempotency_key || item.table)}</strong>
|
||
<small>${escapeHtml(item.operation)} / ${escapeHtml(item.table)} / ${escapeHtml(item.diff_status)} / hash=${escapeHtml(item.parameter_payload_hash)}</small>
|
||
</article>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCli = async () => {
|
||
if (!cliMeta || !cliBody) return;
|
||
cliBody.innerHTML = '<div class="market-intel-empty">讀取交易預覽中...</div>';
|
||
try {
|
||
const response = await fetch(cliEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderCliMeta(data);
|
||
renderCliBody(data);
|
||
} catch (error) {
|
||
cliMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
cliBody.innerHTML = `<div class="market-intel-empty">交易預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderDbProbeMeta = data => {
|
||
dbProbeMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`execute=${data.execute_requested ? 'true' : 'false'}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`tables=${(data.expected_tables || []).length}`,
|
||
`missing=${(data.missing_tables || []).length}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderDbProbeBody = data => {
|
||
const statuses = (data.table_statuses || []).slice(0, 8);
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
dbProbeBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">預設只顯示 planned,不自動查正式 DB;人工 smoke 時才可明確開啟只讀查詢參數。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list">${
|
||
statuses.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.table)}</strong>
|
||
<small>${data.read_only_query_executed ? 'read_only_catalog_query' : 'planned_no_db_connection'}</small>
|
||
</div>
|
||
<span>${item.exists ? 'EXISTS' : 'PENDING'}</span>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadDbProbe = async () => {
|
||
if (!dbProbeMeta || !dbProbeBody) return;
|
||
dbProbeBody.innerHTML = '<div class="market-intel-empty">讀取 DB 探針中...</div>';
|
||
try {
|
||
const response = await fetch(dbProbeEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderDbProbeMeta(data);
|
||
renderDbProbeBody(data);
|
||
} catch (error) {
|
||
dbProbeMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
dbProbeBody.innerHTML = `<div class="market-intel-empty">DB 探針讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderSeedDiffMeta = data => {
|
||
seedDiffMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`execute=${data.execute_requested ? 'true' : 'false'}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`expected=${data.expected_seed_count || 0}`,
|
||
`missing=${(data.missing_codes || []).length}`,
|
||
`changed=${(data.changed_codes || []).length}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderSeedDiffBody = data => {
|
||
const diffs = (data.seed_diffs || []).slice(0, 8);
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
seedDiffBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">預設只顯示 seed 差異 planned,不自動查正式 DB;人工 smoke 時才可明確開啟只讀查詢參數。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list">${
|
||
diffs.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.code)}</strong>
|
||
<small>${escapeHtml(item.diff_status || 'unknown')}</small>
|
||
</div>
|
||
<span>${item.exists ? 'EXISTS' : 'PENDING'}</span>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadSeedDiff = async () => {
|
||
if (!seedDiffMeta || !seedDiffBody) return;
|
||
seedDiffBody.innerHTML = '<div class="market-intel-empty">讀取 Seed 差異探針中...</div>';
|
||
try {
|
||
const response = await fetch(seedDiffEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderSeedDiffMeta(data);
|
||
renderSeedDiffBody(data);
|
||
} catch (error) {
|
||
seedDiffMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
seedDiffBody.innerHTML = `<div class="market-intel-empty">Seed 差異探針讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderLegacyBridgeMeta = data => {
|
||
legacyBridgeMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`execute=${data.execute_requested ? 'true' : 'false'}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`sources=${data.existing_source_count || 0}/${data.source_count || 0}`,
|
||
`rows=${data.total_existing_rows || 0}`,
|
||
`writes=${data.writes_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderLegacyBridgeBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const sources = data.source_summaries || [];
|
||
const operations = data.bridge_operations || [];
|
||
const controls = data.duplicate_controls || [];
|
||
const renderSource = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.table)}</strong>
|
||
<small>${escapeHtml(item.description || '')}</small>
|
||
<small>target=${escapeHtml((item.planned_targets || []).join(' / '))}</small>
|
||
</div>
|
||
<span>${item.exists ? escapeHtml(item.row_count || 0) : 'PENDING'}</span>
|
||
</div>
|
||
`;
|
||
const renderOperation = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.source_table)} → ${escapeHtml(item.target_table)}</strong>
|
||
<small>${escapeHtml(item.operation)} / ${escapeHtml(item.write_status)}</small>
|
||
<small>dedupe=${escapeHtml(item.dedupe_key)}</small>
|
||
</article>
|
||
`;
|
||
const renderControl = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.rule)}</small>
|
||
</div>
|
||
<span>RULE</span>
|
||
</div>
|
||
`;
|
||
|
||
legacyBridgeBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">這裡只盤點既有資料來源與導入規則,不搬資料、不寫 market_*。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-legacy-sources>
|
||
<p class="market-intel-deploy-section-title">SOURCE TABLES</p>
|
||
<div class="market-intel-check-list">${
|
||
sources.length
|
||
? sources.map(renderSource).join('')
|
||
: '<div class="market-intel-empty">尚未提供來源摘要。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-legacy-operations>
|
||
<p class="market-intel-deploy-section-title">BRIDGE OPERATIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
operations.length
|
||
? operations.map(renderOperation).join('')
|
||
: '<div class="market-intel-empty">尚未提供橋接操作。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-legacy-controls>
|
||
<p class="market-intel-deploy-section-title">DUPLICATE CONTROLS</p>
|
||
<div class="market-intel-check-list">${
|
||
controls.length
|
||
? controls.map(renderControl).join('')
|
||
: '<div class="market-intel-empty">尚未提供去重規則。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadLegacyBridge = async () => {
|
||
if (!legacyBridgeMeta || !legacyBridgeBody) return;
|
||
legacyBridgeBody.innerHTML = '<div class="market-intel-empty">讀取既有資料橋接預覽中...</div>';
|
||
try {
|
||
const response = await fetch(legacyBridgeEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderLegacyBridgeMeta(data);
|
||
renderLegacyBridgeBody(data);
|
||
} catch (error) {
|
||
legacyBridgeMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
legacyBridgeBody.innerHTML = `<div class="market-intel-empty">既有資料橋接預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpReadinessMeta = data => {
|
||
mcpReadinessMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`router=${data.router_enabled ? 'on' : 'off'}`,
|
||
`external=${data.external_mcp_complete ? 'complete' : 'pending'}`,
|
||
`internal=${data.internal_mcp_complete ? 'complete' : 'pending'}`,
|
||
`market_tools=${data.market_intel_tool_count || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpReadinessBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const servers = data.server_statuses || [];
|
||
const checks = Object.entries(data.readiness_checks || {});
|
||
const expectedTools = data.expected_market_intel_tools || [];
|
||
const registeredCallers = data.registered_callers || [];
|
||
const toolContract = data.mcp_tool_contract || {};
|
||
const contractTools = toolContract.tools || [];
|
||
const telemetry = data.telemetry || {};
|
||
mcpReadinessBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">目前只做 MCP readiness planned preview;不自動呼叫外部平台、不建立 DB session、不寫入 telemetry。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-servers>
|
||
<p class="market-intel-deploy-section-title">EXTERNAL MCP SERVERS</p>
|
||
<div class="market-intel-check-list">${
|
||
servers.length
|
||
? servers.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.server)}</strong>
|
||
<small>${escapeHtml(item.base_url || 'not configured')}</small>
|
||
<small>${escapeHtml(item.status || '')}</small>
|
||
</div>
|
||
<span>${item.healthy ? 'HEALTHY' : item.configured ? 'PENDING' : 'MISSING'}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 MCP server 狀態。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-checks>
|
||
<p class="market-intel-deploy-section-title">READINESS CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 readiness checks。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-tools>
|
||
<p class="market-intel-deploy-section-title">INTERNAL TOOL CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>registered_callers</strong>
|
||
<small>${escapeHtml(registeredCallers.join(' / ') || 'none')}</small>
|
||
</div>
|
||
<span>${registeredCallers.length}</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>market_intel_contract</strong>
|
||
<small>${escapeHtml((toolContract.blocked_reasons || []).join(' / ') || 'ready')}</small>
|
||
</div>
|
||
<span>${toolContract.contract_ready ? 'READY' : 'PENDING'}</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>expected_tool_names</strong>
|
||
<small>${escapeHtml(expectedTools.join(' / ') || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(data.market_intel_tool_count || 0)}</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>mcp_calls telemetry</strong>
|
||
<small>${escapeHtml(telemetry.mode || 'unknown')} / table=${telemetry.table_exists ? 'exists' : 'pending'}</small>
|
||
</div>
|
||
<span>${telemetry.read_only_query_executed ? 'QUERY' : 'PLANNED'}</span>
|
||
</div>
|
||
${contractTools.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.name)}</strong>
|
||
<small>${escapeHtml(item.server)} / ${escapeHtml((item.router_tools || []).join(' + '))}</small>
|
||
</div>
|
||
<span>${item.router_tools_whitelisted ? 'ALLOW' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMcpReadiness = async () => {
|
||
if (!mcpReadinessMeta || !mcpReadinessBody) return;
|
||
mcpReadinessBody.innerHTML = '<div class="market-intel-empty">讀取 MCP 整合就緒度中...</div>';
|
||
try {
|
||
const response = await fetch(mcpReadinessEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpReadinessMeta(data);
|
||
renderMcpReadinessBody(data);
|
||
} catch (error) {
|
||
mcpReadinessMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpReadinessBody.innerHTML = `<div class="market-intel-empty">MCP 就緒度讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpPreflightMeta = data => {
|
||
const missingRequiredEnv = (data.env_statuses || []).filter(item => item.required && !item.present).length;
|
||
mcpPreflightMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_to_start_stack ? 'yes' : 'no'}`,
|
||
`compose=${data.compose_file_present ? 'present' : 'missing'}`,
|
||
`missing_env=${missingRequiredEnv}`,
|
||
`docker=${data.docker_command_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpPreflightBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const envs = data.env_statuses || [];
|
||
const services = data.service_statuses || [];
|
||
const ports = data.port_statuses || [];
|
||
const fallback = data.fallback_plan || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
|
||
mcpPreflightBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">這裡只做外部 MCP 部署預檢,不執行 docker、SSH、DB role 建立或 crawler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-preflight-env>
|
||
<p class="market-intel-deploy-section-title">ENV GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
envs.map(item => renderCheck(
|
||
item.name,
|
||
item.required ? 'required' : 'optional',
|
||
item.present ? 'SET' : 'MISSING'
|
||
)).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-preflight-services>
|
||
<p class="market-intel-deploy-section-title">COMPOSE SERVICES</p>
|
||
<div class="market-intel-check-list">${
|
||
services.map(item => renderCheck(
|
||
item.service,
|
||
data.compose_path || '',
|
||
item.declared ? 'DECLARED' : 'MISSING'
|
||
)).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-preflight-ports>
|
||
<p class="market-intel-deploy-section-title">LOCALHOST PORTS</p>
|
||
<div class="market-intel-check-list">${
|
||
ports.map(item => renderCheck(
|
||
`localhost:${item.port}`,
|
||
item.localhost_only ? '127.0.0.1 only' : 'not localhost only',
|
||
item.localhost_only ? 'SAFE' : 'BLOCK'
|
||
)).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-preflight-fallback>
|
||
<p class="market-intel-deploy-section-title">FALLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
fallback.map(item => renderCheck(
|
||
item.key,
|
||
item.label,
|
||
'READY'
|
||
)).join('')
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMcpPreflight = async () => {
|
||
if (!mcpPreflightMeta || !mcpPreflightBody) return;
|
||
mcpPreflightBody.innerHTML = '<div class="market-intel-empty">讀取 MCP 部署預檢中...</div>';
|
||
try {
|
||
const response = await fetch(mcpPreflightEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpPreflightMeta(data);
|
||
renderMcpPreflightBody(data);
|
||
} catch (error) {
|
||
mcpPreflightMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpPreflightBody.innerHTML = `<div class="market-intel-empty">MCP 部署預檢讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpActivationMeta = data => {
|
||
mcpActivationMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_operator_activation ? 'yes' : 'no'}`,
|
||
`stages=${(data.stages || []).length}`,
|
||
`blocked=${(data.blocked_reasons || []).length}`,
|
||
`docker=${data.docker_command_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpActivationBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const stages = data.stages || [];
|
||
const fallback = data.fallback_plan || [];
|
||
const safety = Object.entries(data.safety_contract || {});
|
||
const renderStage = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>gate=${escapeHtml(item.gate)} / status=${escapeHtml(item.status)}</small>
|
||
${item.command_preview ? `<small>${escapeHtml(item.command_preview)}</small>` : ''}
|
||
${item.notes ? `<small>${escapeHtml(item.notes)}</small>` : ''}
|
||
</article>
|
||
`;
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
|
||
mcpActivationBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">這是 MCP 啟用順序預覽;不寫 env、不執行 docker/SSH、不建立 DB role、不開 router。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-activation-stages>
|
||
<p class="market-intel-deploy-section-title">ACTIVATION STAGES</p>
|
||
<div class="market-intel-operation-list">${
|
||
stages.length
|
||
? stages.map(renderStage).join('')
|
||
: '<div class="market-intel-empty">尚未提供啟用步驟。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-activation-safety>
|
||
<p class="market-intel-deploy-section-title">SAFETY CONTRACT</p>
|
||
<div class="market-intel-check-list">${
|
||
safety.length
|
||
? safety.map(([key, value]) => renderCheck(key, '', value ? 'YES' : 'NO')).join('')
|
||
: '<div class="market-intel-empty">尚未提供安全合約。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-activation-fallback>
|
||
<p class="market-intel-deploy-section-title">FALLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
fallback.length
|
||
? fallback.map(item => renderCheck(item.key, item.label, 'READY')).join('')
|
||
: '<div class="market-intel-empty">尚未提供備援方案。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMcpActivation = async () => {
|
||
if (!mcpActivationMeta || !mcpActivationBody) return;
|
||
mcpActivationBody.innerHTML = '<div class="market-intel-empty">讀取 MCP 啟用 Runbook 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpActivationEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpActivationMeta(data);
|
||
renderMcpActivationBody(data);
|
||
} catch (error) {
|
||
mcpActivationMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpActivationBody.innerHTML = `<div class="market-intel-empty">MCP 啟用 Runbook 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchGateMeta = data => {
|
||
mcpFetchGateMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`fetch=${data.fetch_requested ? 'true' : 'false'}`,
|
||
`gate=${data.manual_fetch_gate_open ? 'open' : 'blocked'}`,
|
||
`network=${data.network_request_allowed ? 'allow' : 'block'}`,
|
||
`blocked=${(data.blocked_reasons || []).length}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchGateBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const sequence = data.required_sequence || [];
|
||
const readiness = data.mcp_readiness_summary || {};
|
||
mcpFetchGateBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">人工 fetch 目前先由 MCP gate 接管;頁面預設只做 planned preview,不抓外站、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-gate-checks>
|
||
<p class="market-intel-deploy-section-title">FETCH GATE CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 fetch gate checks。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-gate-sequence>
|
||
<p class="market-intel-deploy-section-title">REQUIRED SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供啟用順序。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-gate-readiness>
|
||
<p class="market-intel-deploy-section-title">READINESS SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>router</strong>
|
||
<small>${escapeHtml(readiness.mode || 'unknown')}</small>
|
||
</div>
|
||
<span>${readiness.router_enabled ? 'ON' : 'OFF'}</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>external_mcp</strong>
|
||
<small>${escapeHtml((readiness.blocked_reasons || []).join(' / ') || 'ready')}</small>
|
||
</div>
|
||
<span>${readiness.external_mcp_complete ? 'READY' : 'PENDING'}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMcpFetchGate = async () => {
|
||
if (!mcpFetchGateMeta || !mcpFetchGateBody) return;
|
||
mcpFetchGateBody.innerHTML = '<div class="market-intel-empty">讀取人工 Fetch 安全閘門中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchGateEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchGateMeta(data);
|
||
renderMcpFetchGateBody(data);
|
||
} catch (error) {
|
||
mcpFetchGateMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchGateBody.innerHTML = `<div class="market-intel-empty">人工 Fetch 安全閘門讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpCompletionMeta = data => {
|
||
mcpCompletionMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`progress=${data.overall_mcp_completion_percent || 0}%`,
|
||
`external=${data.external_mcp_runtime_complete ? 'runtime' : data.external_mcp_design_complete ? 'design' : 'pending'}`,
|
||
`internal=${data.internal_mcp_contract_complete ? 'contract' : 'pending'}`,
|
||
`fetch=${data.ready_for_manual_fetch ? 'open' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpCompletionBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const external = data.external_mcp_summary || {};
|
||
const internal = data.internal_mcp_summary || {};
|
||
const activation = data.activation_summary || {};
|
||
const fetchGate = data.fetch_gate_summary || {};
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
|
||
mcpCompletionBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">這份稽核只彙整 MCP 外部 stack、內部 tool contract、啟用 runbook 與 fetch gate;不啟動 MCP、不打 health、不連 DB、不抓外站。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-completion-gates>
|
||
<p class="market-intel-deploy-section-title">COMPLETION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(
|
||
item.key,
|
||
item.label,
|
||
item.passed ? 'PASS' : 'BLOCK'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未提供完整度 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-completion-summary>
|
||
<p class="market-intel-deploy-section-title">MCP SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'external_mcp',
|
||
`${(external.expected_servers || []).join(' / ')} / router=${external.router_enabled ? 'on' : 'off'} / health=${external.health_checked ? 'checked' : 'planned'}`,
|
||
data.external_mcp_runtime_complete ? 'RUNTIME READY' : data.external_mcp_design_complete ? 'DESIGN READY' : 'PENDING'
|
||
)}
|
||
${renderCheck(
|
||
'internal_mcp',
|
||
`${internal.caller || 'market_intel'} / tools=${internal.tool_count || 0} / telemetry=${internal.telemetry_mode || 'unknown'}`,
|
||
data.internal_mcp_contract_complete ? 'CONTRACT READY' : 'PENDING'
|
||
)}
|
||
${renderCheck(
|
||
'activation',
|
||
`${activation.stage_count || 0} stages / fallback=${activation.fallback_count || 0}`,
|
||
activation.ready_for_operator_activation ? 'READY' : 'BLOCKED'
|
||
)}
|
||
${renderCheck(
|
||
'fetch_gate',
|
||
`${fetchGate.mode || 'unknown'} / network=${fetchGate.network_request_allowed ? 'allow' : 'block'}`,
|
||
fetchGate.manual_fetch_gate_open ? 'OPEN' : 'CLOSED'
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-completion-next>
|
||
<p class="market-intel-deploy-section-title">NEXT OPERATOR STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.length
|
||
? steps.map((item, index) => renderCheck(
|
||
`step_${index + 1}`,
|
||
item,
|
||
'MANUAL'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未提供後續步驟。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMcpCompletion = async () => {
|
||
if (!mcpCompletionMeta || !mcpCompletionBody) return;
|
||
mcpCompletionBody.innerHTML = '<div class="market-intel-empty">讀取 MCP 完整度稽核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpCompletionEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpCompletionMeta(data);
|
||
renderMcpCompletionBody(data);
|
||
} catch (error) {
|
||
mcpCompletionMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpCompletionBody.innerHTML = `<div class="market-intel-empty">MCP 完整度稽核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpActivationEvidenceMeta = data => {
|
||
mcpActivationEvidenceMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.activation_evidence_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`health=${(data.health_statuses || []).filter(item => item.passed).length}/4`,
|
||
`persisted=${data.evidence_persisted ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpActivationEvidenceBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const envs = data.env_statuses || [];
|
||
const health = data.health_statuses || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpActivationEvidenceBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此審核只檢查操作員貼上的 redacted evidence;不打 health、不開 DB、不保存 payload、不啟動 fetch。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-activation-evidence-gates>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 evidence gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-activation-evidence-env>
|
||
<p class="market-intel-deploy-section-title">ENV / HEALTH</p>
|
||
<div class="market-intel-check-list">
|
||
${envs.map(item => renderCheck(
|
||
item.name,
|
||
item.secret_literal_in_payload ? 'secret literal blocked' : 'redacted or boolean only',
|
||
item.present && !item.secret_literal_in_payload ? 'OK' : 'BLOCK'
|
||
)).join('')}
|
||
${health.map(item => renderCheck(
|
||
item.target,
|
||
'localhost health evidence',
|
||
item.passed ? '200' : 'BLOCK'
|
||
)).join('')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-activation-evidence-next>
|
||
<p class="market-intel-deploy-section-title">NEXT</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'MANUAL')).join('')
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpActivationEvidenceInput && !mcpActivationEvidenceInput.value.trim() && data.sample_evidence_template) {
|
||
mcpActivationEvidenceInput.value = JSON.stringify(data.sample_evidence_template, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpActivationEvidence = async () => {
|
||
if (!mcpActivationEvidenceMeta || !mcpActivationEvidenceBody) return;
|
||
mcpActivationEvidenceBody.innerHTML = '<div class="market-intel-empty">讀取 MCP 啟用證據審核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpActivationEvidenceEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpActivationEvidenceMeta(data);
|
||
renderMcpActivationEvidenceBody(data);
|
||
} catch (error) {
|
||
mcpActivationEvidenceMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpActivationEvidenceBody.innerHTML = `<div class="market-intel-empty">MCP 啟用證據審核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpActivationEvidence = async () => {
|
||
if (!mcpActivationEvidenceMeta || !mcpActivationEvidenceBody || !mcpActivationEvidenceInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpActivationEvidenceInput.value || '{}');
|
||
} catch (error) {
|
||
mcpActivationEvidenceMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpActivationEvidenceBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpActivationEvidenceBody.innerHTML = '<div class="market-intel-empty">審核 MCP 啟用證據中...</div>';
|
||
try {
|
||
const response = await fetch(mcpActivationEvidenceEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ evidence: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpActivationEvidenceMeta(data);
|
||
renderMcpActivationEvidenceBody(data);
|
||
} catch (error) {
|
||
mcpActivationEvidenceMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpActivationEvidenceBody.innerHTML = `<div class="market-intel-empty">MCP 啟用證據審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpRuntimeSmokeMeta = data => {
|
||
const summary = data.server_summary || {};
|
||
mcpRuntimeSmokeMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.runtime_smoke_receipt_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`healthy=${summary.healthy_count || 0}/${(summary.expected_servers || []).length || 4}`,
|
||
`persisted=${data.receipt_persisted ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpRuntimeSmokeBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const servers = data.server_statuses || [];
|
||
const telemetry = data.telemetry_summary || {};
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpRuntimeSmokeBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此審核只驗收操作員貼上的 runtime smoke receipt;API 不打 health、不開 DB、不保存 payload、不抓外站。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-runtime-smoke-gates>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 receipt gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-runtime-smoke-servers>
|
||
<p class="market-intel-deploy-section-title">SERVER HEALTH</p>
|
||
<div class="market-intel-check-list">${
|
||
servers.length
|
||
? servers.map(item => renderCheck(
|
||
item.server,
|
||
`checked=${item.health_checked ? 'yes' : 'no'} / status=${item.status || 'unknown'}`,
|
||
item.healthy ? 'HEALTHY' : 'BLOCK'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未提供 server health receipt。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-runtime-smoke-telemetry>
|
||
<p class="market-intel-deploy-section-title">TELEMETRY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'telemetry',
|
||
`${telemetry.mode || 'unknown'} / table=${telemetry.table_exists ? 'yes' : 'no'} / query=${telemetry.read_only_query_executed ? 'read-only' : 'missing'}`,
|
||
telemetry.read_only_query_executed && !telemetry.database_write_executed ? 'READ ONLY' : 'BLOCK'
|
||
)}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'MANUAL')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpRuntimeSmokeInput && !mcpRuntimeSmokeInput.value.trim() && data.sample_receipt_template) {
|
||
mcpRuntimeSmokeInput.value = JSON.stringify(data.sample_receipt_template, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpRuntimeSmoke = async () => {
|
||
if (!mcpRuntimeSmokeMeta || !mcpRuntimeSmokeBody) return;
|
||
mcpRuntimeSmokeBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Runtime Smoke 收據中...</div>';
|
||
try {
|
||
const response = await fetch(mcpRuntimeSmokeEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpRuntimeSmokeMeta(data);
|
||
renderMcpRuntimeSmokeBody(data);
|
||
} catch (error) {
|
||
mcpRuntimeSmokeMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpRuntimeSmokeBody.innerHTML = `<div class="market-intel-empty">MCP Runtime Smoke 收據讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpRuntimeSmoke = async () => {
|
||
if (!mcpRuntimeSmokeMeta || !mcpRuntimeSmokeBody || !mcpRuntimeSmokeInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpRuntimeSmokeInput.value || '{}');
|
||
} catch (error) {
|
||
mcpRuntimeSmokeMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpRuntimeSmokeBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpRuntimeSmokeBody.innerHTML = '<div class="market-intel-empty">審核 MCP Runtime Smoke 收據中...</div>';
|
||
try {
|
||
const response = await fetch(mcpRuntimeSmokeEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ receipt: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpRuntimeSmokeMeta(data);
|
||
renderMcpRuntimeSmokeBody(data);
|
||
} catch (error) {
|
||
mcpRuntimeSmokeMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpRuntimeSmokeBody.innerHTML = `<div class="market-intel-empty">MCP Runtime Smoke 收據審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpRuntimePromotionMeta = data => {
|
||
mcpRuntimePromotionMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.runtime_promotion_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`activation=${(data.activation_summary || {}).accepted ? 'ok' : 'pending'}`,
|
||
`receipt=${(data.runtime_receipt_summary || {}).accepted ? 'ok' : 'pending'}`,
|
||
`persisted=${data.promotion_persisted ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpRuntimePromotionBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const activation = data.activation_summary || {};
|
||
const receipt = data.runtime_receipt_summary || {};
|
||
const serverSummary = receipt.server_summary || {};
|
||
const telemetry = receipt.telemetry_summary || {};
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpRuntimePromotionBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此審核只合併 activation evidence 與 runtime smoke receipt;不打 health、不開 DB、不保存 payload、不打開人工 fetch。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-runtime-promotion-gates>
|
||
<p class="market-intel-deploy-section-title">PROMOTION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 promotion gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-runtime-promotion-summaries>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARIES</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'activation_evidence',
|
||
`${activation.passed_gate_count || 0}/${activation.gate_count || 0} gates`,
|
||
activation.accepted ? 'ACCEPTED' : 'PENDING'
|
||
)}
|
||
${renderCheck(
|
||
'runtime_receipt',
|
||
`${receipt.passed_gate_count || 0}/${receipt.gate_count || 0} gates / servers=${serverSummary.healthy_count || 0}/${(serverSummary.expected_servers || []).length || 4}`,
|
||
receipt.accepted ? 'ACCEPTED' : 'PENDING'
|
||
)}
|
||
${renderCheck(
|
||
'telemetry',
|
||
`${telemetry.mode || 'unknown'} / query=${telemetry.read_only_query_executed ? 'read-only' : 'missing'}`,
|
||
telemetry.read_only_query_executed && !telemetry.database_write_executed ? 'READ ONLY' : 'BLOCK'
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-runtime-promotion-next>
|
||
<p class="market-intel-deploy-section-title">NEXT</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'MANUAL')).join('')
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpRuntimePromotionInput && !mcpRuntimePromotionInput.value.trim() && data.sample_promotion_package) {
|
||
mcpRuntimePromotionInput.value = JSON.stringify(data.sample_promotion_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpRuntimePromotion = async () => {
|
||
if (!mcpRuntimePromotionMeta || !mcpRuntimePromotionBody) return;
|
||
mcpRuntimePromotionBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Runtime Promotion 審核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpRuntimePromotionEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpRuntimePromotionMeta(data);
|
||
renderMcpRuntimePromotionBody(data);
|
||
} catch (error) {
|
||
mcpRuntimePromotionMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpRuntimePromotionBody.innerHTML = `<div class="market-intel-empty">MCP Runtime Promotion 審核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpRuntimePromotion = async () => {
|
||
if (!mcpRuntimePromotionMeta || !mcpRuntimePromotionBody || !mcpRuntimePromotionInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpRuntimePromotionInput.value || '{}');
|
||
} catch (error) {
|
||
mcpRuntimePromotionMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpRuntimePromotionBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpRuntimePromotionBody.innerHTML = '<div class="market-intel-empty">審核 MCP Runtime Promotion 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpRuntimePromotionEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ promotion_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpRuntimePromotionMeta(data);
|
||
renderMcpRuntimePromotionBody(data);
|
||
} catch (error) {
|
||
mcpRuntimePromotionMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpRuntimePromotionBody.innerHTML = `<div class="market-intel-empty">MCP Runtime Promotion 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpManualFetchHandoffMeta = data => {
|
||
mcpManualFetchHandoffMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.manual_fetch_handoff_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`promotion=${data.runtime_promotion_accepted ? 'ok' : 'pending'}`,
|
||
`network=${data.network_request_allowed ? 'open' : 'closed'}`,
|
||
`persisted=${data.handoff_persisted ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpManualFetchHandoffBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const promotion = data.promotion_summary || {};
|
||
const fetchGate = data.fetch_gate_summary || {};
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpManualFetchHandoffBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 handoff 只把 runtime promotion 與操作員安全確認交接到人工 fetch gate;API 不打開抓取、不連外、不開 DB、不保存 payload。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-manual-fetch-handoff-gates>
|
||
<p class="market-intel-deploy-section-title">HANDOFF GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 handoff gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-manual-fetch-handoff-summary>
|
||
<p class="market-intel-deploy-section-title">PROMOTION / FETCH GATE</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'runtime_promotion',
|
||
`${promotion.passed_gate_count || 0}/${promotion.gate_count || 0} gates`,
|
||
promotion.accepted ? 'ACCEPTED' : 'PENDING'
|
||
)}
|
||
${renderCheck(
|
||
'fetch_gate',
|
||
`${fetchGate.mode || 'unknown'} / blockers=${(fetchGate.blocked_reasons || []).length}`,
|
||
fetchGate.network_request_allowed ? 'OPEN' : 'CLOSED'
|
||
)}
|
||
${renderCheck(
|
||
'api_boundary',
|
||
'no external network / no DB / no scheduler from this API',
|
||
data.manual_fetch_gate_opened_by_api ? 'BLOCK' : 'CLOSED'
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-manual-fetch-handoff-next>
|
||
<p class="market-intel-deploy-section-title">NEXT</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'MANUAL')).join('')
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpManualFetchHandoffInput && !mcpManualFetchHandoffInput.value.trim() && data.sample_handoff_package) {
|
||
mcpManualFetchHandoffInput.value = JSON.stringify(data.sample_handoff_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpManualFetchHandoff = async () => {
|
||
if (!mcpManualFetchHandoffMeta || !mcpManualFetchHandoffBody) return;
|
||
mcpManualFetchHandoffBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Manual Fetch Handoff 審核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpManualFetchHandoffEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpManualFetchHandoffMeta(data);
|
||
renderMcpManualFetchHandoffBody(data);
|
||
} catch (error) {
|
||
mcpManualFetchHandoffMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpManualFetchHandoffBody.innerHTML = `<div class="market-intel-empty">MCP Manual Fetch Handoff 審核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpManualFetchHandoff = async () => {
|
||
if (!mcpManualFetchHandoffMeta || !mcpManualFetchHandoffBody || !mcpManualFetchHandoffInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpManualFetchHandoffInput.value || '{}');
|
||
} catch (error) {
|
||
mcpManualFetchHandoffMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpManualFetchHandoffBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpManualFetchHandoffBody.innerHTML = '<div class="market-intel-empty">審核 MCP Manual Fetch Handoff 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpManualFetchHandoffEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ handoff_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpManualFetchHandoffMeta(data);
|
||
renderMcpManualFetchHandoffBody(data);
|
||
} catch (error) {
|
||
mcpManualFetchHandoffMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpManualFetchHandoffBody.innerHTML = `<div class="market-intel-empty">MCP Manual Fetch Handoff 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchTargetReviewMeta = data => {
|
||
mcpFetchTargetReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_target_review_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`targets=${data.platform_target_count || 0}`,
|
||
`sources=${data.source_target_count || 0}`,
|
||
`network=${data.network_request_allowed ? 'open' : 'closed'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchTargetReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const targets = data.target_summaries || [];
|
||
const handoff = data.handoff_summary || {};
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchTargetReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 target review 只審核公開入口白名單、節流、樣本數與回退條件;API 不執行抓取、不連外、不開 DB、不保存 payload。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-target-review-gates>
|
||
<p class="market-intel-deploy-section-title">TARGET GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 target gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-target-review-targets>
|
||
<p class="market-intel-deploy-section-title">PLATFORM TARGETS</p>
|
||
<div class="market-intel-check-list">${
|
||
targets.length
|
||
? targets.map(item => renderCheck(
|
||
item.platform_code || 'unknown',
|
||
`${(item.source_keys || []).join(', ') || 'no source'} / delay=${item.delay_seconds || 'n/a'} / limit=${item.sample_limit || 'n/a'}`,
|
||
item.ready ? 'READY' : 'BLOCK'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未提供平台目標。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-target-review-next>
|
||
<p class="market-intel-deploy-section-title">HANDOFF / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'manual_fetch_handoff',
|
||
`${handoff.passed_gate_count || 0}/${handoff.gate_count || 0} gates`,
|
||
handoff.accepted ? 'ACCEPTED' : 'PENDING'
|
||
)}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'MANUAL')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchTargetReviewInput && !mcpFetchTargetReviewInput.value.trim() && data.sample_target_review_package) {
|
||
mcpFetchTargetReviewInput.value = JSON.stringify(data.sample_target_review_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchTargetReview = async () => {
|
||
if (!mcpFetchTargetReviewMeta || !mcpFetchTargetReviewBody) return;
|
||
mcpFetchTargetReviewBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Fetch Target Review 審核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchTargetReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchTargetReviewMeta(data);
|
||
renderMcpFetchTargetReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchTargetReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchTargetReviewBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Target Review 審核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchTargetReview = async () => {
|
||
if (!mcpFetchTargetReviewMeta || !mcpFetchTargetReviewBody || !mcpFetchTargetReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchTargetReviewInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchTargetReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchTargetReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchTargetReviewBody.innerHTML = '<div class="market-intel-empty">審核 MCP Fetch Target Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchTargetReviewEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ target_review_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchTargetReviewMeta(data);
|
||
renderMcpFetchTargetReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchTargetReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchTargetReviewBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Target Review 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchRunPackageMeta = data => {
|
||
mcpFetchRunPackageMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_run_package_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`commands=${data.command_preview_count || 0}`,
|
||
`operator=${data.ready_for_manual_fetch_operator_run ? 'ready' : 'blocked'}`,
|
||
`network=${data.network_request_allowed ? 'open' : 'closed'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchRunPackageBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const commands = data.command_previews || [];
|
||
const target = data.target_review_summary || {};
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchRunPackageBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 run package 只產生人工命令 argv preview 與 receipt 契約;API 不執行 CLI、不抓外站、不寫檔、不開 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-run-package-gates>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 run package gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-package-commands>
|
||
<p class="market-intel-deploy-section-title">COMMAND PREVIEW</p>
|
||
<div class="market-intel-check-list">${
|
||
commands.length
|
||
? commands.map(item => renderCheck(
|
||
`${item.platform_code || 'unknown'}:${item.source_key || 'source'}`,
|
||
`${item.receipt_path || 'no receipt'} / limit=${item.sample_limit || 'n/a'}`,
|
||
item.command_executed ? 'BLOCK' : 'PREVIEW'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未產生命令預覽。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-package-next>
|
||
<p class="market-intel-deploy-section-title">TARGET / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'target_review',
|
||
`${target.platform_target_count || 0} platforms / ${target.source_target_count || 0} sources`,
|
||
target.accepted ? 'ACCEPTED' : 'PENDING'
|
||
)}
|
||
${renderCheck(
|
||
'api_boundary',
|
||
'no CLI / no external request / no file / no DB / no scheduler',
|
||
data.api_executes_cli ? 'BLOCK' : 'CLOSED'
|
||
)}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'MANUAL')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchRunPackageInput && !mcpFetchRunPackageInput.value.trim() && data.sample_run_package) {
|
||
mcpFetchRunPackageInput.value = JSON.stringify(data.sample_run_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchRunPackage = async () => {
|
||
if (!mcpFetchRunPackageMeta || !mcpFetchRunPackageBody) return;
|
||
mcpFetchRunPackageBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Fetch Run Package 審核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchRunPackageEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchRunPackageMeta(data);
|
||
renderMcpFetchRunPackageBody(data);
|
||
} catch (error) {
|
||
mcpFetchRunPackageMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchRunPackageBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Run Package 審核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchRunPackage = async () => {
|
||
if (!mcpFetchRunPackageMeta || !mcpFetchRunPackageBody || !mcpFetchRunPackageInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchRunPackageInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchRunPackageMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchRunPackageBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchRunPackageBody.innerHTML = '<div class="market-intel-empty">審核 MCP Fetch Run Package 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchRunPackageEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ run_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchRunPackageMeta(data);
|
||
renderMcpFetchRunPackageBody(data);
|
||
} catch (error) {
|
||
mcpFetchRunPackageMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchRunPackageBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Run Package 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchRunReadinessMeta = data => {
|
||
mcpFetchRunReadinessMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_run_readiness_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`commands=${data.command_readiness_count || 0}`,
|
||
`operator=${data.ready_for_manual_fetch_operator_run ? 'ready' : 'blocked'}`,
|
||
`api_network=${data.network_request_allowed ? 'open' : 'closed'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchRunReadinessBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const commands = data.command_readiness || [];
|
||
const operator = data.operator_readiness_summary || {};
|
||
const packageSummary = data.run_package_summary || {};
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchRunReadinessBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 readiness 只檢查人工 shell dry-run fetch 前置條件;API 不執行 CLI、不抓外站、不寫檔、不開 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-run-readiness-gates>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 readiness gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-readiness-operator>
|
||
<p class="market-intel-deploy-section-title">OPERATOR EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('run_readiness_path', operator.run_readiness_artifact_path || 'missing', operator.run_readiness_artifact_path_safe ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('dry_run_receipt_path', operator.dry_run_receipt_path || 'missing', operator.dry_run_receipt_path_safe ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('command_review', `keys=${(operator.provided_keys || []).length}`, operator.command_previews_reviewed ? 'REVIEWED' : 'PENDING')}
|
||
${renderCheck('secret_payload', 'no secret/cookie/password key submitted to API', operator.secret_or_token_submitted_to_api ? 'BLOCK' : 'CLEAR')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-readiness-commands>
|
||
<p class="market-intel-deploy-section-title">COMMAND READINESS</p>
|
||
<div class="market-intel-check-list">${
|
||
commands.length
|
||
? commands.map(item => renderCheck(
|
||
`${item.platform_code || 'unknown'}:${item.source_key || 'source'}`,
|
||
`${item.receipt_path || 'no receipt'} / argv=${(item.argv_preview || []).length}`,
|
||
item.ready_for_operator_shell ? 'SHELL' : 'PREVIEW'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未產生命令 readiness。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-readiness-next>
|
||
<p class="market-intel-deploy-section-title">PACKAGE / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'run_package',
|
||
`${packageSummary.command_count || 0} commands / receipts=${packageSummary.receipt_path_count || 0}`,
|
||
packageSummary.accepted ? 'ACCEPTED' : 'PENDING'
|
||
)}
|
||
${renderCheck(
|
||
'api_boundary',
|
||
'no CLI / no external request / no file / no DB / no scheduler',
|
||
data.api_executes_cli ? 'BLOCK' : 'CLOSED'
|
||
)}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'MANUAL')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchRunReadinessInput && !mcpFetchRunReadinessInput.value.trim() && data.sample_run_readiness_package) {
|
||
mcpFetchRunReadinessInput.value = JSON.stringify(data.sample_run_readiness_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchRunReadiness = async () => {
|
||
if (!mcpFetchRunReadinessMeta || !mcpFetchRunReadinessBody) return;
|
||
mcpFetchRunReadinessBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Fetch Run Readiness 審核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchRunReadinessEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchRunReadinessMeta(data);
|
||
renderMcpFetchRunReadinessBody(data);
|
||
} catch (error) {
|
||
mcpFetchRunReadinessMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchRunReadinessBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Run Readiness 審核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchRunReadiness = async () => {
|
||
if (!mcpFetchRunReadinessMeta || !mcpFetchRunReadinessBody || !mcpFetchRunReadinessInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchRunReadinessInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchRunReadinessMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchRunReadinessBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchRunReadinessBody.innerHTML = '<div class="market-intel-empty">審核 MCP Fetch Run Readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchRunReadinessEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ run_readiness: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchRunReadinessMeta(data);
|
||
renderMcpFetchRunReadinessBody(data);
|
||
} catch (error) {
|
||
mcpFetchRunReadinessMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchRunReadinessBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Run Readiness 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchRunReceiptMeta = data => {
|
||
mcpFetchRunReceiptMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_run_receipt_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`commands=${data.expected_command_count || 0}`,
|
||
`sources=${data.receipt_source_count || 0}`,
|
||
`api_network=${data.network_request_allowed ? 'open' : 'closed'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchRunReceiptBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.manual_fetch_receipt_summary || {};
|
||
const expected = data.expected_command_manifest || {};
|
||
const commands = expected.commands || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchRunReceiptBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 receipt 只審核操作員 shell dry-run fetch 後貼回的安全摘要;API 不執行 CLI、不抓外站、不保存 receipt、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-run-receipt-gates>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 receipt gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-receipt-receipt>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('receipt_id', receipt.receipt_id || 'missing', receipt.receipt_id ? 'RECORDED' : 'MISSING')}
|
||
${renderCheck('artifact_path', receipt.receipt_artifact_path || 'missing', receipt.receipt_artifact_path_safe ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('operator_shell', `requests=${receipt.request_count || 0} / errors=${receipt.error_count || 0}`, receipt.operator_shell_external_network_executed ? 'OBSERVED' : 'PENDING')}
|
||
${renderCheck('secret_payload', 'no secret/cookie/password/token key submitted to API', receipt.secret_or_token_submitted_to_api ? 'BLOCK' : 'CLEAR')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-receipt-sources>
|
||
<p class="market-intel-deploy-section-title">SOURCE RECEIPTS</p>
|
||
<div class="market-intel-check-list">${
|
||
(receipt.sources || []).length
|
||
? receipt.sources.map(item => renderCheck(
|
||
`${item.platform_code || 'unknown'}:${item.source_key || 'source'}`,
|
||
`${item.receipt_path || 'no receipt'} / ${item.source_url || 'no url'}`,
|
||
item.status === 'dry_run_completed' ? 'DRY-RUN' : 'BLOCK'
|
||
)).join('')
|
||
: commands.length
|
||
? commands.map(item => renderCheck(
|
||
`${item.platform_code || 'unknown'}:${item.source_key || 'source'}`,
|
||
`${item.receipt_path || 'no receipt'} / ${item.source_url || 'no url'}`,
|
||
'EXPECTED'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未提供 source receipt。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-run-receipt-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'readiness',
|
||
`${data.run_readiness_accepted ? 'accepted' : 'pending'} / expected=${expected.command_count || 0}`,
|
||
data.run_readiness_accepted ? 'ACCEPTED' : 'PENDING'
|
||
)}
|
||
${renderCheck(
|
||
'api_boundary',
|
||
'no CLI / no external request / no file / no DB / no scheduler',
|
||
data.api_executes_cli ? 'BLOCK' : 'CLOSED'
|
||
)}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchRunReceiptInput && !mcpFetchRunReceiptInput.value.trim() && data.sample_run_receipt_package) {
|
||
mcpFetchRunReceiptInput.value = JSON.stringify(data.sample_run_receipt_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchRunReceipt = async () => {
|
||
if (!mcpFetchRunReceiptMeta || !mcpFetchRunReceiptBody) return;
|
||
mcpFetchRunReceiptBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Fetch Run Receipt 審核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchRunReceiptEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchRunReceiptMeta(data);
|
||
renderMcpFetchRunReceiptBody(data);
|
||
} catch (error) {
|
||
mcpFetchRunReceiptMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchRunReceiptBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Run Receipt 審核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchRunReceipt = async () => {
|
||
if (!mcpFetchRunReceiptMeta || !mcpFetchRunReceiptBody || !mcpFetchRunReceiptInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchRunReceiptInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchRunReceiptMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchRunReceiptBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchRunReceiptBody.innerHTML = '<div class="market-intel-empty">審核 MCP Fetch Run Receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchRunReceiptEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ run_receipt: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchRunReceiptMeta(data);
|
||
renderMcpFetchRunReceiptBody(data);
|
||
} catch (error) {
|
||
mcpFetchRunReceiptMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchRunReceiptBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Run Receipt 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchResultParserReviewMeta = data => {
|
||
mcpFetchResultParserReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_result_parser_review_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`sources=${data.parser_source_count || 0}`,
|
||
`candidates=${data.candidate_count || 0}`,
|
||
`api_network=${data.network_request_allowed ? 'open' : 'closed'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchResultParserReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const parser = data.parser_result_summary || {};
|
||
const receipt = data.run_receipt_summary || {};
|
||
const sources = parser.source_summaries || [];
|
||
const campaigns = parser.campaign_candidates || [];
|
||
const products = parser.product_candidates || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchResultParserReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 parser review 只審核操作員貼回的結構化摘要;API 不讀 artifact、不執行 parser、不抓外站、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-result-parser-review-gates>
|
||
<p class="market-intel-deploy-section-title">PARSER GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 parser gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-result-parser-review-parser>
|
||
<p class="market-intel-deploy-section-title">PARSER SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('parser_run_id', parser.parser_run_id || 'missing', parser.parser_run_id ? 'RECORDED' : 'MISSING')}
|
||
${renderCheck('artifact_path', parser.parser_artifact_path || 'missing', parser.parser_artifact_path_safe ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('counts', `campaigns=${parser.campaign_candidate_count || 0} / products=${parser.product_candidate_count || 0}`, parser.summary_counts_match_candidates ? 'MATCHED' : 'PENDING')}
|
||
${renderCheck('raw_payload', 'no raw HTML/body/snapshot submitted to API', parser.raw_payload_submitted_to_api ? 'BLOCK' : 'CLEAR')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-result-parser-review-sources>
|
||
<p class="market-intel-deploy-section-title">SOURCE PARSING</p>
|
||
<div class="market-intel-check-list">${
|
||
sources.length
|
||
? sources.map(item => renderCheck(
|
||
`${item.platform_code || 'unknown'}:${item.source_key || 'source'}`,
|
||
`${item.parser_artifact_path || 'no artifact'} / campaigns=${item.observed_campaign_candidate_count || 0} / products=${item.observed_product_candidate_count || 0}`,
|
||
item.parser_status === 'parsed_preview' ? 'PARSED' : 'BLOCK'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未提供 parsed source summary。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-result-parser-review-candidates>
|
||
<p class="market-intel-deploy-section-title">CANDIDATE PREVIEW</p>
|
||
<div class="market-intel-check-list">${
|
||
campaigns.concat(products).slice(0, 8).map(item => renderCheck(
|
||
item.campaign_key || item.platform_product_id || 'candidate',
|
||
item.campaign_name || item.name || item.campaign_url || item.product_url || '',
|
||
item.required_fields_present ? 'READY' : 'BLOCK'
|
||
)).join('') || '<div class="market-intel-empty">尚未提供活動或商品候選。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-result-parser-review-next>
|
||
<p class="market-intel-deploy-section-title">RECEIPT / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'run_receipt',
|
||
`${data.run_receipt_accepted ? 'accepted' : 'pending'} / sources=${receipt.source_count || 0}`,
|
||
data.run_receipt_accepted ? 'ACCEPTED' : 'PENDING'
|
||
)}
|
||
${renderCheck(
|
||
'api_boundary',
|
||
'no parser CLI / no artifact read / no external request / no DB / no scheduler',
|
||
data.api_executes_cli ? 'BLOCK' : 'CLOSED'
|
||
)}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchResultParserReviewInput && !mcpFetchResultParserReviewInput.value.trim() && data.sample_parser_result_package) {
|
||
mcpFetchResultParserReviewInput.value = JSON.stringify(data.sample_parser_result_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchResultParserReview = async () => {
|
||
if (!mcpFetchResultParserReviewMeta || !mcpFetchResultParserReviewBody) return;
|
||
mcpFetchResultParserReviewBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Fetch Result Parser 審核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchResultParserReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchResultParserReviewMeta(data);
|
||
renderMcpFetchResultParserReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchResultParserReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchResultParserReviewBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Result Parser 審核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchResultParserReview = async () => {
|
||
if (!mcpFetchResultParserReviewMeta || !mcpFetchResultParserReviewBody || !mcpFetchResultParserReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchResultParserReviewInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchResultParserReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchResultParserReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchResultParserReviewBody.innerHTML = '<div class="market-intel-empty">審核 MCP Fetch Result Parser 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchResultParserReviewEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ parser_review: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchResultParserReviewMeta(data);
|
||
renderMcpFetchResultParserReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchResultParserReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchResultParserReviewBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Result Parser 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateHandoffReviewMeta = data => {
|
||
mcpFetchCandidateHandoffReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_handoff_review_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`sources=${data.handoff_source_count || 0}`,
|
||
`candidates=${data.candidate_count || 0}`,
|
||
`queue=${data.candidate_queue_created ? 'created' : 'preview'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateHandoffReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const parser = data.parser_review_summary || {};
|
||
const handoff = data.candidate_handoff_summary || {};
|
||
const groups = handoff.group_summaries || [];
|
||
const policy = handoff.queue_policy || {};
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateHandoffReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 handoff review 只審核 parser 候選交接包;API 不建立 queue、不讀 artifact、不抓外站、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-handoff-review-gates>
|
||
<p class="market-intel-deploy-section-title">HANDOFF GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 handoff gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-handoff-review-parser>
|
||
<p class="market-intel-deploy-section-title">PARSER LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('parser_review', `${parser.accepted ? 'accepted' : 'pending'} / sources=${parser.source_count || 0}`, parser.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('parser_candidates', `campaigns=${parser.campaign_candidate_count || 0} / products=${parser.product_candidate_count || 0}`, parser.all_candidate_required_fields_present ? 'READY' : 'PENDING')}
|
||
${renderCheck('parser_boundary', 'no parser CLI / no external request / no DB / no scheduler', parser.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-handoff-review-groups>
|
||
<p class="market-intel-deploy-section-title">HANDOFF GROUPS</p>
|
||
<div class="market-intel-check-list">${
|
||
groups.length
|
||
? groups.map(item => renderCheck(
|
||
`${item.platform_code || 'unknown'}:${item.source_key || 'source'}`,
|
||
`${item.handoff_status || 'missing'} / campaigns=${item.observed_campaign_candidate_count || 0} / products=${item.observed_product_candidate_count || 0}`,
|
||
item.handoff_status === 'queued_for_operator_review' ? 'QUEUE REVIEW' : 'BLOCK'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未提供 candidate handoff groups。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-handoff-review-queue>
|
||
<p class="market-intel-deploy-section-title">QUEUE POLICY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('target_queue', policy.target_queue || handoff.target_queue || 'missing', policy.target_queue === 'manual_candidate_review' ? 'MANUAL' : 'BLOCK')}
|
||
${renderCheck('dedupe_strategy', policy.dedupe_strategy || 'missing', policy.dedupe_strategy === 'platform_source_candidate_key' ? 'READY' : 'BLOCK')}
|
||
${renderCheck('write_mode', policy.write_mode || 'missing', policy.write_mode === 'preview_only' ? 'PREVIEW' : 'BLOCK')}
|
||
${renderCheck('candidate_count', `${handoff.candidate_count || 0}/${policy.max_candidates_per_handoff || 0}`, data.candidate_queue_created ? 'BLOCK' : 'NO WRITE')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-handoff-review-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'api_boundary',
|
||
'no queue create / no artifact read / no external request / no DB / no scheduler',
|
||
data.api_writes_database ? 'BLOCK' : 'CLOSED'
|
||
)}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateHandoffReviewInput && !mcpFetchCandidateHandoffReviewInput.value.trim() && data.sample_candidate_handoff_package) {
|
||
mcpFetchCandidateHandoffReviewInput.value = JSON.stringify(data.sample_candidate_handoff_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateHandoffReview = async () => {
|
||
if (!mcpFetchCandidateHandoffReviewMeta || !mcpFetchCandidateHandoffReviewBody) return;
|
||
mcpFetchCandidateHandoffReviewBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Fetch Candidate Handoff 審核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateHandoffReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateHandoffReviewMeta(data);
|
||
renderMcpFetchCandidateHandoffReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateHandoffReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateHandoffReviewBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Candidate Handoff 審核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateHandoffReview = async () => {
|
||
if (!mcpFetchCandidateHandoffReviewMeta || !mcpFetchCandidateHandoffReviewBody || !mcpFetchCandidateHandoffReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateHandoffReviewInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateHandoffReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateHandoffReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateHandoffReviewBody.innerHTML = '<div class="market-intel-empty">審核 MCP Fetch Candidate Handoff 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateHandoffReviewEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ candidate_handoff_review: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateHandoffReviewMeta(data);
|
||
renderMcpFetchCandidateHandoffReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateHandoffReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateHandoffReviewBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Candidate Handoff 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueReviewMeta = data => {
|
||
mcpFetchCandidateQueueReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_review_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`items=${data.review_item_count || 0}`,
|
||
`campaigns=${data.campaign_candidate_count || 0}`,
|
||
`products=${data.product_candidate_count || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const handoff = data.handoff_review_summary || {};
|
||
const review = data.candidate_queue_review_summary || {};
|
||
const items = review.review_items || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 queue review 只審核人工覆核草案;API 不建立 queue、不更新 review_state、不抓外站、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-review-gates>
|
||
<p class="market-intel-deploy-section-title">QUEUE REVIEW GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 queue review gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-review-handoff>
|
||
<p class="market-intel-deploy-section-title">HANDOFF LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('handoff_review', `${handoff.accepted ? 'accepted' : 'pending'} / candidates=${handoff.candidate_count || 0}`, handoff.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('handoff_boundary', 'no queue create / no DB / no scheduler', handoff.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-review-items>
|
||
<p class="market-intel-deploy-section-title">REVIEW ITEMS</p>
|
||
<div class="market-intel-check-list">${
|
||
items.slice(0, 10).map(item => renderCheck(
|
||
item.candidate_key || 'candidate',
|
||
`${item.candidate_type || 'candidate'} / ${item.review_state || 'missing'} / ${item.queue_write_status || 'unknown'}`,
|
||
item.review_state === 'needs_review' ? 'NEEDS REVIEW' : 'BLOCK'
|
||
)).join('') || '<div class="market-intel-empty">尚未提供 candidate queue review items。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-review-policy>
|
||
<p class="market-intel-deploy-section-title">REVIEW POLICY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('target_queue', review.target_queue || 'missing', review.target_queue === 'manual_candidate_review' ? 'MANUAL' : 'BLOCK')}
|
||
${renderCheck('review_mode', review.review_mode || 'missing', review.review_mode === 'operator_queue_review_preview' ? 'PREVIEW' : 'BLOCK')}
|
||
${renderCheck('review_state', 'all items must stay needs_review', review.all_review_states_safe ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('queue_write', 'all items must stay not_persisted', review.all_queue_write_status_preview ? 'NO WRITE' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-review-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'api_boundary',
|
||
'no queue create / no review_state update / no DB / no scheduler',
|
||
data.api_writes_database ? 'BLOCK' : 'CLOSED'
|
||
)}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueReviewInput && !mcpFetchCandidateQueueReviewInput.value.trim() && data.sample_candidate_queue_review_package) {
|
||
mcpFetchCandidateQueueReviewInput.value = JSON.stringify(data.sample_candidate_queue_review_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueReview = async () => {
|
||
if (!mcpFetchCandidateQueueReviewMeta || !mcpFetchCandidateQueueReviewBody) return;
|
||
mcpFetchCandidateQueueReviewBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Fetch Candidate Queue 審核中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueReviewMeta(data);
|
||
renderMcpFetchCandidateQueueReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueReviewBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Candidate Queue 審核讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueReview = async () => {
|
||
if (!mcpFetchCandidateQueueReviewMeta || !mcpFetchCandidateQueueReviewBody || !mcpFetchCandidateQueueReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueReviewInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueReviewBody.innerHTML = '<div class="market-intel-empty">審核 MCP Fetch Candidate Queue 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueReviewEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ candidate_queue_review: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueReviewMeta(data);
|
||
renderMcpFetchCandidateQueueReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueReviewBody.innerHTML = `<div class="market-intel-empty">MCP Fetch Candidate Queue 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterPreflightMeta = data => {
|
||
mcpFetchCandidateQueueWriterPreflightMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_preflight_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || 0}`,
|
||
`campaigns=${data.campaign_candidate_count || 0}`,
|
||
`products=${data.product_candidate_count || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterPreflightBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const queue = data.queue_review_summary || {};
|
||
const preflight = data.writer_preflight_summary || {};
|
||
const rows = preflight.payload_rows || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterPreflightBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 writer preflight 只審核未來 CLI writer 草案;API 不開 DB、不建立 transaction、不執行 CLI、不寫 queue、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-preflight-gates>
|
||
<p class="market-intel-deploy-section-title">WRITER PREFLIGHT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 writer preflight gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-preflight-queue>
|
||
<p class="market-intel-deploy-section-title">QUEUE REVIEW LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('queue_review', `${queue.accepted ? 'accepted' : 'pending'} / items=${queue.review_item_count || 0}`, queue.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('writer_ready', queue.ready_for_candidate_queue_writer_preflight ? 'ready_for_writer_preflight' : 'not_ready', queue.ready_for_candidate_queue_writer_preflight ? 'READY' : 'BLOCK')}
|
||
${renderCheck('queue_boundary', 'no queue create / no DB / no scheduler', queue.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-preflight-payload>
|
||
<p class="market-intel-deploy-section-title">WRITER PAYLOAD ROWS</p>
|
||
<div class="market-intel-check-list">${
|
||
rows.slice(0, 10).map(item => renderCheck(
|
||
item.candidate_key || 'candidate',
|
||
`${item.candidate_type || 'candidate'} / ${item.review_state || 'missing'} / ${item.priority_lane || 'lane'}`,
|
||
item.required_fields_present ? 'READY' : 'BLOCK'
|
||
)).join('') || '<div class="market-intel-empty">尚未提供 writer payload rows。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-preflight-columns>
|
||
<p class="market-intel-deploy-section-title">WRITE PLAN</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('target_table', preflight.target_table || 'missing', preflight.target_table === 'market_alert_review_queue' ? 'TARGET' : 'BLOCK')}
|
||
${renderCheck('write_mode', preflight.write_mode || 'missing', preflight.write_mode === 'cli_only_later' ? 'CLI LATER' : 'BLOCK')}
|
||
${renderCheck('dedupe_strategy', preflight.dedupe_strategy || 'missing', preflight.dedupe_strategy === 'candidate_type_platform_source_key' ? 'READY' : 'BLOCK')}
|
||
${renderCheck('required_columns', 'candidate queue insert columns', preflight.insert_columns_cover_required ? 'READY' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-preflight-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'api_boundary',
|
||
'no DB / no transaction / no CLI / no queue write / no scheduler',
|
||
data.api_writes_database ? 'BLOCK' : 'CLOSED'
|
||
)}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterPreflightInput && !mcpFetchCandidateQueueWriterPreflightInput.value.trim() && data.sample_writer_preflight_package) {
|
||
mcpFetchCandidateQueueWriterPreflightInput.value = JSON.stringify(data.sample_writer_preflight_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterPreflight = async () => {
|
||
if (!mcpFetchCandidateQueueWriterPreflightMeta || !mcpFetchCandidateQueueWriterPreflightBody) return;
|
||
mcpFetchCandidateQueueWriterPreflightBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Candidate Queue Writer Preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterPreflightEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterPreflightMeta(data);
|
||
renderMcpFetchCandidateQueueWriterPreflightBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterPreflightMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterPreflightBody.innerHTML = `<div class="market-intel-empty">MCP Candidate Queue Writer Preflight 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterPreflight = async () => {
|
||
if (!mcpFetchCandidateQueueWriterPreflightMeta || !mcpFetchCandidateQueueWriterPreflightBody || !mcpFetchCandidateQueueWriterPreflightInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterPreflightInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterPreflightMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterPreflightBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterPreflightBody.innerHTML = '<div class="market-intel-empty">審核 MCP Candidate Queue Writer Preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterPreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ candidate_queue_writer_preflight: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterPreflightMeta(data);
|
||
renderMcpFetchCandidateQueueWriterPreflightBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterPreflightMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterPreflightBody.innerHTML = `<div class="market-intel-empty">MCP Candidate Queue Writer Preflight 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterCliReviewMeta = data => {
|
||
mcpFetchCandidateQueueWriterCliReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_cli_review_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || 0}`,
|
||
`cli=${data.api_executes_cli ? 'exec' : 'blocked'}`,
|
||
`db=${data.api_writes_database ? 'write' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterCliReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const preflight = data.writer_preflight_summary || {};
|
||
const review = data.writer_cli_review_summary || {};
|
||
const argv = review.command_argv_preview || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterCliReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 CLI review 只審核人工 shell writer 草案;API 不執行 CLI、不讀 approval token、不寫檔、不開 DB、不寫 queue、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-gates>
|
||
<p class="market-intel-deploy-section-title">CLI REVIEW GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 CLI review gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-preflight>
|
||
<p class="market-intel-deploy-section-title">PREFLIGHT LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('writer_preflight', `${preflight.accepted ? 'accepted' : 'pending'} / rows=${preflight.payload_row_count || 0}`, preflight.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('preflight_id', preflight.preflight_id || 'missing', preflight.preflight_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('preflight_boundary', 'no CLI / no DB / no scheduler', preflight.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-command>
|
||
<p class="market-intel-deploy-section-title">COMMAND REVIEW</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('script_path', review.script_path || 'missing', review.script_path === 'scripts/market_intel_candidate_queue_writer.py' ? 'READY' : 'BLOCK')}
|
||
${renderCheck('command_mode', review.command_mode || 'missing', review.command_mode === 'manual_shell_review_only' ? 'MANUAL' : 'BLOCK')}
|
||
${renderCheck('safe_flags', argv.join(' '), review.required_safe_flags_present ? 'READY' : 'BLOCK')}
|
||
${renderCheck('forbidden_flags', '--execute / --apply-real-write / --approval-token', review.forbidden_command_flags_absent ? 'ABSENT' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-payload>
|
||
<p class="market-intel-deploy-section-title">PAYLOAD CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('target_table', review.target_table || 'missing', review.target_table === 'market_alert_review_queue' ? 'TARGET' : 'BLOCK')}
|
||
${renderCheck('row_count', `${review.expected_payload_row_count || 0}`, review.expected_payload_row_count === preflight.payload_row_count ? 'MATCH' : 'BLOCK')}
|
||
${renderCheck('candidate_keys', `${(review.expected_candidate_keys || []).length}`, review.candidate_keys_unique ? 'UNIQUE' : 'BLOCK')}
|
||
${renderCheck('dedupe_keys', `${(review.expected_dedupe_keys || []).length}`, review.dedupe_keys_unique ? 'UNIQUE' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-cli-review-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck(
|
||
'api_boundary',
|
||
'no CLI / no token / no DB / no queue write / no scheduler',
|
||
data.api_executes_cli || data.api_reads_approval_token || data.api_writes_database ? 'BLOCK' : 'CLOSED'
|
||
)}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterCliReviewInput && !mcpFetchCandidateQueueWriterCliReviewInput.value.trim() && data.sample_writer_cli_review_package) {
|
||
mcpFetchCandidateQueueWriterCliReviewInput.value = JSON.stringify(data.sample_writer_cli_review_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterCliReview = async () => {
|
||
if (!mcpFetchCandidateQueueWriterCliReviewMeta || !mcpFetchCandidateQueueWriterCliReviewBody) return;
|
||
mcpFetchCandidateQueueWriterCliReviewBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Writer CLI Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterCliReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterCliReviewMeta(data);
|
||
renderMcpFetchCandidateQueueWriterCliReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterCliReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterCliReviewBody.innerHTML = `<div class="market-intel-empty">MCP Writer CLI Review 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterCliReview = async () => {
|
||
if (!mcpFetchCandidateQueueWriterCliReviewMeta || !mcpFetchCandidateQueueWriterCliReviewBody || !mcpFetchCandidateQueueWriterCliReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterCliReviewInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterCliReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterCliReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterCliReviewBody.innerHTML = '<div class="market-intel-empty">審核 MCP Writer CLI Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterCliReviewEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_cli_review_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterCliReviewMeta(data);
|
||
renderMcpFetchCandidateQueueWriterCliReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterCliReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterCliReviewBody.innerHTML = `<div class="market-intel-empty">MCP Writer CLI Review 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterRunPackageReviewMeta = data => {
|
||
mcpFetchCandidateQueueWriterRunPackageReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_run_package_review_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || 0}`,
|
||
`cli=${data.api_executes_cli ? 'exec' : 'blocked'}`,
|
||
`db=${data.api_writes_database ? 'write' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterRunPackageReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const cli = data.writer_cli_review_summary || {};
|
||
const pkg = data.writer_run_package_review_summary || {};
|
||
const artifacts = pkg.required_artifacts || [];
|
||
const commands = pkg.command_bundle || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterRunPackageReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 run package review 只審核操作包草案;API 不建立 artifact、不寫檔、不執行 CLI、不讀 approval token、不開 DB、不寫 queue、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-gates>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 run package gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-cli-review>
|
||
<p class="market-intel-deploy-section-title">CLI REVIEW LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('writer_cli_review', `${cli.accepted ? 'accepted' : 'pending'} / rows=${cli.payload_row_count || 0}`, cli.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('review_id', cli.review_id || 'missing', cli.review_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('api_boundary', 'no CLI / no token / no DB / no scheduler', cli.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-artifacts>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT MANIFEST</p>
|
||
<div class="market-intel-check-list">${
|
||
artifacts.length
|
||
? artifacts.map(item => renderCheck(item.key || 'artifact', item.path_shape || 'missing', !item.created_by_api ? 'OPERATOR' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 artifact manifest。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-commands>
|
||
<p class="market-intel-deploy-section-title">COMMAND PACKAGE</p>
|
||
<div class="market-intel-check-list">${
|
||
commands.length
|
||
? commands.map(item => renderCheck(item.key || `step_${item.step || 0}`, item.operator_shell_only ? 'operator shell only' : 'missing boundary', item.api_executes || item.writes_database ? 'BLOCK' : 'PREVIEW')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 command package。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-package-review-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('package_mode', pkg.package_mode || 'missing', pkg.package_mode === 'candidate_queue_writer_run_package_review_preview' ? 'PREVIEW' : 'BLOCK')}
|
||
${renderCheck('next_gate', pkg.promotion_next_gate || 'missing', pkg.promotion_next_gate ? 'NEXT' : 'BLOCK')}
|
||
${renderCheck('api_side_effects', 'no artifact / no file / no CLI / no token / no DB / no queue', data.package_artifact_created || data.api_executes_cli || data.api_reads_approval_token || data.api_writes_database || data.api_writes_file ? 'BLOCK' : 'CLOSED')}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterRunPackageReviewInput && !mcpFetchCandidateQueueWriterRunPackageReviewInput.value.trim() && data.sample_writer_run_package_review_package) {
|
||
mcpFetchCandidateQueueWriterRunPackageReviewInput.value = JSON.stringify(data.sample_writer_run_package_review_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterRunPackageReview = async () => {
|
||
if (!mcpFetchCandidateQueueWriterRunPackageReviewMeta || !mcpFetchCandidateQueueWriterRunPackageReviewBody) return;
|
||
mcpFetchCandidateQueueWriterRunPackageReviewBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Writer Run Package Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterRunPackageReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterRunPackageReviewMeta(data);
|
||
renderMcpFetchCandidateQueueWriterRunPackageReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunPackageReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterRunPackageReviewBody.innerHTML = `<div class="market-intel-empty">MCP Writer Run Package Review 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterRunPackageReview = async () => {
|
||
if (!mcpFetchCandidateQueueWriterRunPackageReviewMeta || !mcpFetchCandidateQueueWriterRunPackageReviewBody || !mcpFetchCandidateQueueWriterRunPackageReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterRunPackageReviewInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunPackageReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterRunPackageReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterRunPackageReviewBody.innerHTML = '<div class="market-intel-empty">審核 MCP Writer Run Package Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterRunPackageReviewEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_run_package_review_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterRunPackageReviewMeta(data);
|
||
renderMcpFetchCandidateQueueWriterRunPackageReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunPackageReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterRunPackageReviewBody.innerHTML = `<div class="market-intel-empty">MCP Writer Run Package Review 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterRunReadinessMeta = data => {
|
||
mcpFetchCandidateQueueWriterRunReadinessMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_run_readiness_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || 0}`,
|
||
`operator=${data.ready_for_candidate_queue_writer_operator_run ? 'ready' : 'blocked'}`,
|
||
`db=${data.api_writes_database ? 'write' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterRunReadinessBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const pkg = data.writer_run_package_review_summary || {};
|
||
const operator = data.operator_readiness_summary || {};
|
||
const artifacts = [
|
||
['run_readiness_artifact_path', operator.run_readiness_artifact_path, operator.run_readiness_artifact_path_safe],
|
||
['reviewed_sample_json_path', operator.reviewed_sample_json_path, operator.reviewed_sample_json_path_safe],
|
||
['backup_artifact_path', operator.backup_artifact_path, operator.backup_artifact_path_safe],
|
||
['read_only_preflight_output_path', operator.read_only_preflight_output_path, operator.read_only_preflight_output_path_safe],
|
||
['postwrite_smoke_plan_path', operator.postwrite_smoke_plan_path, operator.postwrite_smoke_plan_path_safe]
|
||
];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterRunReadinessBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 run readiness 只審核操作員準備證據;API 不建立 artifact、不寫檔、不執行 CLI、不讀 approval token、不開 DB、不寫 queue、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-gates>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 readiness gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-package>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE REVIEW</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('run_package_review', `${pkg.accepted ? 'accepted' : 'pending'} / rows=${pkg.payload_row_count || 0}`, pkg.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('target_table', pkg.target_table || 'missing', pkg.target_table === 'market_alert_review_queue' ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('api_boundary', 'no CLI / no token / no DB / no scheduler', pkg.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-operator>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CONFIRMATIONS</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('run_package_reviewed', 'package / manifest / command / payload / table', operator.run_package_reviewed && operator.artifact_manifest_reviewed && operator.command_bundle_reviewed && operator.payload_count_reviewed && operator.target_table_reviewed ? 'CONFIRMED' : 'BLOCK')}
|
||
${renderCheck('preflight_and_backup', 'backup / read-only preflight / post-write smoke', operator.backup_artifact_reviewed && operator.read_only_preflight_reviewed && operator.postwrite_smoke_required ? 'CONFIRMED' : 'BLOCK')}
|
||
${renderCheck('shell_only_boundaries', 'token shell-only, no API CLI/DB/file/scheduler', operator.one_time_token_shell_only_confirmed && operator.operator_confirmed_no_api_cli_execution && operator.operator_confirmed_no_api_database_write && operator.operator_confirmed_no_scheduler_attach ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-artifacts>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT PATHS</p>
|
||
<div class="market-intel-check-list">
|
||
${artifacts.map(item => renderCheck(item[0], item[1] || 'missing', item[2] ? 'SAFE' : 'BLOCK')).join('')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-readiness-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('operator_run', data.ready_for_candidate_queue_writer_operator_run ? 'shell writer CLI allowed after this gate' : 'blocked', data.ready_for_candidate_queue_writer_operator_run ? 'READY' : 'BLOCK')}
|
||
${renderCheck('api_side_effects', 'no artifact / no file / no CLI / no token / no DB / no queue', data.run_readiness_artifact_created || data.api_executes_cli || data.api_reads_approval_token || data.api_writes_database || data.api_writes_file ? 'BLOCK' : 'CLOSED')}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterRunReadinessInput && !mcpFetchCandidateQueueWriterRunReadinessInput.value.trim() && data.sample_writer_run_readiness_package) {
|
||
mcpFetchCandidateQueueWriterRunReadinessInput.value = JSON.stringify(data.sample_writer_run_readiness_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterRunReadiness = async () => {
|
||
if (!mcpFetchCandidateQueueWriterRunReadinessMeta || !mcpFetchCandidateQueueWriterRunReadinessBody) return;
|
||
mcpFetchCandidateQueueWriterRunReadinessBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Writer Run Readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterRunReadinessEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterRunReadinessMeta(data);
|
||
renderMcpFetchCandidateQueueWriterRunReadinessBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunReadinessMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterRunReadinessBody.innerHTML = `<div class="market-intel-empty">MCP Writer Run Readiness 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterRunReadiness = async () => {
|
||
if (!mcpFetchCandidateQueueWriterRunReadinessMeta || !mcpFetchCandidateQueueWriterRunReadinessBody || !mcpFetchCandidateQueueWriterRunReadinessInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterRunReadinessInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunReadinessMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterRunReadinessBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterRunReadinessBody.innerHTML = '<div class="market-intel-empty">審核 MCP Writer Run Readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterRunReadinessEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_run_readiness_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterRunReadinessMeta(data);
|
||
renderMcpFetchCandidateQueueWriterRunReadinessBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunReadinessMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterRunReadinessBody.innerHTML = `<div class="market-intel-empty">MCP Writer Run Readiness 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterRunReceiptReviewMeta = data => {
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_run_receipt_review_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || 0}`,
|
||
`closeout=${data.ready_for_candidate_queue_writer_run_closeout_review ? 'ready' : 'blocked'}`,
|
||
`db=${data.api_writes_database ? 'write' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterRunReceiptReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const readiness = data.writer_run_readiness_summary || {};
|
||
const receipt = data.writer_run_receipt_summary || {};
|
||
const artifacts = [
|
||
['writer_output_path', receipt.writer_output_path, receipt.writer_output_path_safe],
|
||
['postwrite_smoke_result_path', receipt.postwrite_smoke_result_path, receipt.postwrite_smoke_result_path_safe],
|
||
['backup_artifact_path', receipt.backup_artifact_path, receipt.backup_artifact_path_safe]
|
||
];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 run receipt review 只審核操作員貼回的 receipt 摘要;API 不讀 receipt 原文、不執行 CLI、不讀 approval token、不開 DB、不寫 queue、不做 post-write query、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-gates>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 receipt gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-readiness>
|
||
<p class="market-intel-deploy-section-title">RUN READINESS LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('run_readiness', `${readiness.accepted ? 'accepted' : 'pending'} / rows=${readiness.payload_row_count || 0}`, readiness.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('run_package_id', readiness.run_package_id || 'missing', readiness.run_package_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('api_boundary', 'no CLI / no token / no DB / no scheduler', readiness.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-receipt>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('receipt_id', receipt.receipt_id || 'missing', receipt.receipt_id ? 'RECORDED' : 'BLOCK')}
|
||
${renderCheck('writer_exit_code', String(receipt.writer_exit_code ?? 'missing'), receipt.writer_exit_code === 0 ? 'PASS' : 'BLOCK')}
|
||
${renderCheck('inserted_rows', `${receipt.inserted_row_count || 0}/${readiness.payload_row_count || 0}`, receipt.inserted_row_count === readiness.payload_row_count ? 'MATCH' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-artifacts>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT PATHS</p>
|
||
<div class="market-intel-check-list">
|
||
${artifacts.map(item => renderCheck(item[0], item[1] || 'missing', item[2] ? 'SAFE' : 'BLOCK')).join('')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-receipt-review-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('postwrite_smoke', 'operator read-only smoke, expected rows found', receipt.postwrite_smoke_read_only_by_operator && receipt.postwrite_smoke_expected_rows_found && receipt.postwrite_smoke_row_count_matches_payload ? 'PASS' : 'BLOCK')}
|
||
${renderCheck('api_side_effects', 'no receipt file / no CLI / no token / no DB / no queue', data.run_receipt_file_written || data.api_executes_cli || data.api_reads_approval_token || data.api_writes_database || data.postwrite_query_executed_by_api ? 'BLOCK' : 'CLOSED')}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterRunReceiptReviewInput && !mcpFetchCandidateQueueWriterRunReceiptReviewInput.value.trim() && data.sample_writer_run_receipt_review_package) {
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewInput.value = JSON.stringify(data.sample_writer_run_receipt_review_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterRunReceiptReview = async () => {
|
||
if (!mcpFetchCandidateQueueWriterRunReceiptReviewMeta || !mcpFetchCandidateQueueWriterRunReceiptReviewBody) return;
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Writer Run Receipt Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterRunReceiptReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterRunReceiptReviewMeta(data);
|
||
renderMcpFetchCandidateQueueWriterRunReceiptReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewBody.innerHTML = `<div class="market-intel-empty">MCP Writer Run Receipt Review 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterRunReceiptReview = async () => {
|
||
if (!mcpFetchCandidateQueueWriterRunReceiptReviewMeta || !mcpFetchCandidateQueueWriterRunReceiptReviewBody || !mcpFetchCandidateQueueWriterRunReceiptReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterRunReceiptReviewInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewBody.innerHTML = '<div class="market-intel-empty">審核 MCP Writer Run Receipt Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterRunReceiptReviewEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_run_receipt_review_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterRunReceiptReviewMeta(data);
|
||
renderMcpFetchCandidateQueueWriterRunReceiptReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewBody.innerHTML = `<div class="market-intel-empty">MCP Writer Run Receipt Review 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterRunCloseoutReviewMeta = data => {
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_run_closeout_review_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || 0}`,
|
||
`inventory=${data.ready_for_candidate_queue_post_closeout_inventory_review ? 'ready' : 'blocked'}`,
|
||
`db=${data.api_writes_database ? 'write' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterRunCloseoutReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.writer_run_receipt_review_summary || {};
|
||
const closeout = data.operator_closeout_summary || {};
|
||
const artifacts = [
|
||
['closeout_artifact_path', closeout.closeout_artifact_path, closeout.closeout_artifact_path_safe],
|
||
['receipt_review_artifact_path', closeout.receipt_review_artifact_path, closeout.receipt_review_artifact_path_safe],
|
||
['post_closeout_inventory_plan_path', closeout.post_closeout_inventory_plan_path, closeout.post_closeout_inventory_plan_path_safe],
|
||
['manifest_writer_output_path', closeout.manifest_writer_output_path, closeout.manifest_writer_output_path_safe],
|
||
['manifest_postwrite_smoke_result_path', closeout.manifest_postwrite_smoke_result_path, closeout.manifest_postwrite_smoke_result_path_safe],
|
||
['manifest_backup_artifact_path', closeout.manifest_backup_artifact_path, closeout.manifest_backup_artifact_path_safe]
|
||
];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 run closeout review 只審核 receipt review 後的收尾摘要;API 不讀 receipt 原文、不執行 CLI、不讀 approval token、不開 DB、不寫 queue、不做 post-closeout query、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-gates>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 closeout gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-receipt>
|
||
<p class="market-intel-deploy-section-title">RECEIPT REVIEW LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('receipt_review', `${receipt.accepted ? 'accepted' : 'pending'} / rows=${receipt.payload_row_count || 0}`, receipt.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('receipt_id', receipt.receipt_id || 'missing', receipt.receipt_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('api_boundary', 'no CLI / no token / no DB / no scheduler', receipt.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-closeout>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('closeout_id', closeout.closeout_id || 'missing', closeout.closeout_id ? 'RECORDED' : 'BLOCK')}
|
||
${renderCheck('run_package_id', closeout.run_package_id || 'missing', closeout.run_package_id === receipt.run_package_id ? 'MATCH' : 'BLOCK')}
|
||
${renderCheck('receipt_id', closeout.receipt_id || 'missing', closeout.receipt_id === receipt.receipt_id ? 'MATCH' : 'BLOCK')}
|
||
${renderCheck('notes', 'closeout notes + rollback note', closeout.closeout_notes_recorded && closeout.rollback_note_recorded ? 'RECORDED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-artifacts>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT TRACE</p>
|
||
<div class="market-intel-check-list">
|
||
${artifacts.map(item => renderCheck(item[0], item[1] || 'missing', item[2] ? 'SAFE' : 'BLOCK')).join('')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-run-closeout-review-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('operator_boundaries', 'artifact preserved, read-only inventory, no API DB/CLI/file/scheduler', closeout.receipt_reviewed && closeout.artifacts_preserved && closeout.post_closeout_inventory_read_only && closeout.no_api_database_write && closeout.no_scheduler_attach ? 'CONFIRMED' : 'BLOCK')}
|
||
${renderCheck('api_side_effects', 'no closeout file / no CLI / no token / no DB / no queue', data.run_closeout_file_written || data.api_executes_cli || data.api_reads_approval_token || data.api_writes_database || data.post_closeout_query_executed_by_api ? 'BLOCK' : 'CLOSED')}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterRunCloseoutReviewInput && !mcpFetchCandidateQueueWriterRunCloseoutReviewInput.value.trim() && data.sample_writer_run_closeout_review_package) {
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewInput.value = JSON.stringify(data.sample_writer_run_closeout_review_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterRunCloseoutReview = async () => {
|
||
if (!mcpFetchCandidateQueueWriterRunCloseoutReviewMeta || !mcpFetchCandidateQueueWriterRunCloseoutReviewBody) return;
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Writer Run Closeout Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterRunCloseoutReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterRunCloseoutReviewMeta(data);
|
||
renderMcpFetchCandidateQueueWriterRunCloseoutReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewBody.innerHTML = `<div class="market-intel-empty">MCP Writer Run Closeout Review 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterRunCloseoutReview = async () => {
|
||
if (!mcpFetchCandidateQueueWriterRunCloseoutReviewMeta || !mcpFetchCandidateQueueWriterRunCloseoutReviewBody || !mcpFetchCandidateQueueWriterRunCloseoutReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterRunCloseoutReviewInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewBody.innerHTML = '<div class="market-intel-empty">審核 MCP Writer Run Closeout Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterRunCloseoutReviewEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_run_closeout_review_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterRunCloseoutReviewMeta(data);
|
||
renderMcpFetchCandidateQueueWriterRunCloseoutReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewBody.innerHTML = `<div class="market-intel-empty">MCP Writer Run Closeout Review 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterPostCloseoutInventoryReviewMeta = data => {
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_post_closeout_inventory_review_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || 0}`,
|
||
`handoff=${data.ready_for_candidate_queue_review_handoff ? 'ready' : 'blocked'}`,
|
||
`query=${data.api_inventory_query_executed ? 'api' : 'operator'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.writer_run_closeout_review_summary || {};
|
||
const inventory = data.operator_inventory_summary || {};
|
||
const artifacts = [
|
||
['inventory_artifact_path', inventory.inventory_artifact_path, inventory.inventory_artifact_path_safe],
|
||
['closeout_review_artifact_path', inventory.closeout_review_artifact_path, inventory.closeout_review_artifact_path_safe],
|
||
['read_only_query_result_path', inventory.read_only_query_result_path, inventory.read_only_query_result_path_safe]
|
||
];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 post-closeout inventory review 只審核操作員已完成的 live inventory read-only 摘要;API 不查 DB、不補寫 queue row、不讀 approval token、不執行 CLI、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-gates>
|
||
<p class="market-intel-deploy-section-title">INVENTORY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 inventory gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-closeout>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('closeout_review', `${closeout.accepted ? 'accepted' : 'pending'} / rows=${closeout.payload_row_count || 0}`, closeout.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('closeout_id', closeout.closeout_id || 'missing', closeout.closeout_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('manifest', 'writer output + post-write smoke + backup', closeout.artifact_manifest_preserved ? 'PRESERVED' : 'BLOCK')}
|
||
${renderCheck('api_boundary', 'no CLI / no token / no DB / no scheduler', closeout.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-inventory>
|
||
<p class="market-intel-deploy-section-title">INVENTORY SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('inventory_review_id', inventory.inventory_review_id || 'missing', inventory.inventory_review_id ? 'RECORDED' : 'BLOCK')}
|
||
${renderCheck('found_rows', `${inventory.found_row_count || 0}/${closeout.payload_row_count || 0}`, inventory.found_row_count === closeout.payload_row_count ? 'MATCH' : 'BLOCK')}
|
||
${renderCheck('missing_rows', String(inventory.missing_row_count || 0), inventory.missing_row_count === 0 ? 'NONE' : 'BLOCK')}
|
||
${renderCheck('duplicates', String(inventory.unexpected_duplicate_count || 0), inventory.unexpected_duplicate_count === 0 ? 'NONE' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-artifacts>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT TRACE</p>
|
||
<div class="market-intel-check-list">
|
||
${artifacts.map(item => renderCheck(item[0], item[1] || 'missing', item[2] ? 'SAFE' : 'BLOCK')).join('')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-post-closeout-inventory-review-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('read_only_query', `${inventory.query_type || 'missing'} / operator shell`, inventory.read_only_query_executed_by_operator && inventory.query_type === 'read_only_select' ? 'PASS' : 'BLOCK')}
|
||
${renderCheck('operator_boundaries', 'no API DB/query/CLI/file/scheduler', inventory.inventory_read_only && inventory.no_api_database_write && inventory.no_api_inventory_query && inventory.no_api_cli_execution && inventory.no_scheduler_attach ? 'CONFIRMED' : 'BLOCK')}
|
||
${renderCheck('api_side_effects', 'no inventory file / no API query / no DB write / no queue write', data.inventory_file_written || data.api_inventory_query_executed || data.api_writes_database || data.candidate_queue_persisted ? 'BLOCK' : 'CLOSED')}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewInput && !mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewInput.value.trim() && data.sample_writer_post_closeout_inventory_review_package) {
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewInput.value = JSON.stringify(data.sample_writer_post_closeout_inventory_review_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterPostCloseoutInventoryReview = async () => {
|
||
if (!mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewMeta || !mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody) return;
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Writer Post-Closeout Inventory Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterPostCloseoutInventoryReviewMeta(data);
|
||
renderMcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody.innerHTML = `<div class="market-intel-empty">MCP Writer Post-Closeout Inventory Review 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterPostCloseoutInventoryReview = async () => {
|
||
if (!mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewMeta || !mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody || !mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody.innerHTML = '<div class="market-intel-empty">審核 MCP Writer Post-Closeout Inventory Review 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_post_closeout_inventory_review_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterPostCloseoutInventoryReviewMeta(data);
|
||
renderMcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewBody.innerHTML = `<div class="market-intel-empty">MCP Writer Post-Closeout Inventory Review 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterReviewHandoffMeta = data => {
|
||
mcpFetchCandidateQueueWriterReviewHandoffMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_review_handoff_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || 0}`,
|
||
`inventory=${data.ready_for_candidate_queue_review_inventory ? 'ready' : 'blocked'}`,
|
||
`state=${data.api_updates_review_state ? 'api' : 'manual'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterReviewHandoffBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const inventory = data.writer_post_closeout_inventory_review_summary || {};
|
||
const handoff = data.operator_handoff_summary || {};
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterReviewHandoffBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 review handoff 只把已通過的 post-closeout inventory 交接給人工 candidate queue review;API 不更新 review_state、不補寫 queue row、不讀 approval token、不執行 CLI、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-gates>
|
||
<p class="market-intel-deploy-section-title">HANDOFF GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 handoff gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-inventory>
|
||
<p class="market-intel-deploy-section-title">INVENTORY LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('inventory_review', `${inventory.accepted ? 'accepted' : 'pending'} / rows=${inventory.payload_row_count || 0}`, inventory.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('inventory_review_id', inventory.inventory_review_id || 'missing', inventory.inventory_review_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('found_rows', `${inventory.found_row_count || 0}/${inventory.payload_row_count || 0}`, inventory.found_row_count === inventory.payload_row_count ? 'MATCH' : 'BLOCK')}
|
||
${renderCheck('api_boundary', 'no DB / no queue write / no review_state update / no scheduler', inventory.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-handoff>
|
||
<p class="market-intel-deploy-section-title">HANDOFF SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('handoff_id', handoff.handoff_id || 'missing', handoff.handoff_id ? 'RECORDED' : 'BLOCK')}
|
||
${renderCheck('target_table', handoff.target_table || 'missing', handoff.target_table === 'market_alert_review_queue' ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('review_scope', handoff.review_scope || 'missing', handoff.review_scope === 'candidate_queue_review' ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('next_queue', handoff.next_queue || 'missing', handoff.next_queue === 'manual_candidate_queue_review' ? 'MANUAL' : 'BLOCK')}
|
||
${renderCheck('row_count', String(handoff.payload_row_count || 0), handoff.payload_row_count === inventory.payload_row_count ? 'MATCH' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-contract>
|
||
<p class="market-intel-deploy-section-title">REVIEW CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('review_queue_artifact_path', handoff.review_queue_artifact_path || 'missing', handoff.review_queue_artifact_path_safe ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('inventory_artifact_path', handoff.inventory_artifact_path || 'missing', handoff.inventory_artifact_path_safe ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('handoff_artifact_path', handoff.handoff_artifact_path || 'missing', handoff.handoff_artifact_path_safe ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('operator_boundaries', 'manual only / no API DB query/write/file/CLI/scheduler', handoff.inventory_reviewed && handoff.inventory_artifacts_preserved && handoff.candidate_queue_review_only && handoff.manual_review_required && handoff.no_api_database_write && handoff.no_api_inventory_query && handoff.no_api_cli_execution && handoff.no_api_file_write && handoff.no_scheduler_attach ? 'CONFIRMED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-handoff-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('api_side_effects', 'no review_state update / no DB write / no queue write / no scheduler', data.api_updates_review_state || data.api_writes_database || data.candidate_queue_persisted || data.scheduler_attached ? 'BLOCK' : 'CLOSED')}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterReviewHandoffInput && !mcpFetchCandidateQueueWriterReviewHandoffInput.value.trim() && data.sample_writer_review_handoff_package) {
|
||
mcpFetchCandidateQueueWriterReviewHandoffInput.value = JSON.stringify(data.sample_writer_review_handoff_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterReviewHandoff = async () => {
|
||
if (!mcpFetchCandidateQueueWriterReviewHandoffMeta || !mcpFetchCandidateQueueWriterReviewHandoffBody) return;
|
||
mcpFetchCandidateQueueWriterReviewHandoffBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Writer Review Handoff 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterReviewHandoffEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterReviewHandoffMeta(data);
|
||
renderMcpFetchCandidateQueueWriterReviewHandoffBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewHandoffMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterReviewHandoffBody.innerHTML = `<div class="market-intel-empty">MCP Writer Review Handoff 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterReviewHandoff = async () => {
|
||
if (!mcpFetchCandidateQueueWriterReviewHandoffMeta || !mcpFetchCandidateQueueWriterReviewHandoffBody || !mcpFetchCandidateQueueWriterReviewHandoffInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterReviewHandoffInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewHandoffMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterReviewHandoffBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterReviewHandoffBody.innerHTML = '<div class="market-intel-empty">審核 MCP Writer Review Handoff 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterReviewHandoffEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_review_handoff_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterReviewHandoffMeta(data);
|
||
renderMcpFetchCandidateQueueWriterReviewHandoffBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewHandoffMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterReviewHandoffBody.innerHTML = `<div class="market-intel-empty">MCP Writer Review Handoff 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterReviewInventoryMeta = data => {
|
||
mcpFetchCandidateQueueWriterReviewInventoryMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_review_inventory_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.found_row_count || data.payload_row_count || 0}`,
|
||
`decision=${data.ready_for_candidate_queue_review_decision ? 'ready' : 'blocked'}`,
|
||
`state=${data.api_updates_review_state ? 'api' : 'manual'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterReviewInventoryBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const handoff = data.writer_review_handoff_summary || {};
|
||
const inventory = data.operator_review_inventory_summary || {};
|
||
const reviewStateBreakdown = inventory.review_state_breakdown || {};
|
||
const needsReviewCount = reviewStateBreakdown.needs_review || 0;
|
||
const dedupeKeys = inventory.sample_dedupe_keys || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterReviewInventoryBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 review inventory 只審核人工 candidate queue review 前的只讀 queue row 盤點;API 不查 DB、不更新 review_state、不補寫 queue row、不讀 approval token、不執行 CLI、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-gates>
|
||
<p class="market-intel-deploy-section-title">INVENTORY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 inventory gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-handoff>
|
||
<p class="market-intel-deploy-section-title">HANDOFF LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('handoff', `${handoff.accepted ? 'accepted' : 'pending'} / rows=${handoff.payload_row_count || 0}`, handoff.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('handoff_id', handoff.handoff_id || 'missing', handoff.handoff_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('target_table', handoff.target_table || 'missing', handoff.target_table === 'market_alert_review_queue' ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('api_boundary', 'no DB / no queue write / no review_state update / no scheduler', handoff.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-inventory>
|
||
<p class="market-intel-deploy-section-title">REVIEW INVENTORY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('review_inventory_id', inventory.review_inventory_id || 'missing', inventory.review_inventory_id ? 'RECORDED' : 'BLOCK')}
|
||
${renderCheck('found_rows', `${inventory.found_row_count || 0}/${handoff.payload_row_count || 0}`, inventory.found_row_count === handoff.payload_row_count ? 'MATCH' : 'BLOCK')}
|
||
${renderCheck('needs_review', String(needsReviewCount), needsReviewCount === handoff.payload_row_count ? 'LOCKED' : 'BLOCK')}
|
||
${renderCheck('missing_rows', String(inventory.missing_row_count || 0), inventory.missing_row_count === 0 ? 'NONE' : 'BLOCK')}
|
||
${renderCheck('duplicates', String(inventory.unexpected_duplicate_count || 0), inventory.unexpected_duplicate_count === 0 ? 'NONE' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-rows>
|
||
<p class="market-intel-deploy-section-title">ROW SNAPSHOT</p>
|
||
<div class="market-intel-check-list">${
|
||
dedupeKeys.length
|
||
? dedupeKeys.map((dedupeKey, index) => renderCheck(`dedupe_${index + 1}`, `${dedupeKey || 'missing'} / ${inventory.review_state || 'missing'}`, inventory.review_state === 'needs_review' ? 'READY' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 row snapshot。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-inventory-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('read_only_query', `${inventory.query_type || 'missing'} / operator shell`, inventory.read_only_query_executed_by_operator && inventory.query_type === 'read_only_select' ? 'PASS' : 'BLOCK')}
|
||
${renderCheck('operator_boundaries', 'manual only / no API DB query/write/file/CLI/scheduler', inventory.handoff_reviewed && inventory.review_inventory_read_only && inventory.candidate_queue_review_only && inventory.no_missing_rows && inventory.no_unexpected_duplicates && inventory.no_api_cli_execution && inventory.no_api_database_write && inventory.no_api_inventory_query && inventory.no_api_review_state_update && inventory.no_api_queue_insert && inventory.no_api_file_write && inventory.no_scheduler_attach ? 'CONFIRMED' : 'BLOCK')}
|
||
${renderCheck('api_side_effects', 'no review_state update / no DB write / no queue write / no scheduler', data.api_updates_review_state || data.api_writes_database || data.candidate_queue_persisted || data.scheduler_attached ? 'BLOCK' : 'CLOSED')}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterReviewInventoryInput && !mcpFetchCandidateQueueWriterReviewInventoryInput.value.trim() && data.sample_writer_review_inventory_package) {
|
||
mcpFetchCandidateQueueWriterReviewInventoryInput.value = JSON.stringify(data.sample_writer_review_inventory_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterReviewInventory = async () => {
|
||
if (!mcpFetchCandidateQueueWriterReviewInventoryMeta || !mcpFetchCandidateQueueWriterReviewInventoryBody) return;
|
||
mcpFetchCandidateQueueWriterReviewInventoryBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Writer Review Inventory 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterReviewInventoryEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterReviewInventoryMeta(data);
|
||
renderMcpFetchCandidateQueueWriterReviewInventoryBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewInventoryMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterReviewInventoryBody.innerHTML = `<div class="market-intel-empty">MCP Writer Review Inventory 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterReviewInventory = async () => {
|
||
if (!mcpFetchCandidateQueueWriterReviewInventoryMeta || !mcpFetchCandidateQueueWriterReviewInventoryBody || !mcpFetchCandidateQueueWriterReviewInventoryInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterReviewInventoryInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewInventoryMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterReviewInventoryBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterReviewInventoryBody.innerHTML = '<div class="market-intel-empty">審核 MCP Writer Review Inventory 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterReviewInventoryEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_review_inventory_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterReviewInventoryMeta(data);
|
||
renderMcpFetchCandidateQueueWriterReviewInventoryBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewInventoryMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterReviewInventoryBody.innerHTML = `<div class="market-intel-empty">MCP Writer Review Inventory 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterReviewDecisionMeta = data => {
|
||
const decisionRows = data.decision_rows || data.operator_review_decision_summary?.decision_rows || [];
|
||
mcpFetchCandidateQueueWriterReviewDecisionMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_review_decision_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || data.operator_review_decision_summary?.decision_row_count || decisionRows.length || 0}`,
|
||
`decision=${data.ready_for_candidate_queue_review_decision_approval ? 'ready' : 'blocked'}`,
|
||
`match=${data.api_writes_match_result ? 'api' : 'manual'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterReviewDecisionBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const inventory = data.writer_review_inventory_summary || {};
|
||
const decision = data.operator_review_decision_summary || {};
|
||
const rows = decision.decision_rows || data.decision_rows || [];
|
||
const contract = data.decision_contract || {};
|
||
const allowedDecisions = contract.allowed_next_states || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterReviewDecisionBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 review decision 只審核人工候選隊列決策摘要;API 不更新 review_state、不寫 match result、不補 queue row、不讀 approval token、不執行 CLI、不開 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-gates>
|
||
<p class="market-intel-deploy-section-title">DECISION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 decision gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-inventory>
|
||
<p class="market-intel-deploy-section-title">INVENTORY LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('inventory', `${inventory.mode || 'missing'} / ${inventory.accepted ? 'accepted' : 'pending'}`, inventory.accepted ? 'ACCEPTED' : 'BLOCK')}
|
||
${renderCheck('review_inventory_id', inventory.review_inventory_id || 'missing', inventory.review_inventory_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('handoff_id', inventory.handoff_id || 'missing', inventory.handoff_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('target_table', inventory.target_table || 'missing', inventory.target_table === 'market_alert_review_queue' ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('rows', `${inventory.found_row_count || 0}/${inventory.dedupe_key_count || 0}`, inventory.found_row_count === inventory.dedupe_key_count && inventory.found_row_count > 0 ? 'MATCH' : 'BLOCK')}
|
||
${renderCheck('api_boundary', 'no DB / no queue write / no review_state update / no scheduler', inventory.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-decision>
|
||
<p class="market-intel-deploy-section-title">DECISION SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('review_decision_id', decision.review_decision_id || 'missing', decision.review_decision_id ? 'RECORDED' : 'BLOCK')}
|
||
${renderCheck('scope', `${decision.decision_scope || 'missing'} / ${decision.decision_mode || 'missing'}`, decision.decision_scope === 'candidate_queue_review_decision' && decision.decision_mode === 'human_review_only' ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('current_state', decision.expected_current_review_state || 'missing', decision.expected_current_review_state === 'needs_review' ? 'LOCKED' : 'BLOCK')}
|
||
${renderCheck('artifacts', `${decision.review_decision_artifact_path || 'missing'}`, decision.review_decision_artifact_path_safe ? 'SAFE' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-rows>
|
||
<p class="market-intel-deploy-section-title">DECISION ROWS</p>
|
||
<div class="market-intel-check-list">${
|
||
rows.length
|
||
? rows.map((row, index) => renderCheck(
|
||
row.dedupe_key || `row_${index + 1}`,
|
||
`${row.current_review_state || 'missing'} -> ${row.proposed_review_decision || 'missing'} / ${row.evidence_lane || 'missing'}`,
|
||
allowedDecisions.includes(row.proposed_review_decision) && row.current_review_state === 'needs_review' && row.evidence_ref_safe && row.decision_notes_present ? 'READY' : 'BLOCK'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未提供 decision rows。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('allowed_decisions', allowedDecisions.join(' / ') || 'missing', allowedDecisions.length ? 'LOCKED' : 'BLOCK')}
|
||
${renderCheck('manual_only', `${contract.next_gate || 'candidate_queue_review_decision_approval'} / ${contract.decision_scope || 'missing'}`, contract.manual_decision_required ? 'CONFIRMED' : 'BLOCK')}
|
||
${renderCheck('operator_boundaries', 'manual decision / no API DB write / no review_state update / no match write', decision.review_inventory_checked && decision.human_decision_only && decision.no_api_cli_execution && decision.no_api_database_write && decision.no_api_inventory_query && decision.no_api_review_state_update && decision.no_api_queue_insert && decision.no_api_file_write && decision.no_scheduler_attach ? 'CONFIRMED' : 'BLOCK')}
|
||
${renderCheck('identity_guards', 'stronger existing match / false positive / variant / hard veto', decision.stronger_existing_match_guard_preserved && decision.false_positive_guard_completed && decision.variant_sensitive_review_completed && decision.hard_veto_respected ? 'CONFIRMED' : 'BLOCK')}
|
||
${renderCheck('api_side_effects', 'no decision record write / no match write / no scheduler', data.api_writes_decision_record || data.api_writes_match_result || data.decision_record_written || data.candidate_review_state_updated || data.scheduler_attached ? 'BLOCK' : 'CLOSED')}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterReviewDecisionInput && !mcpFetchCandidateQueueWriterReviewDecisionInput.value.trim() && data.sample_writer_review_decision_package) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionInput.value = JSON.stringify(data.sample_writer_review_decision_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterReviewDecision = async () => {
|
||
if (!mcpFetchCandidateQueueWriterReviewDecisionMeta || !mcpFetchCandidateQueueWriterReviewDecisionBody) return;
|
||
mcpFetchCandidateQueueWriterReviewDecisionBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Writer Review Decision 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterReviewDecisionEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionMeta(data);
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterReviewDecisionBody.innerHTML = `<div class="market-intel-empty">MCP Writer Review Decision 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterReviewDecision = async () => {
|
||
if (!mcpFetchCandidateQueueWriterReviewDecisionMeta || !mcpFetchCandidateQueueWriterReviewDecisionBody || !mcpFetchCandidateQueueWriterReviewDecisionInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterReviewDecisionInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterReviewDecisionBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterReviewDecisionBody.innerHTML = '<div class="market-intel-empty">審核 MCP Writer Review Decision 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterReviewDecisionEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_review_decision_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionMeta(data);
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterReviewDecisionBody.innerHTML = `<div class="market-intel-empty">MCP Writer Review Decision 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterReviewDecisionApprovalMeta = data => {
|
||
const approvalRows = data.approval_rows || data.operator_review_decision_approval_summary?.approved_rows || [];
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_review_decision_approval_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || data.operator_review_decision_approval_summary?.approved_row_count || approvalRows.length || 0}`,
|
||
`preflight=${data.ready_for_candidate_queue_review_decision_writer_preflight ? 'ready' : 'blocked'}`,
|
||
`write=${data.api_writes_approval_record || data.api_updates_review_state ? 'api' : 'manual'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterReviewDecisionApprovalBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const decision = data.writer_review_decision_summary || {};
|
||
const approval = data.operator_review_decision_approval_summary || {};
|
||
const rows = data.approval_rows || approval.approved_rows || [];
|
||
const contract = data.approval_contract || {};
|
||
const allowedApprovals = contract.allowed_next_states || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 decision approval 只審核人工批准摘要;API 不寫 approval record、不寫 decision record、不寫 match result、不更新 review_state、不補 queue、不讀 token、不執行 CLI、不開 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-gates>
|
||
<p class="market-intel-deploy-section-title">APPROVAL GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 approval gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-decision>
|
||
<p class="market-intel-deploy-section-title">DECISION LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('review_decision', `${decision.accepted ? 'accepted' : 'pending'} / rows=${decision.decision_row_count || 0}`, decision.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('review_decision_id', decision.review_decision_id || 'missing', decision.review_decision_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('target_table', decision.target_table || 'missing', decision.target_table === 'market_alert_review_queue' ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('api_boundary', 'no DB / no decision write / no match write / no review_state update / no scheduler', decision.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-approval>
|
||
<p class="market-intel-deploy-section-title">APPROVAL SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('approval_id', approval.approval_id || 'missing', approval.approval_id ? 'RECORDED' : 'BLOCK')}
|
||
${renderCheck('approval_scope', approval.approval_scope || 'missing', approval.approval_scope === 'candidate_queue_review_decision_approval' ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('approval_mode', approval.approval_mode || 'missing', approval.approval_mode === 'human_approval_only' ? 'MANUAL' : 'BLOCK')}
|
||
${renderCheck('row_count', `${approval.approved_row_count || 0}/${decision.decision_row_count || 0}`, approval.approved_row_count === decision.decision_row_count ? 'MATCH' : 'BLOCK')}
|
||
${renderCheck('artifact_path', approval.approval_artifact_path || 'missing', approval.approval_artifact_path_safe ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('operator_boundaries', 'manual approval only / no API DB write/file/CLI/review_state/match/scheduler', approval.review_decision_checked && approval.approval_is_human_only && approval.manual_record_required && approval.no_api_cli_execution && approval.no_api_database_write && approval.no_api_review_state_update && approval.no_api_match_write && approval.no_api_queue_insert && approval.no_api_file_write && approval.no_scheduler_attach ? 'CONFIRMED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-rows>
|
||
<p class="market-intel-deploy-section-title">APPROVAL ROWS</p>
|
||
<div class="market-intel-check-list">${
|
||
rows.length
|
||
? rows.map((row, index) => renderCheck(
|
||
row.dedupe_key || `row_${index + 1}`,
|
||
`${row.current_review_state || 'missing'} -> ${row.approved_review_decision || 'missing'} / ${row.approval_bucket || 'missing'}`,
|
||
allowedApprovals.includes(row.approved_review_decision) && row.evidence_ref_safe && row.approval_notes_present && row.false_positive_guard_passed && row.variant_conflict_checked && row.overwrite_protection_checked ? 'READY' : 'BLOCK'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未提供 approval rows。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('allowed_approvals', allowedApprovals.join(' / ') || 'missing', allowedApprovals.length ? 'LOCKED' : 'BLOCK')}
|
||
${renderCheck('next_gate', contract.next_gate || 'missing', contract.next_gate === 'candidate_queue_review_decision_writer_preflight' ? 'PREFLIGHT' : 'BLOCK')}
|
||
${renderCheck('api_side_effects', 'no approval record / no decision record / no match write / no review_state update / no scheduler', data.api_writes_approval_record || data.api_writes_decision_record || data.api_writes_match_result || data.api_updates_review_state || data.api_writes_database || data.scheduler_attached ? 'BLOCK' : 'CLOSED')}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterReviewDecisionApprovalInput && !mcpFetchCandidateQueueWriterReviewDecisionApprovalInput.value.trim() && data.sample_writer_review_decision_approval_package) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalInput.value = JSON.stringify(data.sample_writer_review_decision_approval_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterReviewDecisionApproval = async () => {
|
||
if (!mcpFetchCandidateQueueWriterReviewDecisionApprovalMeta || !mcpFetchCandidateQueueWriterReviewDecisionApprovalBody) return;
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Writer Review Decision Approval 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterReviewDecisionApprovalEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionApprovalMeta(data);
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionApprovalBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalBody.innerHTML = `<div class="market-intel-empty">MCP Writer Review Decision Approval 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterReviewDecisionApproval = async () => {
|
||
if (!mcpFetchCandidateQueueWriterReviewDecisionApprovalMeta || !mcpFetchCandidateQueueWriterReviewDecisionApprovalBody || !mcpFetchCandidateQueueWriterReviewDecisionApprovalInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterReviewDecisionApprovalInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalBody.innerHTML = '<div class="market-intel-empty">審核 MCP Writer Review Decision Approval 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterReviewDecisionApprovalEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_review_decision_approval_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionApprovalMeta(data);
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionApprovalBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalBody.innerHTML = `<div class="market-intel-empty">MCP Writer Review Decision Approval 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightMeta = data => {
|
||
const rows = data.preflight_rows || data.operator_writer_preflight_summary?.preflight_rows || [];
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`accepted=${data.mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight_accepted ? 'yes' : 'no'}`,
|
||
`gates=${data.passed_gate_count || 0}/${data.gate_count || 0}`,
|
||
`rows=${data.payload_row_count || data.operator_writer_preflight_summary?.preflight_row_count || rows.length || 0}`,
|
||
`cli=${data.ready_for_candidate_queue_review_decision_writer_cli_review ? 'ready' : 'blocked'}`,
|
||
`write=${data.api_updates_review_state || data.api_writes_database ? 'api' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const approval = data.writer_review_decision_approval_summary || {};
|
||
const preflight = data.operator_writer_preflight_summary || {};
|
||
const rows = data.preflight_rows || preflight.preflight_rows || [];
|
||
const contract = data.writer_preflight_contract || {};
|
||
const allowedStates = contract.allowed_next_states || [];
|
||
const steps = data.next_operator_steps || [];
|
||
const renderCheck = (key, label, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此 writer preflight 只審核人工 approval 後的 CLI-only 更新草案;API 不寫 preflight、approval、decision、match、不更新 review_state、不讀 token、不執行 CLI、不開 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-gates>
|
||
<p class="market-intel-deploy-section-title">PREFLIGHT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => renderCheck(item.key, item.label, item.passed ? 'PASS' : 'BLOCK')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 preflight gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-approval>
|
||
<p class="market-intel-deploy-section-title">APPROVAL LINK</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('approval', `${approval.accepted ? 'accepted' : 'pending'} / rows=${approval.approved_row_count || 0}`, approval.accepted ? 'ACCEPTED' : 'PENDING')}
|
||
${renderCheck('approval_id', approval.approval_id || 'missing', approval.approval_id ? 'LINKED' : 'BLOCK')}
|
||
${renderCheck('target_table', approval.target_table || 'missing', approval.target_table === 'market_alert_review_queue' ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('api_boundary', 'no DB / no approval write / no decision write / no match write / no review_state update / no scheduler', approval.side_effects_clear ? 'CLOSED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-summary>
|
||
<p class="market-intel-deploy-section-title">WRITER PREFLIGHT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('writer_preflight_id', preflight.writer_preflight_id || 'missing', preflight.writer_preflight_id ? 'RECORDED' : 'BLOCK')}
|
||
${renderCheck('scope', preflight.preflight_scope || 'missing', preflight.preflight_scope === 'candidate_queue_review_decision_approval_writer_preflight' ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('mode', preflight.preflight_mode || 'missing', preflight.preflight_mode === 'cli_only_writer_preflight_preview' ? 'CLI ONLY' : 'BLOCK')}
|
||
${renderCheck('row_count', `${preflight.preflight_row_count || 0}/${approval.approved_row_count || 0}`, preflight.preflight_row_count === approval.approved_row_count ? 'MATCH' : 'BLOCK')}
|
||
${renderCheck('artifact_path', preflight.writer_preflight_artifact_path || 'missing', preflight.writer_preflight_artifact_path_safe ? 'SAFE' : 'BLOCK')}
|
||
${renderCheck('operator_boundaries', 'approval checked / no API DB write/file/CLI/review_state/match/scheduler', preflight.approval_checked && preflight.writer_preflight_is_cli_only && preflight.no_api_cli_execution && preflight.no_api_database_write && preflight.no_api_review_state_update && preflight.no_api_match_write && preflight.no_api_queue_insert && preflight.no_api_file_write && preflight.no_scheduler_attach ? 'CONFIRMED' : 'BLOCK')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-rows>
|
||
<p class="market-intel-deploy-section-title">PREFLIGHT ROWS</p>
|
||
<div class="market-intel-check-list">${
|
||
rows.length
|
||
? rows.map((row, index) => renderCheck(
|
||
row.dedupe_key || `row_${index + 1}`,
|
||
`${row.current_review_state || 'missing'} -> ${row.target_review_state || 'missing'} / ${row.evidence_lane || 'missing'}`,
|
||
allowedStates.includes(row.target_review_state) && row.preflight_evidence_ref_safe && row.preflight_notes_present && row.false_positive_guard_passed && row.variant_conflict_checked && row.overwrite_protection_checked ? 'READY' : 'BLOCK'
|
||
)).join('')
|
||
: '<div class="market-intel-empty">尚未提供 preflight rows。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-mcp-fetch-candidate-queue-writer-review-decision-approval-writer-preflight-next>
|
||
<p class="market-intel-deploy-section-title">BOUNDARY / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
${renderCheck('allowed_states', allowedStates.join(' / ') || 'missing', allowedStates.length ? 'LOCKED' : 'BLOCK')}
|
||
${renderCheck('next_gate', contract.next_gate || 'missing', contract.next_gate === 'candidate_queue_review_decision_writer_cli_review' ? 'CLI REVIEW' : 'BLOCK')}
|
||
${renderCheck('api_side_effects', 'no preflight record / no approval record / no decision record / no match write / no review_state update / no scheduler', data.api_writes_preflight_record || data.api_writes_approval_record || data.api_writes_decision_record || data.api_writes_match_result || data.api_updates_review_state || data.api_writes_database || data.scheduler_attached ? 'BLOCK' : 'CLOSED')}
|
||
${steps.map((item, index) => renderCheck(`step_${index + 1}`, item, 'NEXT')).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
if (mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightInput && !mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightInput.value.trim() && data.sample_writer_review_decision_approval_writer_preflight_package) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightInput.value = JSON.stringify(data.sample_writer_review_decision_approval_writer_preflight_package, null, 2);
|
||
}
|
||
};
|
||
|
||
const loadMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflight = async () => {
|
||
if (!mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightMeta || !mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody) return;
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody.innerHTML = '<div class="market-intel-empty">讀取 MCP Decision Approval Writer Preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightMeta(data);
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody.innerHTML = `<div class="market-intel-empty">MCP Decision Approval Writer Preflight 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const reviewMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflight = async () => {
|
||
if (!mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightMeta || !mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody || !mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightInput.value || '{}');
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody.innerHTML = '<div class="market-intel-empty">審核 MCP Decision Approval Writer Preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ writer_review_decision_approval_writer_preflight_package: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightMeta(data);
|
||
renderMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody(data);
|
||
} catch (error) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightBody.innerHTML = `<div class="market-intel-empty">MCP Decision Approval Writer Preflight 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderManualSampleMeta = data => {
|
||
manualSampleMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_manual_sample_fetch ? 'yes' : 'no'}`,
|
||
`platforms=${data.platform_count || 0}`,
|
||
`sources=${data.sample_source_total || 0}`,
|
||
`network=${data.external_network_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderManualSampleBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const platforms = data.sample_platforms || [];
|
||
const sequence = data.operator_sequence || [];
|
||
const fallback = data.fallback_plan || [];
|
||
const boundaries = data.safe_boundaries || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderNamedItem = (item, status) => {
|
||
const normalized = typeof item === 'string' ? { key: item, label: item } : (item || {});
|
||
return `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(normalized.key || normalized.label || 'item')}</strong>
|
||
<small>${escapeHtml(normalized.label || normalized.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(normalized.status || status || 'required').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
};
|
||
const renderPlatform = item => {
|
||
const firstSource = (item.selected_sources || [])[0] || {};
|
||
return `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.priority)}. ${escapeHtml(item.platform_code)}</strong>
|
||
<small>${escapeHtml(firstSource.name || item.platform_name)} / interval=${escapeHtml(item.request_interval_sec)}s / timeout=${escapeHtml(item.timeout_sec)}s</small>
|
||
</div>
|
||
<span>${escapeHtml(item.network_status || 'not_executed').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
};
|
||
manualSampleBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只規劃第一次人工樣本 fetch;API/UI 不抓外站、不寫 DB、不建立 crawler run、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-manual-sample-checks>
|
||
<p class="market-intel-deploy-section-title">SAMPLE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 sample gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-manual-sample-platforms>
|
||
<p class="market-intel-deploy-section-title">SAMPLE PLATFORMS</p>
|
||
<div class="market-intel-check-list">${
|
||
platforms.length
|
||
? platforms.map(renderPlatform).join('')
|
||
: '<div class="market-intel-empty">尚未提供平台樣本。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-manual-sample-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map(item => renderNamedItem(item, 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供人工順序。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-manual-sample-fallback>
|
||
<p class="market-intel-deploy-section-title">FALLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
fallback.length
|
||
? fallback.map(item => renderNamedItem(item, 'ready')).join('')
|
||
: '<div class="market-intel-empty">尚未提供備援方案。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-manual-sample-boundaries>
|
||
<p class="market-intel-deploy-section-title">BOUNDARIES</p>
|
||
<div class="market-intel-check-list">${
|
||
boundaries.length
|
||
? boundaries.map(item => renderNamedItem(item, 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供安全邊界。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadManualSample = async () => {
|
||
if (!manualSampleMeta || !manualSampleBody) return;
|
||
manualSampleBody.innerHTML = '<div class="market-intel-empty">讀取人工樣本 Fetch 計畫中...</div>';
|
||
try {
|
||
const response = await fetch(manualSampleEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderManualSampleMeta(data);
|
||
renderManualSampleBody(data);
|
||
} catch (error) {
|
||
manualSampleMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
manualSampleBody.innerHTML = `<div class="market-intel-empty">人工樣本 Fetch 計畫讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderSampleAcceptanceMeta = data => {
|
||
sampleAcceptanceMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`contract=${data.contract_ready ? 'ready' : 'blocked'}`,
|
||
`loaded=${data.sample_result_loaded ? 'yes' : 'no'}`,
|
||
`accepted=${data.sample_result_accepted ? 'yes' : 'no'}`,
|
||
`import=${data.candidate_import_allowed ? 'allow' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderSampleAcceptanceBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const acceptance = data.acceptance_checks || [];
|
||
const reject = data.reject_conditions || [];
|
||
const decisions = data.operator_decisions || [];
|
||
const sequence = data.promotion_sequence || [];
|
||
const boundaries = data.safe_boundaries || [];
|
||
const thresholds = data.acceptance_thresholds || {};
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderNamedItem = (item, status) => {
|
||
const normalized = typeof item === 'string' ? { key: item, label: item } : (item || {});
|
||
return `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(normalized.key || normalized.label || 'item')}</strong>
|
||
<small>${escapeHtml(normalized.label || normalized.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(normalized.status || status || 'required').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
};
|
||
sampleAcceptanceBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只定義 sample fetch 回來後的驗收契約;不載入 sample result、不抓外站、不寫 DB、不建立候選活動。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">required_fields=${escapeHtml((data.required_result_fields || []).length)} / diagnostics=${escapeHtml((data.required_diagnostic_fields || []).length)} / min_candidates=${escapeHtml(thresholds.minimum_campaign_candidates || 0)} / bands=${escapeHtml((thresholds.accepted_candidate_bands || []).join('/'))}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-sample-acceptance-checks>
|
||
<p class="market-intel-deploy-section-title">CONTRACT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 contract gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-fields>
|
||
<p class="market-intel-deploy-section-title">REQUIRED FIELDS</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.required_result_fields || []).map(item => renderNamedItem(String(item), 'required')).join('')
|
||
|| '<div class="market-intel-empty">尚未提供欄位契約。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-rules>
|
||
<p class="market-intel-deploy-section-title">ACCEPTANCE RULES</p>
|
||
<div class="market-intel-check-list">${
|
||
acceptance.length
|
||
? acceptance.map(item => renderNamedItem(item, 'not_evaluated')).join('')
|
||
: '<div class="market-intel-empty">尚未提供驗收規則。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-reject>
|
||
<p class="market-intel-deploy-section-title">REJECT CONDITIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
reject.length
|
||
? reject.map(item => renderNamedItem(item, 'reject')).join('')
|
||
: '<div class="market-intel-empty">尚未提供拒收條件。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-decisions>
|
||
<p class="market-intel-deploy-section-title">OPERATOR DECISIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
decisions.length
|
||
? decisions.map(item => renderNamedItem(item, item.write_status || 'blocked')).join('')
|
||
: '<div class="market-intel-empty">尚未提供人工決策。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-sequence>
|
||
<p class="market-intel-deploy-section-title">PROMOTION SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map(item => renderNamedItem(String(item), 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供升級順序。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-acceptance-boundaries>
|
||
<p class="market-intel-deploy-section-title">BOUNDARIES</p>
|
||
<div class="market-intel-check-list">${
|
||
boundaries.length
|
||
? boundaries.map(item => renderNamedItem(String(item), 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供安全邊界。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadSampleAcceptance = async () => {
|
||
if (!sampleAcceptanceMeta || !sampleAcceptanceBody) return;
|
||
sampleAcceptanceBody.innerHTML = '<div class="market-intel-empty">讀取樣本結果驗收契約中...</div>';
|
||
try {
|
||
const response = await fetch(sampleAcceptanceEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderSampleAcceptanceMeta(data);
|
||
renderSampleAcceptanceBody(data);
|
||
} catch (error) {
|
||
sampleAcceptanceMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleAcceptanceBody.innerHTML = `<div class="market-intel-empty">樣本結果驗收契約讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderSampleReviewMeta = data => {
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`loaded=${data.sample_result_loaded ? 'yes' : 'no'}`,
|
||
`reviewed=${data.sample_result_reviewed ? 'yes' : 'no'}`,
|
||
`result=${data.review_result || 'planned'}`,
|
||
`import=${data.candidate_import_allowed ? 'open' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderSampleReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = data.review_checks || [];
|
||
const findings = data.review_findings || [];
|
||
const candidates = data.candidate_summary || {};
|
||
const actions = data.operator_next_actions || [];
|
||
const boundaries = data.safe_boundaries || [];
|
||
const renderCheck = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || 'check')}</strong>
|
||
<small>${escapeHtml(item.label || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.status || 'planned').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
const renderNamedItem = (item, status) => {
|
||
const normalized = typeof item === 'string' ? { key: item, label: item } : (item || {});
|
||
return `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(normalized.key || normalized.label || 'item')}</strong>
|
||
<small>${escapeHtml(normalized.label || normalized.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(normalized.severity || normalized.write_status || status || 'blocked').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
};
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只用純函式審核人工 sample result;目前預設不載入結果、不抓外站、不存檔、不寫 DB、不建立候選活動。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">candidate_count=${escapeHtml(candidates.candidate_count || 0)} / accepted=${escapeHtml(candidates.accepted_candidate_count || 0)} / bands=${escapeHtml((candidates.accepted_candidate_bands || []).join('/'))}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-sample-review-checks>
|
||
<p class="market-intel-deploy-section-title">REVIEW CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未載入 sample result,審核檢查維持 planned。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-review-findings>
|
||
<p class="market-intel-deploy-section-title">FINDINGS</p>
|
||
<div class="market-intel-check-list">${
|
||
findings.length
|
||
? findings.map(item => renderNamedItem(item, 'block')).join('')
|
||
: '<div class="market-intel-empty">目前沒有審核發現;尚未載入結果時代表未評估。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-review-actions>
|
||
<p class="market-intel-deploy-section-title">NEXT ACTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
actions.length
|
||
? actions.map(item => renderNamedItem(item, item.write_status || 'blocked')).join('')
|
||
: '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-sample-review-boundaries>
|
||
<p class="market-intel-deploy-section-title">BOUNDARIES</p>
|
||
<div class="market-intel-check-list">${
|
||
boundaries.length
|
||
? boundaries.map(item => renderNamedItem(String(item), 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供安全邊界。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadSampleReview = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody) return;
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">讀取樣本結果審核預覽中...</div>';
|
||
try {
|
||
const response = await fetch(sampleReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderSampleReviewMeta(data);
|
||
renderSampleReviewBody(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">樣本結果審核預覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const evaluateSampleReview = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 sample result 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleReviewEvaluateEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderSampleReviewMeta(data);
|
||
renderSampleReviewBody(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">樣本結果審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateHandoff = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.handoff_summary || {};
|
||
const candidates = data.candidates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`handoff=${data.handoff_ready ? 'ready' : 'blocked'}`,
|
||
`candidates=${summary.candidate_count || 0}`,
|
||
`persisted=${data.candidate_handoff_persisted ? 'yes' : 'no'}`,
|
||
`import=${data.candidate_import_allowed ? 'open' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生候選活動 handoff preview;不保存 payload、不建立活動、不寫 market_*。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">${
|
||
candidates.length
|
||
? candidates.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.candidate_text || item.candidate_url)}</strong>
|
||
<small>${escapeHtml(item.platform_code)} / ${escapeHtml(item.confidence_band)} / score=${escapeHtml(item.score)} / ${escapeHtml(item.write_status)}</small>
|
||
<small>${escapeHtml(item.candidate_url)}</small>
|
||
</article>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">目前沒有可交接候選;請先讓 sample result 通過審核。</div>'
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateHandoff = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生候選 handoff 預覽中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateHandoffEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateHandoff(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">候選 handoff 產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueDraft = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.queue_summary || {};
|
||
const queueItems = data.queue_items || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`queue=${data.queue_draft_ready ? 'ready' : 'blocked'}`,
|
||
`items=${summary.queue_item_count || 0}`,
|
||
`persisted=${data.review_queue_persisted ? 'yes' : 'no'}`,
|
||
`import=${data.candidate_import_allowed ? 'open' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生候選審核 queue 草案;不建立正式 queue、不寫 DB、不自動核准候選。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">${
|
||
queueItems.length
|
||
? queueItems.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.candidate_text || item.candidate_url)}</strong>
|
||
<small>${escapeHtml(item.platform_code)} / ${escapeHtml(item.confidence_band)} / score=${escapeHtml(item.score)} / priority=${escapeHtml(item.review_priority)} / ${escapeHtml(item.review_state)}</small>
|
||
<small>${escapeHtml(item.write_status)} / approval=${item.approval_required ? 'required' : 'none'}</small>
|
||
<small>${escapeHtml(item.candidate_url)}</small>
|
||
</article>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">目前沒有可建立草案的候選;請先產生通過審核的 handoff。</div>'
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueDraft = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生候選審核 queue 草案中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueDraftEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueDraft(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">候選審核 queue 草案產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueApproval = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.approval_summary || {};
|
||
const gates = data.approval_gates || [];
|
||
const rows = (data.queue_write_preview || {}).rows || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`target=${summary.target_table || 'unknown'}`,
|
||
`rows=${summary.row_preview_count || 0}`,
|
||
`gates=${summary.gates_passed || 0}/${summary.gate_count || 0}`,
|
||
`write=${data.review_queue_write_allowed ? 'open' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只做 queue 寫入送審 gate;不建立 approval record、不寫 market_alert_review_queue、不開 DB transaction。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list mb-3">${
|
||
gates.length
|
||
? gates.map(item => renderNamedItem(item, item.passed ? 'pass' : 'block')).join('')
|
||
: '<div class="market-intel-empty">尚未產生 gate。</div>'
|
||
}</div>
|
||
<div class="market-intel-operation-list">${
|
||
rows.length
|
||
? rows.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.alert_candidate_id)}</strong>
|
||
<small>${escapeHtml(item.review_state)} / ${escapeHtml(item.priority_lane)} / ${escapeHtml(item.threshold_level)} / score=${escapeHtml(item.total_score)}</small>
|
||
<small>${escapeHtml(item.dedupe_key)} / ${escapeHtml(item.write_status)}</small>
|
||
</article>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">目前沒有可送審的 queue row preview。</div>'
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueApproval = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue 寫入送審 gate 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueApprovalEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueApproval(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue 寫入送審 gate 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueTransaction = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.transaction_summary || {};
|
||
const statements = data.statements || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`target=${summary.target_table || 'unknown'}`,
|
||
`statements=${summary.statement_count || 0}`,
|
||
`conflict=${summary.conflict_policy || 'none'}`,
|
||
`ready=${data.transaction_ready ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 transaction preview;不開 DB connection、不開 transaction、不 commit、不建立 approval record。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">${
|
||
statements.length
|
||
? statements.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.idempotency_key)}</strong>
|
||
<small>${escapeHtml(item.operation)} / ${escapeHtml(item.table)} / ${escapeHtml(item.write_status)}</small>
|
||
<small>${escapeHtml(item.sql_shape)}</small>
|
||
<small>payload=${escapeHtml(item.parameter_payload_hash)}</small>
|
||
</article>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">目前沒有可檢查的 transaction statement。</div>'
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueTransaction = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue transaction preview 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueTransactionEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueTransaction(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue transaction preview 產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueWriter = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.transaction_preview_summary || {};
|
||
const gates = data.approval_gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_real_write ? 'yes' : 'no'}`,
|
||
`statements=${summary.statement_count || 0}`,
|
||
`execute=${data.execute_requested ? 'yes' : 'no'}`,
|
||
`apply=${data.apply_real_write_requested ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 CLI writer gate;不讀取 token、不開 DB connection、不寫 queue。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueWriter = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue writer CLI gate 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueWriterEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueWriter(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer CLI gate 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueuePreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const columns = data.mapped_insert_columns || [];
|
||
const missing = data.missing_insert_columns || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`schema=${data.schema_ready ? 'ready' : 'blocked'}`,
|
||
`mapped=${columns.length}`,
|
||
`missing=${missing.length}`,
|
||
`dedupe=${data.dedupe_unique_index_present ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡檢查 payload 與 queue table 欄位相容性;頁面預設不連 DB、不寫 queue。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">
|
||
<article class="market-intel-operation">
|
||
<strong>mapped insert columns</strong>
|
||
<small>${escapeHtml(columns.join(', ') || 'none')}</small>
|
||
</article>
|
||
<article class="market-intel-operation">
|
||
<strong>missing columns</strong>
|
||
<small>${escapeHtml(missing.join(', ') || 'none')}</small>
|
||
</article>
|
||
<article class="market-intel-operation">
|
||
<strong>unmapped payload keys</strong>
|
||
<small>${escapeHtml((data.unmapped_payload_keys || []).join(', ') || 'none')}</small>
|
||
</article>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueuePreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue writer preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueuePreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueuePreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer preflight 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueuePostwriteSmoke = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const found = data.found_dedupe_keys || [];
|
||
const missing = data.missing_dedupe_keys || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`passed=${data.postwrite_smoke_passed ? 'yes' : 'no'}`,
|
||
`expected=${data.expected_dedupe_key_count || 0}`,
|
||
`found=${data.found_count || 0}`,
|
||
`missing=${data.missing_count || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只做 CLI 寫入後的 queue row 只讀驗證;頁面預設不連 DB、不寫 queue。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-operation-list">
|
||
<article class="market-intel-operation">
|
||
<strong>found dedupe keys</strong>
|
||
<small>${escapeHtml(found.join(', ') || 'none')}</small>
|
||
</article>
|
||
<article class="market-intel-operation">
|
||
<strong>missing dedupe keys</strong>
|
||
<small>${escapeHtml(missing.join(', ') || 'none')}</small>
|
||
</article>
|
||
<article class="market-intel-operation">
|
||
<strong>read-only status</strong>
|
||
<small>query=${data.read_only_query_executed ? 'yes' : 'no'} / write=${data.database_write_executed ? 'yes' : 'no'} / commit=${data.database_commit_executed ? 'yes' : 'no'}</small>
|
||
</article>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueuePostwriteSmoke = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue writer post-write smoke 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueuePostwriteSmokeEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueuePostwriteSmoke(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer post-write smoke 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueOperatorDrill = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const commands = data.command_sequence || [];
|
||
const gates = data.gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.operator_drill_ready ? 'yes' : 'no'}`,
|
||
`commands=${commands.length}`,
|
||
`api_cli=${data.api_executes_cli ? 'yes' : 'no'}`,
|
||
`db_write=${data.database_write_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生操作員演練順序;API/UI 不讀 token、不執行 CLI、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list mb-3">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
<div class="market-intel-operation-list">${
|
||
commands.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.step)}. ${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>${escapeHtml(item.command_shape)}</small>
|
||
<small>db_write=${item.executes_database ? 'yes' : 'no'}</small>
|
||
</article>
|
||
`).join('')
|
||
}</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueOperatorDrill = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue writer operator drill 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueOperatorDrillEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueOperatorDrill(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer operator drill 產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const manifest = data.payload_manifest || {};
|
||
const artifacts = data.required_artifacts || [];
|
||
const commands = data.command_bundle || [];
|
||
const signoff = data.operator_signoff || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.package_ready ? 'yes' : 'no'}`,
|
||
`payloads=${manifest.payload_count || 0}`,
|
||
`api_file=${data.api_writes_file ? 'yes' : 'no'}`,
|
||
`db_write=${data.database_write_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理正式 CLI 小流量寫入前的證據包與命令包;API/UI 不產檔、不讀 token、不執行 CLI、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">manifest=${escapeHtml((manifest.manifest_hash || '').slice(0, 16) || 'none')} / dedupe=${escapeHtml((manifest.dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REQUIRED ARTIFACTS</p>
|
||
<div class="market-intel-operation-list">${
|
||
artifacts.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>${escapeHtml(item.path_shape)}</small>
|
||
<small>created_by_api=${item.created_by_api ? 'yes' : 'no'}</small>
|
||
</article>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 artifacts。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND BUNDLE</p>
|
||
<div class="market-intel-operation-list">${
|
||
commands.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.step)}. ${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.command_shape)}</small>
|
||
<small>db_write=${item.executes_database ? 'yes' : 'no'}</small>
|
||
</article>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 commands。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SIGNOFF</p>
|
||
<div class="market-intel-check-list">${
|
||
signoff.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`signoff_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 signoff。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue writer run package 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueRunPackageEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify({ sample_result: parsed })
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer run package 產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const evidence = data.operator_evidence_summary || {};
|
||
const packageSummary = data.run_package_summary || {};
|
||
const gates = data.gates || [];
|
||
const steps = data.next_operator_steps || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`cli_ready=${data.ready_for_cli_operator_run ? 'yes' : 'no'}`,
|
||
`api_write=${data.ready_for_api_database_write ? 'yes' : 'no'}`,
|
||
`payloads=${packageSummary.payload_count || 0}`,
|
||
`paths=${evidence.artifact_path_count || 0}`,
|
||
`token_api=${evidence.approval_token_submitted_to_api ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查正式 CLI 小流量寫入前的操作員證據;API/UI 不讀 token、不執行 CLI、不產檔、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">manifest=${escapeHtml((packageSummary.manifest_hash || '').slice(0, 16) || 'none')} / artifacts=${escapeHtml(packageSummary.required_artifact_count || 0)} / commands=${escapeHtml(packageSummary.command_count || 0)}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 readiness gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['reviewed_sample_json_path_recorded', evidence.reviewed_sample_json_path_recorded],
|
||
['backup_artifact_path_recorded', evidence.backup_artifact_path_recorded],
|
||
['preflight_artifact_path_recorded', evidence.preflight_artifact_path_recorded],
|
||
['migration_live_smoke_passed', evidence.migration_live_smoke_passed],
|
||
['operator_acknowledged_shell_only_token', evidence.operator_acknowledged_shell_only_token],
|
||
['approval_token_submitted_to_api', evidence.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
</div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NEXT STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>MANUAL</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
const body = parsed && parsed.sample_result
|
||
? parsed
|
||
: { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue writer run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueRunReadinessEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer run readiness 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const writer = data.writer_output_summary || {};
|
||
const smoke = data.postwrite_smoke_summary || {};
|
||
const evidence = data.operator_evidence_summary || {};
|
||
const gates = data.gates || [];
|
||
const steps = data.next_operator_steps || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.receipt_passed ? 'pass' : 'blocked'}`,
|
||
`writer=${writer.mode || 'missing'}`,
|
||
`smoke=${smoke.postwrite_smoke_passed ? 'pass' : 'blocked'}`,
|
||
`api_write=${data.ready_for_api_database_write ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 CLI 寫入後的 writer output 與 post-write smoke receipt;API/UI 不讀 token、不執行 CLI、不連 DB、不補寫 queue。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">expected=${escapeHtml((data.expected_dedupe_keys || []).join(', ') || 'none')} / observed=${escapeHtml((writer.observed_dedupe_keys || []).join(', ') || 'none')} / found=${escapeHtml((smoke.found_dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 receipt gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER / SMOKE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['writer_committed', writer.database_commit_executed],
|
||
['writer_dedupe_match', writer.dedupe_keys_match_expected],
|
||
['writer_token_key_detected', writer.approval_token_key_detected],
|
||
['smoke_read_only', smoke.read_only_query_executed],
|
||
['smoke_passed', smoke.postwrite_smoke_passed],
|
||
['smoke_dedupe_match', smoke.dedupe_keys_match_expected]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
</div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['writer_output_json_path_recorded', evidence.writer_output_json_path_recorded],
|
||
['postwrite_smoke_json_path_recorded', evidence.postwrite_smoke_json_path_recorded],
|
||
['operator_confirmed_no_token_in_artifacts', evidence.operator_confirmed_no_token_in_artifacts],
|
||
['approval_token_submitted_to_api', evidence.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
</div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NEXT STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>MANUAL</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
const body = parsed && parsed.sample_result
|
||
? parsed
|
||
: { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue writer run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueRunReceiptEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer run receipt 審核失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueRunCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const receipt = data.receipt_summary || {};
|
||
const closeout = data.operator_closeout_summary || {};
|
||
const promotion = data.promotion_gate || {};
|
||
const gates = data.gates || [];
|
||
const steps = data.next_operator_steps || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.closeout_passed ? 'pass' : 'blocked'}`,
|
||
`receipt=${receipt.receipt_passed ? 'pass' : 'blocked'}`,
|
||
`next=${promotion.allowed ? 'manual' : 'hold'}`,
|
||
`api_write=${data.ready_for_api_database_write ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只做 run receipt 後的 closeout gate;通過後也只代表可進人工 queue review / read-only inventory,不代表 API/UI 可寫 DB 或掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['receipt_passed', receipt.receipt_passed],
|
||
['dedupe_keys', receipt.expected_dedupe_key_count || 0],
|
||
['writer_match', receipt.writer_dedupe_keys_match_expected],
|
||
['smoke_match', receipt.postwrite_smoke_dedupe_keys_match_expected],
|
||
['safe_boundaries_complete', receipt.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${typeof value === 'boolean' ? (value ? 'YES' : 'NO') : escapeHtml(value)}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_artifact_path_recorded', closeout.closeout_artifact_path_recorded],
|
||
['operator_confirmed_queue_review_next', closeout.operator_confirmed_queue_review_next],
|
||
['operator_confirmed_no_scheduler_attach', closeout.operator_confirmed_no_scheduler_attach],
|
||
['operator_confirmed_no_api_db_write', closeout.operator_confirmed_no_api_db_write],
|
||
['approval_token_submitted_to_api', closeout.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION / NEXT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(promotion.next_manual_phase || 'manual_phase')}</strong>
|
||
<small>requires_operator_approval=${promotion.requires_operator_approval ? 'yes' : 'no'}</small>
|
||
</div>
|
||
<span>${promotion.allowed ? 'ALLOW' : 'HOLD'}</span>
|
||
</div>
|
||
${steps.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>MANUAL</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueRunCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
const body = parsed && parsed.sample_result
|
||
? parsed
|
||
: { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">收尾 queue writer run closeout 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueRunCloseoutEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueRunCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue writer run closeout 收尾失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewHandoff = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const closeout = data.closeout_summary || {};
|
||
const operator = data.operator_handoff_summary || {};
|
||
const contract = data.review_contract || {};
|
||
const gates = data.gates || [];
|
||
const steps = data.next_operator_steps || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`handoff=${data.handoff_ready ? 'ready' : 'blocked'}`,
|
||
`closeout=${closeout.closeout_passed ? 'pass' : 'blocked'}`,
|
||
`expected=${(data.expected_dedupe_keys || []).length}`,
|
||
`api_write=${data.ready_for_api_database_write ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生人工 queue review handoff;不查 DB、不更新 review_state、不補寫 queue、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">expected dedupe keys:${escapeHtml((data.expected_dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">HANDOFF GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 handoff gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REVIEW CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>expected_review_state</strong>
|
||
<small>${escapeHtml(contract.expected_review_state || 'needs_review')}</small>
|
||
</div>
|
||
<span>MANUAL</span>
|
||
</div>
|
||
${(contract.required_columns || []).map(item => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(item)}</strong></div>
|
||
<span>FIELD</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['operator_confirmed_queue_review_next', operator.operator_confirmed_queue_review_next],
|
||
['operator_confirmed_no_scheduler_attach', operator.operator_confirmed_no_scheduler_attach],
|
||
['operator_confirmed_no_api_db_write', operator.operator_confirmed_no_api_db_write],
|
||
['approval_token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NEXT STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>MANUAL</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewHandoff = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
|
||
const body = parsed && parsed.sample_result
|
||
? parsed
|
||
: { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review handoff 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewHandoffEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewHandoff(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review handoff 產生失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewInventory = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const operator = data.operator_inventory_summary || {};
|
||
const table = data.alert_review_queue_table || {};
|
||
const smoke = data.postwrite_smoke_summary || {};
|
||
const live = data.live_inventory_summary || {};
|
||
const gates = data.gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`inventory=${data.review_inventory_ready ? 'ready' : 'blocked'}`,
|
||
`read_only=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`found=${(data.found_dedupe_keys || []).length}`,
|
||
`api_update=${data.api_updates_review_state ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只做 queue review inventory 只讀交接檢查;不更新 review_state、不補寫 queue、不讀 token、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">expected:${escapeHtml((data.expected_dedupe_keys || []).join(', ') || 'none')} / found:${escapeHtml((data.found_dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">INVENTORY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 inventory gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">QUEUE TABLE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['table_exists', table.exists],
|
||
['row_count', table.row_count ?? 'n/a'],
|
||
['table_status', table.status || 'not_reported'],
|
||
['postwrite_mode', smoke.mode || 'planned'],
|
||
['smoke_passed', smoke.postwrite_smoke_passed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">LIVE INVENTORY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['live_mode', live.mode || 'planned'],
|
||
['summary_ready', live.summary_ready],
|
||
['total_rows', live.total_rows ?? 'n/a'],
|
||
['operator_read_only', operator.operator_confirmed_inventory_read_only],
|
||
['approval_token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ROWS</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.row_summaries || []).map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.priority_lane || 'n/a')} / score=${escapeHtml(String(row.total_score ?? 'n/a'))}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.review_state || 'unknown')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未執行只讀 row inventory。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewInventory = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review inventory 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewInventoryEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewInventory(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review inventory 檢查失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecision = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const operator = data.operator_decision_summary || {};
|
||
const contract = data.decision_contract || {};
|
||
const gates = data.gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`decision=${data.decision_ready ? 'ready' : 'blocked'}`,
|
||
`proposed=${operator.proposed_review_decision || 'none'}`,
|
||
`rows=${(data.decision_rows || []).length}`,
|
||
`api_update=${data.api_updates_review_state ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生人工 queue review decision 草案;不更新 review_state、不寫 decision record、不讀 token、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">DECISION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 decision gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div><strong>expected_current_state</strong></div>
|
||
<span>${escapeHtml(contract.expected_current_state || 'needs_review')}</span>
|
||
</div>
|
||
${(contract.allowed_next_states || []).map(item => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(item)}</strong></div>
|
||
<span>ALLOW</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['reviewer_id', operator.reviewer_id || 'missing'],
|
||
['decision_notes_present', operator.decision_notes_present],
|
||
['manual_decision_only', operator.operator_confirmed_manual_decision_only],
|
||
['approval_token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">DECISION ROWS</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.decision_rows || []).map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.current_review_state || 'unknown')} -> ${escapeHtml(row.proposed_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.write_status || 'preview')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 decision rows。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecision = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review decision 草案中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecision(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision 草案失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionApproval = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const operator = data.operator_approval_summary || {};
|
||
const summary = data.approval_summary || {};
|
||
const contract = data.approval_contract || {};
|
||
const gates = data.approval_gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`approval=${data.approval_ready ? 'ready' : 'blocked'}`,
|
||
`rows=${(data.decision_update_preview || []).length}`,
|
||
`api_update=${data.api_updates_review_state ? 'yes' : 'no'}`,
|
||
`cli_ready=${data.ready_for_cli_decision_writer ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 queue review decision 批准 gate;不更新 review_state、不寫 decision record、不讀 token、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">APPROVAL GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 approval gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">APPROVAL SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', summary.target_table || data.target_table || 'unknown'],
|
||
['row_preview_count', summary.row_preview_count ?? 0],
|
||
['manual_required', summary.manual_approval_required],
|
||
['api_write_allowed', summary.api_write_allowed],
|
||
['next_stage', contract.next_stage || 'not_ready']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['payload_reviewed', operator.operator_confirmed_decision_payload_reviewed],
|
||
['cli_only_apply', operator.operator_confirmed_decision_apply_requires_cli],
|
||
['not_api_update', operator.operator_confirmed_review_state_update_is_not_api],
|
||
['notes_present', operator.decision_approval_notes_present],
|
||
['approval_token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">UPDATE PREVIEW</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.decision_update_preview || []).map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.expected_current_review_state || 'unknown')} -> ${escapeHtml(row.approved_next_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.write_status || 'preview')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 update preview。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionApproval = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision 批准 gate 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionApprovalEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionApproval(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision 批准 gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionTransaction = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const operator = data.operator_transaction_summary || {};
|
||
const summary = data.transaction_preview_summary || {};
|
||
const contract = data.transaction_contract || {};
|
||
const gates = data.transaction_gates || [];
|
||
const statements = data.statements || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`transaction=${data.transaction_ready ? 'ready' : 'blocked'}`,
|
||
`statements=${summary.statement_count || 0}`,
|
||
`api_update=${data.api_updates_review_state ? 'yes' : 'no'}`,
|
||
`cli_required=${summary.manual_cli_required ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 queue review decision transaction preview;不更新 review_state、不開 DB connection、不讀 token、不執行 CLI、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">TRANSACTION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 transaction gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', summary.target_table || data.target_table || 'unknown'],
|
||
['expected_current_state', contract.expected_current_state || 'needs_review'],
|
||
['next_stage', contract.next_stage || 'not_ready'],
|
||
['api_write_allowed', summary.api_write_allowed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['payload_reviewed', operator.operator_confirmed_transaction_payload_reviewed],
|
||
['cli_only_transaction', operator.operator_confirmed_cli_only_transaction],
|
||
['not_api_update', operator.operator_confirmed_review_state_update_is_not_api],
|
||
['notes_present', operator.decision_transaction_notes_present],
|
||
['approval_token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">STATEMENTS</p>
|
||
<div class="market-intel-check-list">${
|
||
statements.map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.idempotency_key || row.lookup?.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.expected_current_review_state || 'unknown')} -> ${escapeHtml(row.next_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.write_status || 'preview')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 transaction statements。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionTransaction = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review decision transaction preview 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionTransactionEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionTransaction(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision transaction preview 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionPreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.statement_summary || {};
|
||
const payloads = summary.review_state_updates || [];
|
||
const contract = data.update_contract || {};
|
||
const catalog = data.catalog_probe_plan || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`payload=${data.preflight_payload_ready ? 'ready' : 'blocked'}`,
|
||
`statements=${summary.statement_count || 0}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`db_write=${data.database_write_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 review_state writer preflight;API/UI 不讀 token、不連 DB、不更新 review_state、不 commit、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PREFLIGHT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.preflight_gates || []).map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 preflight gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', contract.target_table || data.target_table || 'unknown'],
|
||
['operation', contract.statement_type || data.target_operation || 'unknown'],
|
||
['lookup', contract.lookup || 'dedupe_key'],
|
||
['current_state', contract.expected_current_review_state || 'needs_review'],
|
||
['allowed_next_states', (contract.allowed_next_states || []).join(', ') || 'none']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CATALOG PLAN</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['mode', catalog.mode || 'planned_only'],
|
||
['required_columns', (catalog.required_columns || []).join(', ') || 'none'],
|
||
['unique_lookup', catalog.required_unique_lookup || 'dedupe_key'],
|
||
['connection_opened', catalog.database_connection_opened],
|
||
['query_executed', catalog.read_only_query_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PAYLOAD</p>
|
||
<div class="market-intel-check-list">${
|
||
payloads.map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || row.idempotency_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.expected_current_review_state || 'unknown')} -> ${escapeHtml(row.next_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.operation || 'update')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 review_state update payload。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE FLAGS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_updates_review_state', data.api_updates_review_state],
|
||
['review_state_update_executed', data.review_state_update_executed],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionPreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision writer preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionPreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionPreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer preflight 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionPostwriteSmoke = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const rows = data.row_summaries || [];
|
||
const mismatches = data.state_mismatches || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`passed=${data.postwrite_smoke_passed ? 'yes' : 'no'}`,
|
||
`expected=${data.expected_dedupe_key_count || 0}`,
|
||
`found=${data.found_count || 0}`,
|
||
`mismatch=${data.state_mismatch_count || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只做 review_state writer post-write 只讀驗證;API/UI 不讀 token、不更新 review_state、不 commit、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">EXPECTED UPDATES</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.expected_review_state_updates || []).map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(item.expected_current_review_state || 'unknown')} -> ${escapeHtml(item.expected_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.parameter_payload_hash || 'preview')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 review_state update payload。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READ-ONLY RESULT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['read_only_query', data.read_only_query_executed],
|
||
['db_connection', data.database_connection_opened],
|
||
['api_updates_review_state', data.api_updates_review_state],
|
||
['review_state_update_executed', data.review_state_update_executed],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="market-intel-deploy-grid mt-3">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ROWS</p>
|
||
<div class="market-intel-check-list">${
|
||
rows.map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.review_state || 'unknown')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.updated_at || 'pending')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未執行只讀查詢或查無 row。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MISMATCH</p>
|
||
<div class="market-intel-check-list">${
|
||
mismatches.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(item.expected_review_state || 'none')} / ${escapeHtml(item.actual_review_state || 'none')}</small>
|
||
</div>
|
||
<span>CHECK</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">目前沒有 state mismatch。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionPostwriteSmoke = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision writer post-write smoke 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionPostwriteSmokeEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionPostwriteSmoke(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer post-write smoke 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionOperatorDrill = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const commands = data.command_sequence || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.operator_drill_ready ? 'yes' : 'no'}`,
|
||
`statements=${(data.statement_summary || {}).statement_count || 0}`,
|
||
`api_write=${data.api_updates_review_state ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 review_state writer 的人工操作 drill;API/UI 不讀 token、不執行 CLI、不更新 review_state。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 drill gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND ORDER</p>
|
||
<div class="market-intel-check-list">${
|
||
commands.map(command => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`${command.step}. ${command.key}`)}</strong>
|
||
<small>${escapeHtml(command.command_shape || '')}</small>
|
||
</div>
|
||
<span>${command.executes_database ? 'DB' : 'NO DB'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生命令順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
<div class="market-intel-deploy-grid mt-3">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">INPUTS</p>
|
||
<div class="market-intel-check-list">${
|
||
Object.entries(data.input_summaries || {}).map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供輸入摘要。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE FLAGS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_token', data.api_reads_approval_token],
|
||
['api_updates_review_state', data.api_updates_review_state],
|
||
['review_state_update_executed', data.review_state_update_executed],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionOperatorDrill = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review decision writer operator drill 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionOperatorDrillEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionOperatorDrill(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer operator drill 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.package_gates || [];
|
||
const manifest = data.payload_manifest || {};
|
||
const artifacts = data.required_artifacts || [];
|
||
const commands = data.command_bundle || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`package=${data.package_ready ? 'ready' : 'blocked'}`,
|
||
`payloads=${manifest.payload_count || 0}`,
|
||
`artifact=${data.package_artifact_created ? 'created' : 'preview'}`,
|
||
`api_write=${data.api_updates_review_state ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 review_state writer run package 預覽;API/UI 不寫檔、不讀 token、不執行 CLI、不更新 review_state。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PAYLOAD MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['payload_count', manifest.payload_count || 0],
|
||
['manifest_hash', manifest.manifest_hash || 'none'],
|
||
['dedupe_keys', (manifest.dedupe_keys || []).join(', ') || 'none'],
|
||
['allowed_next_states', (manifest.allowed_next_states || []).join(', ') || 'none']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="market-intel-deploy-grid mt-3">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARTIFACTS</p>
|
||
<div class="market-intel-check-list">${
|
||
artifacts.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.path_shape || '')}</small>
|
||
</div>
|
||
<span>${item.created_by_api ? 'API' : 'MANUAL'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 artifact 清單。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMANDS</p>
|
||
<div class="market-intel-check-list">${
|
||
commands.map(command => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`${command.step}. ${command.key}`)}</strong>
|
||
<small>${escapeHtml(command.command_shape || '')}</small>
|
||
</div>
|
||
<span>${command.executes_database ? 'DB' : 'NO DB'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生命令 bundle。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE FLAGS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_writes_file', data.api_writes_file],
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_token', data.api_reads_approval_token],
|
||
['api_updates_review_state', data.api_updates_review_state],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review decision writer run package 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionRunPackageEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const evidence = data.operator_evidence_summary || {};
|
||
const packageSummary = data.run_package_summary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`cli_ready=${data.ready_for_cli_operator_run ? 'yes' : 'no'}`,
|
||
`api_update=${data.ready_for_api_review_state_update ? 'yes' : 'no'}`,
|
||
`payloads=${packageSummary.payload_count || 0}`,
|
||
`paths=${evidence.artifact_path_count || 0}`,
|
||
`token_api=${evidence.approval_token_submitted_to_api ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 review_state CLI 更新前的操作員證據;API/UI 不讀 token、不執行 CLI、不產檔、不更新 review_state。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">manifest=${escapeHtml((packageSummary.manifest_hash || '').slice(0, 16) || 'none')} / artifacts=${escapeHtml(packageSummary.required_artifact_count || 0)} / commands=${escapeHtml(packageSummary.command_count || 0)}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 readiness gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['review_state_transaction_json_path_recorded', evidence.review_state_transaction_json_path_recorded],
|
||
['backup_artifact_path_recorded', evidence.backup_artifact_path_recorded],
|
||
['preflight_artifact_path_recorded', evidence.preflight_artifact_path_recorded],
|
||
['preflight_only', evidence.operator_confirmed_review_state_preflight_only],
|
||
['shell_only_token', evidence.operator_acknowledged_shell_only_token],
|
||
['approval_token_submitted_to_api', evidence.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE FLAGS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_token', data.api_reads_approval_token],
|
||
['api_updates_review_state', data.api_updates_review_state],
|
||
['review_state_update_executed', data.review_state_update_executed],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision writer run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionRunReadinessEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer run readiness 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const writer = data.writer_output_summary || {};
|
||
const smoke = data.postwrite_smoke_summary || {};
|
||
const evidence = data.operator_evidence_summary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.receipt_passed ? 'pass' : 'blocked'}`,
|
||
`writer=${writer.mode || 'missing'}`,
|
||
`smoke=${smoke.postwrite_smoke_passed ? 'pass' : 'blocked'}`,
|
||
`api_update=${data.ready_for_api_review_state_update ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 review_state CLI 更新後的 writer output 與 post-write smoke receipt;API/UI 不讀 token、不執行 CLI、不連 DB、不補更新 review_state。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">expected=${escapeHtml((data.expected_dedupe_keys || []).join(', ') || 'none')} / writer=${escapeHtml((writer.observed_dedupe_keys || []).join(', ') || 'none')} / smoke=${escapeHtml((smoke.found_dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER / SMOKE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['writer_committed', writer.database_commit_executed],
|
||
['writer_review_state_update', writer.review_state_update_executed],
|
||
['writer_dedupe_match', writer.dedupe_keys_match_expected],
|
||
['writer_token_key_detected', writer.approval_token_key_detected],
|
||
['smoke_read_only', smoke.read_only_query_executed],
|
||
['smoke_verified', smoke.review_state_update_verified],
|
||
['smoke_state_mismatch', smoke.state_mismatch_count || 0]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['writer_output_json_path_recorded', evidence.writer_output_json_path_recorded],
|
||
['postwrite_smoke_json_path_recorded', evidence.postwrite_smoke_json_path_recorded],
|
||
['operator_confirmed_no_token_in_artifacts', evidence.operator_confirmed_no_token_in_artifacts],
|
||
['approval_token_submitted_to_api', evidence.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review decision writer run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionRunReceiptEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer run receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionRunCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.receipt_summary || {};
|
||
const closeout = data.operator_closeout_summary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.closeout_passed ? 'pass' : 'blocked'}`,
|
||
`receipt=${receipt.receipt_passed ? 'pass' : 'blocked'}`,
|
||
`updates=${receipt.expected_review_state_update_count || 0}`,
|
||
`api_update=${data.ready_for_api_review_state_update ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只收尾 review_state writer run closeout;API/UI 不讀 token、不執行 CLI、不連 DB、不更新 review_state、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_operation', receipt.target_operation || 'unknown'],
|
||
['receipt_passed', receipt.receipt_passed],
|
||
['safe_boundaries_complete', receipt.safe_boundaries_complete],
|
||
['writer_review_state_update', receipt.writer_review_state_update_executed],
|
||
['smoke_review_state_verified', receipt.postwrite_smoke_review_state_verified],
|
||
['api_updates_review_state', receipt.api_updates_review_state]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_artifact_path_recorded', closeout.closeout_artifact_path_recorded],
|
||
['review_state_closeout_next', closeout.operator_confirmed_review_state_closeout_next],
|
||
['inventory_read_only', closeout.operator_confirmed_post_closeout_inventory_read_only],
|
||
['no_scheduler_attach', closeout.operator_confirmed_no_scheduler_attach],
|
||
['no_api_db_write', closeout.operator_confirmed_no_api_db_write],
|
||
['approval_token_submitted_to_api', closeout.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${value ? 'YES' : 'NO'}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionRunCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">收尾 queue review decision writer run closeout 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionRunCloseoutEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionRunCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer run closeout 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionPostCloseoutInventory = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.closeout_summary || {};
|
||
const smoke = data.postwrite_smoke_summary || {};
|
||
const table = data.alert_review_queue_table || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`inventory=${data.post_closeout_inventory_ready ? 'pass' : 'blocked'}`,
|
||
`closeout=${closeout.closeout_passed ? 'pass' : 'blocked'}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`api_update=${data.ready_for_api_review_state_update ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 review_state closeout 後的只讀 inventory / smoke;API/UI 不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">expected=${escapeHtml((data.expected_dedupe_keys || []).join(', ') || 'none')} / found=${escapeHtml((data.found_dedupe_keys || []).join(', ') || 'none')} / missing=${escapeHtml((data.missing_dedupe_keys || []).join(', ') || 'none')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">INVENTORY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 inventory gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READ-ONLY RESULT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['table_exists', table.exists],
|
||
['table_rows', table.row_count == null ? 'n/a' : table.row_count],
|
||
['smoke_passed', smoke.postwrite_smoke_passed],
|
||
['review_state_verified', smoke.review_state_update_verified],
|
||
['state_mismatch_count', smoke.state_mismatch_count || 0],
|
||
['db_write', data.database_write_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ROW SNAPSHOT</p>
|
||
<div class="market-intel-check-list">${
|
||
(data.row_summaries || []).slice(0, 6).map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.review_state || 'unknown')} / score=${escapeHtml(row.total_score == null ? 'n/a' : row.total_score)}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.priority_lane || 'ROW')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未載入 row snapshot。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionPostCloseoutInventory = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision post-closeout inventory 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionPostCloseoutInventoryEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionPostCloseoutInventory(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision post-closeout inventory 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewCompletionArchive = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const manifest = data.archive_manifest || {};
|
||
const inventory = manifest.inventory || {};
|
||
const artifactPaths = manifest.artifact_paths || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`archive=${data.review_completion_archive_ready ? 'pass' : 'blocked'}`,
|
||
`manifest=${data.archive_manifest_ready ? 'ready' : 'draft'}`,
|
||
`rows=${data.row_summary_count || 0}`,
|
||
`api_write=${data.ready_for_api_database_write ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 review completion archive manifest;API/UI 不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['expected_keys', inventory.expected_dedupe_key_count || 0],
|
||
['found_keys', inventory.found_dedupe_key_count || 0],
|
||
['missing_keys', inventory.missing_dedupe_key_count || 0],
|
||
['row_snapshot', inventory.row_summary_count || 0],
|
||
['state_mismatch', inventory.state_mismatch_count || 0],
|
||
['archive_file_written', data.archive_file_written]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARTIFACT PATHS</p>
|
||
<div class="market-intel-check-list">
|
||
${Object.entries(artifactPaths).map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(value || 'missing')}</small>
|
||
</div>
|
||
<span>${value ? 'SET' : 'MISS'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 artifact path。</div>'}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewCompletionArchive = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review completion archive 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewCompletionArchiveEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewCompletionArchive(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review completion archive 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewArchiveSummary = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const sections = data.summary_sections || [];
|
||
const artifactPaths = data.artifact_paths || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`summary=${data.archive_summary_ready ? 'ready' : 'blocked'}`,
|
||
`llm=${data.llm_call_executed ? 'called' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`,
|
||
`rows=${data.row_summary_count || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 archive summary input;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 summary gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(section => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>INPUT</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 summary section。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['ai_summary_generated', data.ai_summary_generated],
|
||
['llm_call_executed', data.llm_call_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['summary_file_written', data.summary_file_written],
|
||
['database_write_executed', data.database_write_executed],
|
||
['archive_summary_path', artifactPaths.archive_summary_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewArchiveSummary = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review archive summary 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewArchiveSummaryEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewArchiveSummary(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review archive summary 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const routePolicy = data.model_route_policy || {};
|
||
const cascade = routePolicy.primary_cascade || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`preflight=${data.ai_summary_preflight_ready ? 'ready' : 'blocked'}`,
|
||
`manual_ollama=${data.ready_for_manual_ollama_summary_run ? 'ready' : 'blocked'}`,
|
||
`llm=${data.llm_call_executed ? 'called' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 AI summary preflight;後續摘要必須 Ollama-first,Gemini 只能在 Ollama cascade 全失敗後備援。API/UI 不呼叫模型、不派送 Telegram、不寫檔、不讀 token、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PREFLIGHT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 preflight gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MODEL ROUTE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['primary_policy', routePolicy.primary_policy || 'missing'],
|
||
['fallback_policy', routePolicy.fallback_policy || 'missing'],
|
||
['forbidden_node', routePolicy.app_host_forbidden_as_ollama_node || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
${cascade.map(node => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(node.label || node.key || 'ollama')}</strong>
|
||
<small>${escapeHtml(node.host || '')}</small>
|
||
</div>
|
||
<span>OLLAMA</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['ready_for_ai_summary_generation', data.ready_for_ai_summary_generation],
|
||
['ready_for_llm_call', data.ready_for_llm_call],
|
||
['ready_for_telegram_dispatch', data.ready_for_telegram_dispatch],
|
||
['ai_summary_generated', data.ai_summary_generated],
|
||
['llm_call_executed', data.llm_call_executed],
|
||
['ollama_call_executed', data.ollama_call_executed],
|
||
['gemini_call_executed', data.gemini_call_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['summary_file_written', data.summary_file_written],
|
||
['database_write_executed', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryPreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary preflight 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const prompt = data.prompt_contract || {};
|
||
const schema = data.summary_output_schema || {};
|
||
const routePolicy = data.model_route_policy || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`package=${data.ai_summary_run_package_ready ? 'ready' : 'blocked'}`,
|
||
`manual_ollama=${data.ready_for_manual_ollama_summary_run ? 'ready' : 'blocked'}`,
|
||
`llm=${data.llm_call_executed ? 'called' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生手動 Ollama 摘要任務包 contract;API/UI 不呼叫模型、不寫 run package、不派送 Telegram、不更新 review_state、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMPT CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['contract', prompt.contract_version || 'missing'],
|
||
['language', prompt.language || 'missing'],
|
||
['primary_policy', routePolicy.primary_policy || 'missing'],
|
||
['fallback_policy', routePolicy.fallback_policy || 'missing'],
|
||
['schema', schema.schema_version || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['ready_for_ai_summary_generation', data.ready_for_ai_summary_generation],
|
||
['api_executes_llm', data.api_executes_llm],
|
||
['llm_call_executed', data.llm_call_executed],
|
||
['ollama_call_executed', data.ollama_call_executed],
|
||
['gemini_call_executed', data.gemini_call_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['run_package_file_written', data.run_package_file_written],
|
||
['summary_file_written', data.summary_file_written],
|
||
['database_write_executed', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review AI summary run package 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryRunPackageEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryOutputReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const validation = data.summary_output_validation || {};
|
||
const routePolicy = data.model_route_policy || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.ai_summary_output_receipt_ready ? 'ready' : 'blocked'}`,
|
||
`schema=${data.summary_output_schema_valid ? 'pass' : 'blocked'}`,
|
||
`refs=${data.summary_output_evidence_refs_grounded ? 'pass' : 'blocked'}`,
|
||
`llm=${data.llm_call_executed ? 'called' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只驗收人工 Ollama 摘要輸出;API/UI 不呼叫模型、不寫 receipt、不派送 Telegram、不更新 review_state、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 output receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OUTPUT VALIDATION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['missing_fields', (validation.missing_fields || []).join(', ') || 'none'],
|
||
['empty_fields', (validation.empty_fields || []).join(', ') || 'none'],
|
||
['evidence_refs', (validation.evidence_refs || []).join(', ') || 'none'],
|
||
['route_accepted', validation.model_route_accepted],
|
||
['primary_policy', routePolicy.primary_policy || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_llm', data.api_executes_llm],
|
||
['llm_call_executed', data.llm_call_executed],
|
||
['ollama_call_executed', data.ollama_call_executed],
|
||
['gemini_call_executed', data.gemini_call_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['summary_receipt_file_written', data.summary_receipt_file_written],
|
||
['summary_file_written', data.summary_file_written],
|
||
['database_write_executed', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryOutputReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">驗收 queue review AI summary output receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryOutputReceiptEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryOutputReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary output receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistencePreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const contract = data.summary_persistence_contract || {};
|
||
const record = data.summary_record_preview || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`preflight=${data.summary_persistence_preflight_ready ? 'ready' : 'blocked'}`,
|
||
`transaction=${data.ready_for_summary_transaction_preview ? 'ready' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 AI summary persistence preflight;後續若要落地 metadata_json 必須另開 transaction preview 與 CLI-only writer gate。API/UI 不寫 DB、不派送 Telegram、不呼叫模型。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PERSISTENCE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 persistence preflight gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">TARGET CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', contract.target_table || 'missing'],
|
||
['target_column', contract.target_column || 'missing'],
|
||
['json_path', (contract.target_json_path || []).join('.') || 'missing'],
|
||
['planned_updates', contract.planned_update_count || 0],
|
||
['api_write_allowed', contract.api_write_allowed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY RECORD</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['payload_hash', record.payload_hash || 'missing'],
|
||
['headline', record.headline || 'missing'],
|
||
['findings', record.key_finding_count || 0],
|
||
['risks', record.risk_flag_count || 0],
|
||
['actions', record.recommended_action_count || 0],
|
||
['evidence_refs', (record.evidence_refs || []).join(', ') || 'none']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_llm', data.api_executes_llm],
|
||
['llm_call_executed', data.llm_call_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['summary_persistence_preflight_file_written', data.summary_persistence_preflight_file_written],
|
||
['summary_persistence_record_written', data.summary_persistence_record_written],
|
||
['metadata_patch_written', data.metadata_patch_written],
|
||
['database_write_executed', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistencePreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary persistence preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryPersistencePreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistencePreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence preflight 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTransaction = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.transaction_summary || {};
|
||
const statements = data.statements || [];
|
||
const rollback = data.rollback_plan || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`transaction=${data.summary_persistence_transaction_ready ? 'ready' : 'blocked'}`,
|
||
`statements=${data.statement_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 AI summary persistence transaction preview;正式 metadata_json 寫入必須另開 CLI writer gate。API/UI 不開 DB、不寫檔、不派送 Telegram、不呼叫模型。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">TRANSACTION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 transaction gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', summary.target_table || 'missing'],
|
||
['target_column', summary.target_column || 'missing'],
|
||
['metadata_key', summary.metadata_key || 'missing'],
|
||
['statement_count', summary.statement_count || 0],
|
||
['api_write_allowed', summary.api_write_allowed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">STATEMENTS</p>
|
||
<div class="market-intel-check-list">${
|
||
statements.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.statement_type || 'UPDATE')}</strong>
|
||
<small>${escapeHtml((item.where && item.where.dedupe_key) || 'missing')}</small>
|
||
</div>
|
||
<span>${item.execute_in_api ? 'WRITE' : 'PREVIEW'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未產生 statement preview。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ROLLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
rollback.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.required ? 'REQUIRED' : 'OPTIONAL'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 rollback plan。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTransaction = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review AI summary persistence transaction preview 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryPersistenceTransactionEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTransaction(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence transaction 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceWriterPreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const contract = data.writer_preflight_contract || {};
|
||
const statements = data.statement_payloads || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`preflight=${data.summary_persistence_writer_preflight_ready ? 'ready' : 'blocked'}`,
|
||
`run_package=${data.ready_for_summary_persistence_run_package ? 'ready' : 'blocked'}`,
|
||
`statements=${data.statement_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 AI summary persistence writer preflight;正式 metadata_json 寫入仍需未來 CLI-only run package。API/UI 不讀 token、不執行 CLI、不開 DB、不寫檔、不派送 Telegram。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER PREFLIGHT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 writer preflight gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['target_table', contract.target_table || 'missing'],
|
||
['target_column', contract.target_column || 'missing'],
|
||
['json_path', (contract.target_json_path || []).join('.') || 'missing'],
|
||
['manual_cli_required', contract.manual_cli_required],
|
||
['api_write_allowed', contract.api_write_allowed],
|
||
['backup_required', contract.requires_metadata_json_backup],
|
||
['postwrite_smoke', contract.requires_postwrite_smoke]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">STATEMENT PAYLOADS</p>
|
||
<div class="market-intel-check-list">${
|
||
statements.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.statement_type || 'UPDATE')}</strong>
|
||
<small>${escapeHtml(item.dedupe_key || 'missing')}</small>
|
||
</div>
|
||
<span>${item.execute_in_api ? 'WRITE' : 'CLI'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未載入 statement payload。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_approval_token', data.api_reads_approval_token],
|
||
['api_writes_database', data.api_writes_database],
|
||
['summary_persistence_writer_preflight_file_written', data.summary_persistence_writer_preflight_file_written],
|
||
['database_connection_opened', data.database_connection_opened],
|
||
['database_write_executed', data.database_write_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceWriterPreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary persistence writer preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryPersistenceWriterPreflightEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceWriterPreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence writer preflight 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.package_gates || [];
|
||
const manifest = data.payload_manifest || {};
|
||
const artifacts = data.required_artifacts || [];
|
||
const commands = data.command_bundle || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`package=${data.package_ready ? 'ready' : 'blocked'}`,
|
||
`readiness=${data.ready_for_summary_persistence_run_readiness ? 'ready' : 'blocked'}`,
|
||
`payloads=${manifest.payload_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 AI summary persistence run package preview;正式 metadata_json 寫入仍需後續 operator readiness 與 CLI receipt。API/UI 不讀 token、不執行 CLI、不開 DB、不寫檔、不派送 Telegram。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PAYLOAD MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['payload_count', manifest.payload_count || 0],
|
||
['manifest_hash', manifest.manifest_hash || 'missing'],
|
||
['summary_hash', manifest.summary_payload_hash || 'missing'],
|
||
['dedupe_keys', (manifest.dedupe_keys || []).join(', ') || 'none']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REQUIRED ARTIFACTS</p>
|
||
<div class="market-intel-check-list">${
|
||
artifacts.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.path_shape || '')}</small>
|
||
</div>
|
||
<span>${item.created_by_api ? 'API' : 'OPERATOR'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未列出 artifacts。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND BUNDLE</p>
|
||
<div class="market-intel-check-list">${
|
||
commands.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.command_shape || '')}</small>
|
||
</div>
|
||
<span>${item.executed ? 'EXECUTED' : item.executes_database ? 'CLI DB' : 'PREP'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 command bundle。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_approval_token', data.api_reads_approval_token],
|
||
['api_writes_database', data.api_writes_database],
|
||
['package_artifact_created', data.package_artifact_created],
|
||
['summary_persistence_run_package_file_written', data.summary_persistence_run_package_file_written],
|
||
['database_connection_opened', data.database_connection_opened],
|
||
['database_write_executed', data.database_write_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review AI summary persistence run package 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewAiSummaryPersistenceRunPackageEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.run_package_summary || {};
|
||
const operator = data.operator_ai_summary_persistence_run_readiness || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`readiness=${data.run_readiness_ready ? 'ready' : 'blocked'}`,
|
||
`payloads=${summary.payload_count || 0}`,
|
||
`operator_artifacts=${operator.artifact_path_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 AI summary persistence operator readiness;API/UI 不讀 token、不執行 CLI、不開 DB、不寫 metadata_json、不派送 Telegram。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 readiness gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['package_ready', summary.package_ready],
|
||
['payload_count', summary.payload_count || 0],
|
||
['command_count', summary.command_count || 0],
|
||
['writer_command_present', summary.writer_command_present],
|
||
['manifest_hash', summary.manifest_hash || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['run_package_path', operator.ai_summary_persistence_run_package_artifact_path || 'missing'],
|
||
['writer_preflight_path', operator.ai_summary_persistence_writer_preflight_artifact_path || 'missing'],
|
||
['backup_path', operator.metadata_json_backup_artifact_path || 'missing'],
|
||
['postwrite_smoke_path', operator.ai_summary_persistence_postwrite_smoke_artifact_path || 'missing'],
|
||
['token_submitted_to_api', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_approval_token', data.api_reads_approval_token],
|
||
['api_writes_database', data.api_writes_database],
|
||
['run_readiness_file_written', data.run_readiness_file_written],
|
||
['database_connection_opened', data.database_connection_opened],
|
||
['database_write_executed', data.database_write_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary persistence run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceRunReadinessEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence run readiness 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const readiness = data.run_readiness_summary || {};
|
||
const writer = data.writer_output_summary || {};
|
||
const smoke = data.postwrite_smoke_summary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.run_receipt_passed ? 'passed' : 'blocked'}`,
|
||
`closeout=${data.ready_for_summary_persistence_closeout ? 'ready' : 'blocked'}`,
|
||
`statements=${data.statement_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 AI summary persistence CLI 寫入後的回執;API/UI 不讀 token、不執行 CLI、不開 DB、不寫 metadata_json、不派送 Telegram。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN READINESS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['run_ready', readiness.ready_for_cli_operator_run],
|
||
['payload_count', readiness.payload_count || 0],
|
||
['expected_keys', (readiness.expected_dedupe_keys || []).join(', ') || 'none'],
|
||
['summary_hash', readiness.expected_summary_payload_hash || 'missing'],
|
||
['commands_executed', readiness.commands_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER OUTPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', writer.provided],
|
||
['mode', writer.mode || 'missing'],
|
||
['metadata_update', writer.metadata_json_update_executed],
|
||
['dedupe_match', writer.dedupe_keys_match_expected],
|
||
['hash_match', writer.summary_payload_hash_matches_expected],
|
||
['token_key', writer.approval_token_key_detected]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">POST-WRITE SMOKE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', smoke.provided],
|
||
['mode', smoke.mode || 'missing'],
|
||
['read_only', smoke.read_only_query_executed],
|
||
['metadata_verified', smoke.metadata_json_update_verified],
|
||
['dedupe_match', smoke.dedupe_keys_match_expected],
|
||
['db_write', smoke.database_write_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_approval_token', data.api_reads_approval_token],
|
||
['api_writes_database', data.api_writes_database],
|
||
['run_receipt_file_written', data.run_receipt_file_written],
|
||
['database_connection_opened', data.database_connection_opened],
|
||
['database_write_executed', data.database_write_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary persistence run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceRunReceiptEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence run receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceRunCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.receipt_summary || {};
|
||
const operator = data.operator_closeout_summary || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.closeout_passed ? 'passed' : 'blocked'}`,
|
||
`telegram_gate=${data.ready_for_summary_persistence_telegram_dispatch_gate ? 'ready' : 'blocked'}`,
|
||
`statements=${data.statement_count || 0}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只收尾 AI summary persistence receipt;後續 Telegram dispatch 必須另開 gate。API/UI 不讀 token、不執行 CLI、不開 DB、不寫 metadata_json、不派送 Telegram。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['receipt_passed', receipt.receipt_passed],
|
||
['closeout_ready', receipt.ready_for_summary_persistence_closeout],
|
||
['expected_keys', receipt.expected_dedupe_key_count || 0],
|
||
['writer_metadata', receipt.writer_metadata_json_update_executed],
|
||
['smoke_verified', receipt.postwrite_smoke_metadata_json_verified],
|
||
['safe_boundaries', receipt.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_artifact', operator.closeout_artifact_path_recorded],
|
||
['closeout_confirmed', operator.operator_confirmed_summary_persistence_closeout],
|
||
['telegram_separate_gate', operator.operator_confirmed_telegram_dispatch_requires_separate_gate],
|
||
['no_api_db_write', operator.operator_confirmed_no_api_db_write],
|
||
['no_telegram_dispatch', operator.operator_confirmed_no_telegram_dispatch],
|
||
['token_submitted', operator.approval_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'blocked'],
|
||
['requires_real_db_write', promotion.requires_real_db_write],
|
||
['requires_scheduler_attach', promotion.requires_scheduler_attach],
|
||
['telegram_separate_gate', promotion.telegram_dispatch_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_executes_cli', data.api_executes_cli],
|
||
['api_reads_approval_token', data.api_reads_approval_token],
|
||
['api_writes_database', data.api_writes_database],
|
||
['closeout_file_written', data.closeout_file_written],
|
||
['database_connection_opened', data.database_connection_opened],
|
||
['database_write_executed', data.database_write_executed],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceRunCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">收尾 queue review AI summary persistence run closeout 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceRunCloseoutEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceRunCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary persistence run closeout 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.closeout_summary || {};
|
||
const operator = data.operator_telegram_dispatch_gate_summary || {};
|
||
const contract = data.telegram_message_contract || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`dispatch_gate=${data.telegram_dispatch_gate_passed ? 'passed' : 'blocked'}`,
|
||
`run_package=${data.ready_for_summary_persistence_telegram_dispatch_run_package ? 'ready' : 'blocked'}`,
|
||
`statements=${data.statement_count || 0}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 AI summary Telegram dispatch gate;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">DISPATCH GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 dispatch gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT SOURCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_passed', closeout.closeout_passed],
|
||
['telegram_gate_ready', closeout.ready_for_summary_persistence_telegram_dispatch_gate],
|
||
['expected_keys', closeout.expected_dedupe_key_count || 0],
|
||
['summary_hash', closeout.expected_summary_payload_hash || 'missing'],
|
||
['metadata_smoke', closeout.postwrite_smoke_metadata_json_verified],
|
||
['safe_boundaries', closeout.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MESSAGE CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['source', `${contract.source_column || 'metadata'}:${(contract.source_json_path || []).join('.')}`],
|
||
['channel', contract.target_channel || 'manual_gate'],
|
||
['format', contract.message_format || 'unknown'],
|
||
['max_chars', contract.max_message_chars || 0],
|
||
['evidence_refs', contract.requires_evidence_refs],
|
||
['api_dispatches', contract.api_dispatches_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR GATE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['artifact_path', operator.telegram_dispatch_gate_artifact_path_recorded],
|
||
['dispatch_gate', operator.operator_confirmed_summary_persistence_telegram_dispatch_gate],
|
||
['message_reviewed', operator.operator_confirmed_telegram_message_reviewed],
|
||
['manual_run_package', operator.operator_confirmed_dispatch_requires_manual_run_package],
|
||
['channel_label', operator.telegram_channel_label_recorded],
|
||
['token_submitted', operator.forbidden_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION / NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'blocked'],
|
||
['ready_for_telegram_dispatch', data.ready_for_telegram_dispatch],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['database_write_executed', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached],
|
||
['dispatch_gate_file_written', data.telegram_dispatch_gate_file_written]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGateEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.package_gates || [];
|
||
const manifest = data.message_package_manifest || {};
|
||
const gate = data.telegram_dispatch_gate_summary || {};
|
||
const operator = data.operator_telegram_dispatch_run_package || {};
|
||
const commands = data.command_bundle || [];
|
||
const artifacts = data.required_artifacts || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`package=${data.telegram_dispatch_run_package_ready ? 'ready' : 'blocked'}`,
|
||
`readiness=${data.ready_for_summary_persistence_telegram_dispatch_run_readiness ? 'ready' : 'blocked'}`,
|
||
`commands=${commands.length}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 AI summary Telegram dispatch run package preview;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MESSAGE MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['channel', manifest.target_channel || 'manual_gate'],
|
||
['format', manifest.message_format || 'unknown'],
|
||
['dedupe_keys', manifest.expected_dedupe_key_count || 0],
|
||
['summary_hash', manifest.expected_summary_payload_hash || 'missing'],
|
||
['manifest_hash', manifest.manifest_hash || 'missing'],
|
||
['api_dispatches', manifest.api_dispatches_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND PLAN</p>
|
||
<div class="market-intel-check-list">
|
||
${commands.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`${item.step}. ${item.key}`)}</strong>
|
||
<small>${escapeHtml(item.command_shape || '')}</small>
|
||
</div>
|
||
<span>${item.executed ? 'DONE' : (item.executes_telegram ? 'MANUAL' : 'PLAN')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 command bundle。</div>'}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARTIFACTS</p>
|
||
<div class="market-intel-check-list">
|
||
${artifacts.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.path_shape || '')}</small>
|
||
</div>
|
||
<span>${item.created_by_api ? 'API' : 'OPERATOR'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 artifact checklist。</div>'}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SOURCE / NO-SIDE-EFFECTS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['gate_passed', gate.telegram_dispatch_gate_passed],
|
||
['gate_side_effects_clear', gate.side_effects_clear],
|
||
['operator_package', operator.operator_confirmed_ai_summary_telegram_dispatch_run_package],
|
||
['manual_only', operator.operator_confirmed_dispatch_is_manual_only],
|
||
['ready_for_telegram_dispatch', data.ready_for_telegram_dispatch],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['run_package_file_written', data.telegram_dispatch_run_package_file_written],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review AI summary Telegram dispatch run package 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackageEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const packageSummary = data.telegram_dispatch_run_package_summary || {};
|
||
const operator = data.operator_telegram_dispatch_run_readiness || {};
|
||
const boundary = data.manual_dispatch_boundary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`readiness=${data.telegram_dispatch_run_readiness_ready ? 'ready' : 'blocked'}`,
|
||
`manual=${data.ready_for_manual_telegram_dispatch ? 'ready' : 'blocked'}`,
|
||
`receipt=${data.ready_for_summary_persistence_telegram_dispatch_run_receipt ? 'ready' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查人工 Telegram dispatch readiness;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 readiness gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['package_ready', packageSummary.package_ready],
|
||
['channel', packageSummary.target_channel || 'manual_gate'],
|
||
['format', packageSummary.message_format || 'unknown'],
|
||
['dedupe_keys', packageSummary.expected_dedupe_key_count || 0],
|
||
['summary_hash', packageSummary.expected_summary_payload_hash || 'missing'],
|
||
['manual_command', packageSummary.manual_dispatch_command_present],
|
||
['commands_executed', packageSummary.commands_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['artifact_paths', operator.artifact_path_count || 0],
|
||
['run_readiness', operator.operator_confirmed_ai_summary_telegram_dispatch_run_readiness],
|
||
['message_preview', operator.operator_confirmed_message_preview_reviewed],
|
||
['manual_command', operator.operator_confirmed_manual_dispatch_command_reviewed],
|
||
['token_shell_only', operator.operator_confirmed_telegram_token_shell_only],
|
||
['receipt_required', operator.operator_confirmed_dispatch_receipt_required],
|
||
['token_leak', operator.forbidden_token_submitted_to_api]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MANUAL BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_may_dispatch_telegram', boundary.api_may_dispatch_telegram],
|
||
['operator_shell_required', boundary.operator_shell_required],
|
||
['telegram_token_shell_only', boundary.telegram_token_shell_only],
|
||
['requires_message_preview_review', boundary.requires_message_preview_review],
|
||
['requires_dispatch_receipt', boundary.requires_dispatch_receipt],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['telegram_dispatched', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadinessEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch run readiness 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const readiness = data.telegram_dispatch_run_readiness_summary || {};
|
||
const receipt = data.telegram_dispatch_receipt_summary || {};
|
||
const operator = data.operator_telegram_dispatch_run_receipt || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.telegram_dispatch_run_receipt_passed ? 'passed' : 'blocked'}`,
|
||
`closeout=${data.ready_for_summary_persistence_telegram_dispatch_closeout ? 'ready' : 'blocked'}`,
|
||
`message=${receipt.message_id || 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核人工 Telegram dispatch receipt;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN READINESS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['readiness_ready', readiness.run_readiness_ready],
|
||
['manual_dispatch', readiness.ready_for_manual_telegram_dispatch],
|
||
['channel', readiness.target_channel || 'manual_gate'],
|
||
['format', readiness.message_format || 'unknown'],
|
||
['dedupe_keys', readiness.expected_dedupe_key_count || 0],
|
||
['summary_hash', readiness.expected_summary_payload_hash || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">TELEGRAM RECEIPT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', receipt.provided],
|
||
['mode', receipt.mode || 'missing'],
|
||
['ok', receipt.ok],
|
||
['message_id', receipt.message_id || 'missing'],
|
||
['chat_id', receipt.chat_id || 'missing'],
|
||
['channel', receipt.telegram_channel_label || 'missing'],
|
||
['hash_match', receipt.summary_payload_hash_matches_expected],
|
||
['token_key_detected', receipt.approval_or_telegram_token_key_detected]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR / API BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['receipt_artifact', operator.telegram_dispatch_receipt_artifact_path_recorded],
|
||
['message_preview', operator.telegram_message_preview_artifact_path_recorded],
|
||
['operator_receipt', operator.operator_confirmed_telegram_dispatch_receipt],
|
||
['message_id_recorded', operator.operator_confirmed_message_id_recorded],
|
||
['no_token', operator.operator_confirmed_no_token_in_receipt],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['telegram_dispatched_by_api', data.telegram_dispatched],
|
||
['scheduler_attached', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceiptEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch run receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.telegram_dispatch_run_receipt_summary || {};
|
||
const operator = data.operator_telegram_dispatch_closeout || {};
|
||
const audit = data.dispatch_audit_summary || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.telegram_dispatch_closeout_passed ? 'passed' : 'blocked'}`,
|
||
`archive=${data.ready_for_summary_persistence_telegram_dispatch_archive ? 'ready' : 'blocked'}`,
|
||
`message=${audit.message_id || 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理人工 Telegram dispatch closeout;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN RECEIPT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', receipt.provided],
|
||
['mode', receipt.mode || 'missing'],
|
||
['receipt_passed', receipt.receipt_passed],
|
||
['ready_closeout', receipt.ready_for_summary_persistence_telegram_dispatch_closeout],
|
||
['message_id', receipt.message_id || 'missing'],
|
||
['channel', receipt.telegram_channel_label || 'missing'],
|
||
['hash_match', receipt.summary_payload_hash_matches_expected],
|
||
['safe_boundaries', receipt.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">DISPATCH AUDIT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['manual_dispatch_proven', audit.manual_dispatch_proven],
|
||
['api_dispatch_blocked', audit.api_dispatch_blocked],
|
||
['duplicate_ruled_out', audit.duplicate_dispatch_ruled_out],
|
||
['receipt_archived', audit.receipt_archived],
|
||
['monitoring', audit.post_closeout_monitoring_confirmed],
|
||
['chat_id', audit.chat_id || 'missing'],
|
||
['summary_hash', audit.summary_payload_hash || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_artifact', operator.telegram_dispatch_closeout_artifact_path_recorded],
|
||
['receipt_artifact', operator.telegram_dispatch_receipt_artifact_path_recorded],
|
||
['closeout_confirmed', operator.operator_confirmed_telegram_dispatch_closeout],
|
||
['receipt_archived', operator.operator_confirmed_receipt_archived],
|
||
['message_visible', operator.operator_confirmed_message_visible],
|
||
['no_duplicate', operator.operator_confirmed_no_duplicate_dispatch],
|
||
['no_token', operator.operator_confirmed_no_token_in_artifacts],
|
||
['no_api_dispatch', operator.operator_confirmed_no_api_telegram_dispatch],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch closeout 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseoutEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch closeout 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.telegram_dispatch_closeout_summary || {};
|
||
const manifest = data.telegram_dispatch_archive_manifest || {};
|
||
const message = manifest.telegram_message || {};
|
||
const paths = manifest.artifact_paths || {};
|
||
const operator = data.operator_telegram_dispatch_archive || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`archive=${data.telegram_dispatch_archive_ready ? 'ready' : 'blocked'}`,
|
||
`manifest=${data.archive_manifest_ready ? 'ready' : 'blocked'}`,
|
||
`message=${message.message_id || 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理人工 Telegram dispatch archive manifest;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', closeout.provided],
|
||
['mode', closeout.mode || 'missing'],
|
||
['closeout_passed', closeout.closeout_passed],
|
||
['ready_archive', closeout.ready_for_summary_persistence_telegram_dispatch_archive],
|
||
['statement_count', closeout.statement_count || 0],
|
||
['side_effects_clear', closeout.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['message_id', message.message_id || 'missing'],
|
||
['chat_id', message.chat_id || 'missing'],
|
||
['channel', message.telegram_channel_label || 'missing'],
|
||
['hash_match', message.summary_payload_hash_matches_expected],
|
||
['receipt_path', paths.telegram_dispatch_receipt_artifact_path || 'missing'],
|
||
['closeout_path', paths.telegram_dispatch_closeout_artifact_path || 'missing'],
|
||
['archive_path', paths.telegram_dispatch_archive_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR ARCHIVE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['archive_confirmed', operator.operator_confirmed_telegram_dispatch_archive],
|
||
['read_only', operator.operator_confirmed_archive_is_read_only],
|
||
['receipt_archived', operator.operator_confirmed_receipt_archived],
|
||
['message_id_archived', operator.operator_confirmed_message_id_archived],
|
||
['audit_archived', operator.operator_confirmed_dispatch_audit_archived],
|
||
['monitoring', operator.operator_confirmed_post_closeout_monitoring],
|
||
['no_duplicate', operator.operator_confirmed_no_duplicate_dispatch],
|
||
['no_token', operator.operator_confirmed_no_token_in_artifacts],
|
||
['no_api_dispatch', operator.operator_confirmed_no_api_telegram_dispatch],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch archive 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch archive 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const archive = data.telegram_dispatch_archive_summary || {};
|
||
const message = archive.message || {};
|
||
const audit = archive.dispatch_audit || {};
|
||
const operator = data.operator_telegram_dispatch_archive_summary || {};
|
||
const sections = data.telegram_dispatch_archive_summary_sections || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`summary=${data.telegram_dispatch_archive_summary_ready ? 'ready' : 'blocked'}`,
|
||
`report=${data.ready_for_summary_persistence_telegram_dispatch_report_input ? 'ready' : 'blocked'}`,
|
||
`message=${message.message_id || 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch archive summary input;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive summary gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE INPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', archive.provided],
|
||
['mode', archive.mode || 'missing'],
|
||
['archive_ready', archive.telegram_dispatch_archive_ready],
|
||
['manifest_ready', archive.archive_manifest_ready],
|
||
['statement_count', archive.statement_count || 0],
|
||
['side_effects_clear', archive.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MESSAGE / AUDIT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['message_id', message.message_id || 'missing'],
|
||
['chat_id', message.chat_id || 'missing'],
|
||
['channel', message.telegram_channel_label || 'missing'],
|
||
['hash_match', message.summary_payload_hash_matches_expected],
|
||
['manual_dispatch', audit.manual_dispatch_proven],
|
||
['api_blocked', audit.api_dispatch_blocked],
|
||
['duplicate_ruled_out', audit.duplicate_dispatch_ruled_out],
|
||
['monitoring', audit.post_closeout_monitoring_confirmed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(section => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>INPUT</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 summary sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['summary_confirmed', operator.operator_confirmed_telegram_dispatch_archive_summary],
|
||
['read_only', operator.operator_confirmed_summary_is_read_only],
|
||
['artifacts_reviewed', operator.operator_confirmed_message_and_artifacts_reviewed],
|
||
['audit_reviewed', operator.operator_confirmed_dispatch_audit_reviewed],
|
||
['separate_report_gate', operator.operator_confirmed_report_input_requires_separate_gate],
|
||
['no_token', operator.operator_confirmed_no_token_in_artifacts],
|
||
['no_api_dispatch', operator.operator_confirmed_no_api_telegram_dispatch],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach],
|
||
['summary_path', operator.telegram_dispatch_archive_summary_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch archive summary 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummaryEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch archive summary 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.telegram_dispatch_archive_summary_input || {};
|
||
const message = summary.message || {};
|
||
const audit = summary.dispatch_audit || {};
|
||
const operator = data.operator_telegram_dispatch_report_input || {};
|
||
const sections = data.telegram_dispatch_report_input_sections || [];
|
||
const contract = data.telegram_dispatch_report_input_contract || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`report_input=${data.telegram_dispatch_report_input_ready ? 'ready' : 'blocked'}`,
|
||
`run_package=${data.ready_for_market_intel_report_run_package ? 'ready' : 'blocked'}`,
|
||
`message=${message.message_id || 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch report input;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT INPUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 report input gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE SUMMARY INPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', summary.provided],
|
||
['mode', summary.mode || 'missing'],
|
||
['summary_ready', summary.telegram_dispatch_archive_summary_ready],
|
||
['report_ready', summary.ready_for_summary_persistence_telegram_dispatch_report_input],
|
||
['statement_count', summary.statement_count || 0],
|
||
['side_effects_clear', summary.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['contract', contract.contract_version || 'missing'],
|
||
['family', contract.target_report_family || 'missing'],
|
||
['language', contract.language || 'missing'],
|
||
['required_sections', (contract.required_sections || []).join(', ') || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MESSAGE / AUDIT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['message_id', message.message_id || 'missing'],
|
||
['chat_id', message.chat_id || 'missing'],
|
||
['channel', message.telegram_channel_label || 'missing'],
|
||
['hash_match', message.summary_payload_hash_matches_expected],
|
||
['manual_dispatch', audit.manual_dispatch_proven],
|
||
['api_blocked', audit.api_dispatch_blocked],
|
||
['duplicate_ruled_out', audit.duplicate_dispatch_ruled_out],
|
||
['monitoring', audit.post_closeout_monitoring_confirmed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(section => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>REPORT</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 report sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR REPORT INPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['report_confirmed', operator.operator_confirmed_telegram_dispatch_report_input],
|
||
['read_only', operator.operator_confirmed_report_input_is_read_only],
|
||
['archive_summary_reviewed', operator.operator_confirmed_archive_summary_reviewed],
|
||
['sections_reviewed', operator.operator_confirmed_report_sections_reviewed],
|
||
['separate_report_gate', operator.operator_confirmed_report_generation_requires_separate_gate],
|
||
['no_token', operator.operator_confirmed_no_token_in_report_input],
|
||
['no_api_dispatch', operator.operator_confirmed_no_api_telegram_dispatch],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach],
|
||
['report_input_path', operator.telegram_dispatch_report_input_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch report input 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInputEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report input 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const inputSummary = data.telegram_dispatch_report_input_summary || {};
|
||
const evidence = inputSummary.evidence_refs || {};
|
||
const runPackage = data.telegram_dispatch_report_run_package || {};
|
||
const sections = data.telegram_dispatch_report_run_package_sections || [];
|
||
const operator = data.operator_telegram_dispatch_report_run_package || {};
|
||
const boundaries = runPackage.execution_boundaries || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`package=${data.telegram_dispatch_report_run_package_ready ? 'ready' : 'blocked'}`,
|
||
`readiness=${data.ready_for_market_intel_report_run_readiness ? 'ready' : 'blocked'}`,
|
||
`report=${data.ready_for_report_generation ? 'generated' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch report run package;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT RUN PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT INPUT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', inputSummary.provided],
|
||
['mode', inputSummary.mode || 'missing'],
|
||
['input_ready', inputSummary.telegram_dispatch_report_input_ready],
|
||
['run_package_ready', inputSummary.ready_for_market_intel_report_run_package],
|
||
['contract', inputSummary.report_contract_version || 'missing'],
|
||
['family', inputSummary.target_report_family || 'missing'],
|
||
['statement_count', inputSummary.statement_count || 0],
|
||
['side_effects_clear', inputSummary.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['contract', runPackage.contract_version || 'missing'],
|
||
['source_contract', runPackage.source_contract_version || 'missing'],
|
||
['family', runPackage.target_report_family || 'missing'],
|
||
['language', runPackage.language || 'missing'],
|
||
['required_sections', (runPackage.required_package_sections || []).join(', ') || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(section => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>PACKAGE</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE / BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['message_id', evidence.message_id || 'missing'],
|
||
['chat_id', evidence.chat_id || 'missing'],
|
||
['channel', evidence.telegram_channel_label || 'missing'],
|
||
['summary_hash', evidence.summary_payload_hash || 'missing'],
|
||
['api_generates_report', boundaries.api_generates_report],
|
||
['api_writes_report_file', boundaries.api_writes_report_file],
|
||
['api_calls_llm', boundaries.api_calls_llm],
|
||
['api_dispatches_telegram', boundaries.api_dispatches_telegram],
|
||
['api_writes_database', boundaries.api_writes_database],
|
||
['api_attaches_scheduler', boundaries.api_attaches_scheduler]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR PACKAGE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['package_confirmed', operator.operator_confirmed_telegram_dispatch_report_run_package],
|
||
['read_only', operator.operator_confirmed_report_run_package_is_read_only],
|
||
['input_reviewed', operator.operator_confirmed_report_input_reviewed],
|
||
['package_reviewed', operator.operator_confirmed_report_package_reviewed],
|
||
['readiness_gate', operator.operator_confirmed_report_generation_requires_readiness_gate],
|
||
['no_token', operator.operator_confirmed_no_token_in_report_run_package],
|
||
['no_api_dispatch', operator.operator_confirmed_no_api_telegram_dispatch],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach],
|
||
['package_path', operator.telegram_dispatch_report_run_package_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch report run package 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackageEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const packageSummary = data.telegram_dispatch_report_run_package_summary || {};
|
||
const evidence = packageSummary.evidence_refs || {};
|
||
const manifest = data.report_generation_readiness_manifest || {};
|
||
const command = manifest.manual_generation_command || {};
|
||
const boundaries = manifest.execution_boundaries || {};
|
||
const operator = data.operator_telegram_dispatch_report_run_readiness || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`readiness=${data.telegram_dispatch_report_run_readiness_ready ? 'ready' : 'blocked'}`,
|
||
`manual_report=${data.ready_for_manual_report_generation ? 'ready' : 'blocked'}`,
|
||
`report=${data.ready_for_report_generation ? 'generated' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 Telegram dispatch report run readiness;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT RUN READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 readiness gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', packageSummary.provided],
|
||
['mode', packageSummary.mode || 'missing'],
|
||
['package_ready', packageSummary.telegram_dispatch_report_run_package_ready],
|
||
['readiness_ready', packageSummary.ready_for_market_intel_report_run_readiness],
|
||
['contract', packageSummary.package_contract_version || 'missing'],
|
||
['family', packageSummary.target_report_family || 'missing'],
|
||
['statement_count', packageSummary.statement_count || 0],
|
||
['side_effects_clear', packageSummary.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS MANIFEST</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['manifest', manifest.manifest_version || 'missing'],
|
||
['source_contract', manifest.source_contract_version || 'missing'],
|
||
['family', manifest.target_report_family || 'missing'],
|
||
['language', manifest.language || 'missing'],
|
||
['intended_artifacts', (manifest.intended_report_artifacts || []).join(', ') || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">MANUAL REPORT COMMAND</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['key', command.key || 'missing'],
|
||
['executes_report_generation', command.executes_report_generation],
|
||
['executes_database', command.executes_database],
|
||
['executes_telegram', command.executes_telegram],
|
||
['executed_by_api', command.executed_by_api],
|
||
['command_shape', command.command_shape || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE / BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['message_id', evidence.message_id || 'missing'],
|
||
['chat_id', evidence.chat_id || 'missing'],
|
||
['channel', evidence.telegram_channel_label || 'missing'],
|
||
['summary_hash', evidence.summary_payload_hash || 'missing'],
|
||
['api_generates_report', boundaries.api_generates_report],
|
||
['api_writes_report_file', boundaries.api_writes_report_file],
|
||
['api_calls_llm', boundaries.api_calls_llm],
|
||
['api_dispatches_telegram', boundaries.api_dispatches_telegram],
|
||
['api_writes_database', boundaries.api_writes_database],
|
||
['api_attaches_scheduler', boundaries.api_attaches_scheduler]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR READINESS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['readiness_confirmed', operator.operator_confirmed_telegram_dispatch_report_run_readiness],
|
||
['package_reviewed', operator.operator_confirmed_report_run_package_reviewed],
|
||
['contract_reviewed', operator.operator_confirmed_report_contract_reviewed],
|
||
['manual_only', operator.operator_confirmed_report_generation_is_manual_only],
|
||
['receipt_required', operator.operator_confirmed_report_output_requires_receipt],
|
||
['no_token', operator.operator_confirmed_no_token_in_report_readiness],
|
||
['no_api_generation', operator.operator_confirmed_no_api_report_generation],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach],
|
||
['readiness_path', operator.telegram_dispatch_report_run_readiness_artifact_path || 'missing'],
|
||
['output_path', operator.report_output_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch report run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadinessEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report run readiness 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const readiness = data.telegram_dispatch_report_run_readiness_summary || {};
|
||
const receipt = data.telegram_dispatch_report_run_receipt_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_run_receipt || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.telegram_dispatch_report_run_receipt_passed ? 'pass' : 'blocked'}`,
|
||
`closeout=${data.ready_for_market_intel_report_closeout ? 'ready' : 'blocked'}`,
|
||
`report=${data.ready_for_report_generation ? 'generated' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report run receipt;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT RUN RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', readiness.provided],
|
||
['mode', readiness.mode || 'missing'],
|
||
['readiness_ready', readiness.telegram_dispatch_report_run_readiness_ready],
|
||
['receipt_ready', readiness.ready_for_market_intel_report_run_receipt],
|
||
['manual_report', readiness.ready_for_manual_report_generation],
|
||
['manifest', readiness.manifest_version || 'missing'],
|
||
['family', readiness.target_report_family || 'missing'],
|
||
['side_effects_clear', readiness.side_effects_clear]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT RECEIPT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', receipt.provided],
|
||
['mode', receipt.mode || 'missing'],
|
||
['generated', receipt.report_generated],
|
||
['family', receipt.target_report_family || 'missing'],
|
||
['language', receipt.language || 'missing'],
|
||
['summary_hash_match', receipt.summary_payload_hash_matches_expected],
|
||
['sections_present', receipt.required_report_sections_present],
|
||
['artifact_path', receipt.report_output_artifact_path || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['generated_by_api', receipt.generated_by_api],
|
||
['api_writes_file', receipt.api_writes_file],
|
||
['api_calls_llm', receipt.api_calls_llm],
|
||
['api_dispatches_telegram', receipt.api_dispatches_telegram],
|
||
['api_writes_database', receipt.api_writes_database],
|
||
['scheduler_attached', receipt.scheduler_attached],
|
||
['token_key_detected', receipt.approval_or_telegram_token_key_detected]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR RECEIPT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['package_path', operator.telegram_dispatch_report_run_package_artifact_path_recorded],
|
||
['readiness_path', operator.telegram_dispatch_report_run_readiness_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['receipt_path', operator.report_run_receipt_artifact_path_recorded],
|
||
['receipt_confirmed', operator.operator_confirmed_report_run_receipt],
|
||
['hash_confirmed', operator.operator_confirmed_report_output_hash_matched],
|
||
['sections_reviewed', operator.operator_confirmed_report_sections_reviewed],
|
||
['no_api_generation', operator.operator_confirmed_no_api_report_generation],
|
||
['no_db_llm_scheduler', operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceiptEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report run receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.telegram_dispatch_report_run_receipt_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_closeout || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.telegram_dispatch_report_closeout_passed ? 'pass' : 'blocked'}`,
|
||
`archive=${data.ready_for_market_intel_report_archive ? 'ready' : 'blocked'}`,
|
||
`report=${data.ready_for_report_generation ? 'generated' : 'blocked'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只收尾 Telegram dispatch report closeout;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', receipt.provided],
|
||
['mode', receipt.mode || 'missing'],
|
||
['receipt_passed', receipt.receipt_passed],
|
||
['closeout_ready', receipt.ready_for_market_intel_report_closeout],
|
||
['family', receipt.target_report_family || 'missing'],
|
||
['language', receipt.language || 'missing'],
|
||
['report_path', receipt.report_output_artifact_path || 'missing'],
|
||
['summary_hash_match', receipt.summary_payload_hash_matches_expected],
|
||
['sections_present', receipt.required_report_sections_present]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_path', operator.report_closeout_artifact_path_recorded],
|
||
['receipt_path', operator.report_run_receipt_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['closeout_confirmed', operator.operator_confirmed_report_closeout],
|
||
['receipt_archived', operator.operator_confirmed_report_receipt_archived],
|
||
['hash_confirmed', operator.operator_confirmed_report_output_hash_matched],
|
||
['sections_reviewed', operator.operator_confirmed_report_sections_reviewed],
|
||
['archive_separate_gate', operator.operator_confirmed_report_archive_requires_separate_gate],
|
||
['no_token_or_runtime_side_effect', operator.operator_confirmed_no_token_in_report_closeout && operator.operator_confirmed_no_api_report_generation && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_writes_file', data.api_writes_file],
|
||
['api_calls_llm', data.api_executes_llm],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['api_writes_database', data.api_writes_database],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached],
|
||
['report_file_written', data.report_file_written],
|
||
['closeout_file_written', data.report_closeout_file_written]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['requires_operator_approval', promotion.requires_operator_approval],
|
||
['archive_separate_gate', promotion.report_archive_requires_separate_gate],
|
||
['api_must_not_generate_report', promotion.api_must_not_generate_report],
|
||
['api_must_not_dispatch_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">收尾 queue review AI summary Telegram dispatch report closeout 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseoutEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report closeout 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.telegram_dispatch_report_closeout_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_archive || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`archive=${data.telegram_dispatch_report_archive_passed ? 'pass' : 'blocked'}`,
|
||
`summary=${data.ready_for_market_intel_report_archive_summary ? 'ready' : 'blocked'}`,
|
||
`file=${data.report_archive_file_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report archive;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT ARCHIVE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', closeout.provided],
|
||
['mode', closeout.mode || 'missing'],
|
||
['closeout_passed', closeout.closeout_passed],
|
||
['archive_ready', closeout.ready_for_market_intel_report_archive],
|
||
['next_phase', closeout.promotion_next_manual_phase || 'missing'],
|
||
['family', closeout.target_report_family || 'missing'],
|
||
['report_path', closeout.report_output_artifact_path || 'missing'],
|
||
['summary_hash_match', closeout.summary_payload_hash_matches_expected],
|
||
['sections_present', closeout.required_report_sections_present]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR ARCHIVE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['archive_path', operator.report_archive_artifact_path_recorded],
|
||
['closeout_path', operator.report_closeout_artifact_path_recorded],
|
||
['receipt_path', operator.report_run_receipt_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['archive_confirmed', operator.operator_confirmed_report_archive],
|
||
['closeout_archived', operator.operator_confirmed_report_closeout_archived],
|
||
['receipt_archived', operator.operator_confirmed_report_receipt_archived],
|
||
['output_archived', operator.operator_confirmed_report_output_archived],
|
||
['hash_confirmed', operator.operator_confirmed_report_output_hash_matched],
|
||
['manifest_retention', operator.operator_confirmed_archive_manifest_reviewed && operator.operator_confirmed_archive_retention_policy_recorded]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_writes_file', data.api_writes_file],
|
||
['archive_file_written', data.report_archive_file_written],
|
||
['api_calls_llm', data.api_executes_llm],
|
||
['api_dispatches_telegram', data.api_dispatches_telegram],
|
||
['api_writes_database', data.api_writes_database],
|
||
['database_write', data.database_write_executed],
|
||
['scheduler_attached', data.scheduler_attached],
|
||
['report_file_written', data.report_file_written]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['requires_operator_approval', promotion.requires_operator_approval],
|
||
['summary_separate_gate', promotion.report_archive_summary_requires_separate_gate],
|
||
['api_must_not_write_archive_file', promotion.api_must_not_write_archive_file],
|
||
['api_must_not_dispatch_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report archive 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report archive 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.telegram_dispatch_report_archive_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_archive_summary || {};
|
||
const sections = data.telegram_dispatch_report_archive_summary_sections || [];
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`summary=${data.telegram_dispatch_report_archive_summary_passed ? 'pass' : 'blocked'}`,
|
||
`catalog=${data.ready_for_market_intel_report_catalog_handoff ? 'ready' : 'blocked'}`,
|
||
`file=${data.report_archive_summary_file_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch report archive summary;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫檔、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">REPORT ARCHIVE SUMMARY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive summary gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 summary sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', summary.provided],
|
||
['mode', summary.mode || 'missing'],
|
||
['archive_passed', summary.archive_passed],
|
||
['report_family', summary.target_report_family || 'missing'],
|
||
['report_path', summary.report_output_artifact_path || 'missing'],
|
||
['hash_match', summary.summary_payload_hash_matches_expected],
|
||
['sections_present', summary.required_report_sections_present],
|
||
['retention_recorded', summary.operator_confirmed_archive_retention_policy_recorded]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['summary_path', operator.report_archive_summary_artifact_path || 'missing'],
|
||
['archive_path', operator.report_archive_artifact_path_recorded],
|
||
['closeout_path', operator.report_closeout_artifact_path_recorded],
|
||
['receipt_path', operator.report_run_receipt_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['summary_confirmed', operator.operator_confirmed_report_archive_summary],
|
||
['catalog_handoff_gate', operator.operator_confirmed_report_catalog_handoff_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_archive_summary && operator.operator_confirmed_no_api_report_generation && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['catalog_handoff_separate_gate', promotion.report_catalog_handoff_requires_separate_gate],
|
||
['api_must_not_generate_report', promotion.api_must_not_generate_report],
|
||
['api_must_not_write_summary_file', promotion.api_must_not_write_summary_file],
|
||
['api_must_not_dispatch_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch report archive summary 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummaryEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report archive summary 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.telegram_dispatch_report_archive_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_handoff || {};
|
||
const sections = data.telegram_dispatch_report_catalog_handoff_sections || [];
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`handoff=${data.telegram_dispatch_report_catalog_handoff_passed ? 'pass' : 'blocked'}`,
|
||
`index=${data.ready_for_market_intel_report_catalog_index ? 'ready' : 'blocked'}`,
|
||
`catalog=${data.catalog_record_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch report catalog handoff;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫 catalog record、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CATALOG HANDOFF GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 catalog handoff gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">HANDOFF SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 handoff sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', summary.provided],
|
||
['mode', summary.mode || 'missing'],
|
||
['summary_passed', summary.archive_summary_passed],
|
||
['report_family', summary.target_report_family || 'missing'],
|
||
['report_hash', summary.report_output_hash || 'missing'],
|
||
['section_count', summary.section_count || 0],
|
||
['catalog_ready', summary.ready_for_market_intel_report_catalog_handoff]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR HANDOFF</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['handoff_path', operator.report_catalog_handoff_artifact_path || 'missing'],
|
||
['summary_path', operator.report_archive_summary_artifact_path_recorded],
|
||
['archive_path', operator.report_archive_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['catalog_key', operator.catalog_record_key_recorded],
|
||
['catalog_family', operator.catalog_family_recorded],
|
||
['index_gate', operator.operator_confirmed_report_catalog_index_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_handoff && operator.operator_confirmed_no_api_report_generation && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['catalog_index_separate_gate', promotion.report_catalog_index_requires_separate_gate],
|
||
['write_preflight_separate_gate', promotion.report_catalog_write_preflight_requires_separate_gate],
|
||
['api_must_not_write_catalog_record', promotion.api_must_not_write_catalog_record],
|
||
['api_must_not_dispatch_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch report catalog handoff 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoffEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog handoff 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const handoff = data.telegram_dispatch_report_catalog_handoff || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_index || {};
|
||
const sections = data.telegram_dispatch_report_catalog_index_sections || [];
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`index=${data.telegram_dispatch_report_catalog_index_passed ? 'pass' : 'blocked'}`,
|
||
`write_preflight=${data.ready_for_market_intel_report_catalog_write_preflight ? 'ready' : 'blocked'}`,
|
||
`catalog=${data.catalog_record_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只整理 Telegram dispatch report catalog index;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫 catalog record、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CATALOG INDEX GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 catalog index gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">INDEX SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 index sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CATALOG HANDOFF</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', handoff.provided],
|
||
['mode', handoff.mode || 'missing'],
|
||
['handoff_passed', handoff.catalog_handoff_passed],
|
||
['report_family', handoff.target_report_family || 'missing'],
|
||
['report_hash', handoff.report_output_hash || 'missing'],
|
||
['section_count', handoff.section_count || 0],
|
||
['index_ready', handoff.ready_for_market_intel_report_catalog_index]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR INDEX</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['index_path', operator.report_catalog_index_artifact_path || 'missing'],
|
||
['handoff_path', operator.report_catalog_handoff_artifact_path_recorded],
|
||
['summary_path', operator.report_archive_summary_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['catalog_key', operator.catalog_record_key_recorded],
|
||
['catalog_family', operator.catalog_family_recorded],
|
||
['index_key', operator.catalog_index_key_recorded],
|
||
['write_preflight_gate', operator.operator_confirmed_report_catalog_write_preflight_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_index && operator.operator_confirmed_no_api_report_generation && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['write_preflight_separate_gate', promotion.report_catalog_write_preflight_requires_separate_gate],
|
||
['record_write_separate_gate', promotion.report_catalog_record_write_requires_separate_gate],
|
||
['api_must_not_write_index_file', promotion.api_must_not_write_catalog_index_file],
|
||
['api_must_not_write_catalog_record', promotion.api_must_not_write_catalog_record]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">整理 queue review AI summary Telegram dispatch report catalog index 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndexEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog index 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const index = data.telegram_dispatch_report_catalog_index || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_write_preflight || {};
|
||
const sections = data.telegram_dispatch_report_catalog_write_preflight_sections || [];
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`preflight=${data.telegram_dispatch_report_catalog_write_preflight_passed ? 'pass' : 'blocked'}`,
|
||
`record_write=${data.ready_for_market_intel_report_catalog_record_write ? 'ready' : 'blocked'}`,
|
||
`catalog=${data.catalog_record_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 Telegram dispatch report catalog write preflight;API/UI 不讀 token、不呼叫 LLM、不開 DB、不寫 catalog record、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE PREFLIGHT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 write preflight gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PREFLIGHT SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 preflight sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CATALOG INDEX</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', index.provided],
|
||
['mode', index.mode || 'missing'],
|
||
['index_passed', index.catalog_index_passed],
|
||
['report_family', index.target_report_family || 'missing'],
|
||
['report_hash', index.report_output_hash || 'missing'],
|
||
['section_count', index.section_count || 0],
|
||
['preflight_ready', index.ready_for_market_intel_report_catalog_write_preflight]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR PREFLIGHT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['preflight_path', operator.report_catalog_write_preflight_artifact_path || 'missing'],
|
||
['index_path', operator.report_catalog_index_artifact_path_recorded],
|
||
['handoff_path', operator.report_catalog_handoff_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['catalog_key', operator.catalog_record_key_recorded],
|
||
['catalog_family', operator.catalog_family_recorded],
|
||
['index_key', operator.catalog_index_key_recorded],
|
||
['record_schema', operator.catalog_record_schema_recorded],
|
||
['record_write_gate', operator.operator_confirmed_report_catalog_record_write_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_write_preflight && operator.operator_confirmed_no_api_report_generation && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['requires_real_db_write', promotion.requires_real_db_write],
|
||
['record_write_separate_gate', promotion.report_catalog_record_write_requires_separate_gate],
|
||
['commit_separate_gate', promotion.report_catalog_record_commit_requires_separate_gate],
|
||
['api_must_not_write_catalog_record', promotion.api_must_not_write_catalog_record]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch report catalog write preflight 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflightEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog write preflight 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const preflight = data.telegram_dispatch_report_catalog_write_preflight || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_write || {};
|
||
const sections = data.telegram_dispatch_report_catalog_record_write_sections || [];
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`record_write=${data.telegram_dispatch_report_catalog_record_write_passed ? 'pass' : 'blocked'}`,
|
||
`cli=${data.ready_for_market_intel_report_catalog_record_cli_run ? 'ready' : 'blocked'}`,
|
||
`catalog=${data.catalog_record_written ? 'written' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 Telegram dispatch report catalog record write gate;API/UI 不讀 token、不開 DB、不寫 catalog record、不產報表、不補發 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECORD WRITE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 record write gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECORD WRITE SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 record write sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE PREFLIGHT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', preflight.provided],
|
||
['mode', preflight.mode || 'missing'],
|
||
['preflight_passed', preflight.catalog_write_preflight_passed],
|
||
['record_write_ready', preflight.ready_for_market_intel_report_catalog_record_write],
|
||
['report_family', preflight.target_report_family || 'missing'],
|
||
['report_hash', preflight.report_output_hash || 'missing'],
|
||
['schema_recorded', preflight.catalog_record_schema_recorded]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR RECORD WRITE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['record_write_path', operator.report_catalog_record_write_artifact_path || 'missing'],
|
||
['preflight_path', operator.report_catalog_write_preflight_artifact_path_recorded],
|
||
['index_path', operator.report_catalog_index_artifact_path_recorded],
|
||
['output_path', operator.report_output_artifact_path_recorded],
|
||
['catalog_key', operator.catalog_record_key_recorded],
|
||
['record_schema', operator.catalog_record_schema_recorded],
|
||
['backup', operator.catalog_record_backup_artifact_path_recorded],
|
||
['dry_run', operator.catalog_record_write_dry_run_artifact_path_recorded],
|
||
['commit_gate', operator.operator_confirmed_report_catalog_record_commit_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_record_write && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['allowed', promotion.allowed],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['requires_cli_run', promotion.requires_cli_run],
|
||
['requires_real_db_write', promotion.requires_real_db_write],
|
||
['run_package_gate', promotion.report_catalog_record_run_package_requires_separate_gate],
|
||
['commit_gate', promotion.report_catalog_record_commit_requires_separate_gate],
|
||
['api_must_not_write_database', promotion.api_must_not_write_database]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch report catalog record write gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWriteEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record write gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const recordWrite = data.telegram_dispatch_report_catalog_record_write || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_run_package || {};
|
||
const sections = data.telegram_dispatch_report_catalog_record_run_package_sections || [];
|
||
const command = data.command_bundle || {};
|
||
const manifest = data.payload_manifest || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`run_package=${data.telegram_dispatch_report_catalog_record_run_package_passed ? 'pass' : 'blocked'}`,
|
||
`readiness=${data.ready_for_market_intel_report_catalog_record_run_readiness ? 'ready' : 'blocked'}`,
|
||
`cli=${data.catalog_record_cli_executed ? 'executed' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只產生 Telegram dispatch report catalog record run package 預覽;API/UI 不讀 token、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE SECTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
sections.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.facts || []).join(' / '))}</small>
|
||
</div>
|
||
<span>READY</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run package sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECORD WRITE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', recordWrite.provided],
|
||
['mode', recordWrite.mode || 'missing'],
|
||
['record_write_passed', recordWrite.catalog_record_write_passed],
|
||
['cli_ready', recordWrite.ready_for_market_intel_report_catalog_record_cli_run],
|
||
['report_family', recordWrite.target_report_family || 'missing'],
|
||
['report_hash', recordWrite.report_output_hash || 'missing'],
|
||
['dry_run', recordWrite.catalog_record_write_dry_run_artifact_path_recorded]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PACKAGE INPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['package_path', operator.report_catalog_record_run_package_artifact_path || 'missing'],
|
||
['write_gate_path', operator.report_catalog_record_write_artifact_path_recorded],
|
||
['manifest_path', operator.catalog_record_payload_manifest_path_recorded],
|
||
['backup', operator.catalog_record_backup_artifact_path_recorded],
|
||
['dry_run', operator.catalog_record_write_dry_run_artifact_path_recorded],
|
||
['cli_command', operator.catalog_record_cli_command_recorded],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_record_run_package && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND / PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['script', command.script_path || 'missing'],
|
||
['api_must_not_execute', command.api_must_not_execute_command],
|
||
['manifest_execute_in_api', manifest.execute_in_api],
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['run_readiness_gate', promotion.report_catalog_record_run_readiness_requires_separate_gate],
|
||
['commit_gate', promotion.report_catalog_record_commit_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">產生 queue review AI summary Telegram dispatch report catalog record run package 預覽中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackageEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record run package 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const runPackage = data.telegram_dispatch_report_catalog_record_run_package || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_run_readiness || {};
|
||
const manifest = data.catalog_record_run_readiness_manifest || {};
|
||
const command = manifest.manual_cli_run_command || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`readiness=${data.telegram_dispatch_report_catalog_record_run_readiness_passed ? 'pass' : 'blocked'}`,
|
||
`operator_cli=${data.ready_for_cli_operator_run ? 'ready' : 'blocked'}`,
|
||
`api_cli=${data.api_executes_cli ? 'executed' : 'blocked'}`,
|
||
`db=${data.database_write_executed ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 Telegram dispatch report catalog record run readiness;API/UI 不讀 token、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run readiness gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN PACKAGE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', runPackage.provided],
|
||
['mode', runPackage.mode || 'missing'],
|
||
['package_passed', runPackage.catalog_record_run_package_passed],
|
||
['record_family', runPackage.record_family || 'missing'],
|
||
['statement_count', runPackage.statement_count || 0],
|
||
['report_hash', runPackage.report_output_hash || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['readiness_path', operator.report_catalog_record_run_readiness_artifact_path || 'missing'],
|
||
['run_package_path', operator.report_catalog_record_run_package_artifact_path_recorded],
|
||
['manifest_path', operator.catalog_record_payload_manifest_path_recorded],
|
||
['backup', operator.catalog_record_backup_artifact_path_recorded],
|
||
['dry_run', operator.catalog_record_write_dry_run_artifact_path_recorded],
|
||
['receipt_path', operator.catalog_record_run_receipt_artifact_path_recorded],
|
||
['smoke_path', operator.catalog_record_postwrite_smoke_artifact_path_recorded]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLI BOUNDARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['manifest', manifest.manifest_version || 'missing'],
|
||
['script', command.script_path || 'missing'],
|
||
['executed_by_api', command.executed_by_api],
|
||
['approval_env', command.requires_approval_env_var || 'missing'],
|
||
['operator_shell_only', operator.operator_confirmed_one_time_token_shell_only],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_record_run_readiness && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['requires_cli', promotion.requires_cli_run],
|
||
['requires_db_write', promotion.requires_real_db_write],
|
||
['receipt_gate', promotion.report_catalog_record_run_receipt_requires_separate_gate],
|
||
['commit_gate', promotion.report_catalog_record_commit_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review AI summary Telegram dispatch report catalog record run readiness 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadinessEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record run readiness 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const readiness = data.telegram_dispatch_report_catalog_record_run_readiness || {};
|
||
const writer = data.telegram_dispatch_report_catalog_record_run_receipt_writer || {};
|
||
const smoke = data.telegram_dispatch_report_catalog_record_run_receipt_postwrite_smoke || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_run_receipt || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`receipt=${data.telegram_dispatch_report_catalog_record_run_receipt_passed ? 'pass' : 'blocked'}`,
|
||
`writer=${writer.catalog_record_written ? 'written' : 'blocked'}`,
|
||
`smoke=${smoke.postwrite_smoke_passed ? 'pass' : 'blocked'}`,
|
||
`api_db=${data.api_writes_database ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核外部 CLI 回貼的 Telegram dispatch report catalog record run receipt;API/UI 不讀 token、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RECEIPT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 run receipt gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">READINESS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', readiness.provided],
|
||
['mode', readiness.mode || 'missing'],
|
||
['passed', readiness.run_readiness_passed],
|
||
['record_family', readiness.record_family || 'missing'],
|
||
['statement_count', readiness.statement_count || 0],
|
||
['expected_hash', readiness.expected_summary_payload_hash || 'missing']
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER OUTPUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['mode', writer.mode || 'missing'],
|
||
['cli_executed', writer.catalog_record_cli_executed],
|
||
['record_written', writer.catalog_record_written],
|
||
['db_write', writer.database_write_executed],
|
||
['commit', writer.database_commit_executed],
|
||
['hash_match', writer.summary_payload_hash_matches_expected]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">POSTWRITE SMOKE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['mode', smoke.mode || 'missing'],
|
||
['read_only', smoke.read_only_query_executed],
|
||
['smoke_passed', smoke.postwrite_smoke_passed],
|
||
['record_found', smoke.catalog_record_found],
|
||
['hash_match', smoke.summary_payload_hash_matches_expected],
|
||
['db_write', smoke.database_write_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['receipt_path', operator.report_catalog_record_run_receipt_artifact_path || 'missing'],
|
||
['readiness_path', operator.report_catalog_record_run_readiness_artifact_path_recorded],
|
||
['writer_path', operator.catalog_record_writer_output_artifact_path_recorded],
|
||
['smoke_path', operator.catalog_record_postwrite_smoke_artifact_path_recorded],
|
||
['commit_gate', operator.operator_confirmed_catalog_record_commit_requires_separate_gate],
|
||
['runtime_clear', operator.operator_confirmed_no_token_in_report_catalog_record_run_receipt && operator.operator_confirmed_no_api_file_write && operator.operator_confirmed_no_api_cli_execution && operator.operator_confirmed_no_api_catalog_record_write && operator.operator_confirmed_no_api_telegram_dispatch && operator.operator_confirmed_no_api_db_write && operator.operator_confirmed_no_llm_call && operator.operator_confirmed_no_scheduler_attach]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['commit_gate', promotion.report_catalog_record_commit_requires_separate_gate],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_cli', promotion.api_must_not_execute_cli],
|
||
['api_catalog_record', promotion.api_must_not_write_catalog_record]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record run receipt 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceiptEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record run receipt 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const receipt = data.telegram_dispatch_report_catalog_record_run_receipt || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_commit || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`commit=${data.telegram_dispatch_report_catalog_record_commit_passed ? 'pass' : 'blocked'}`,
|
||
`receipt=${receipt.run_receipt_passed ? 'pass' : 'blocked'}`,
|
||
`writer_commit=${receipt.writer_database_commit_executed ? 'seen' : 'missing'}`,
|
||
`api_db=${data.api_writes_database ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report catalog record commit gate;API/UI 不讀 token、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不執行 commit、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMIT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 commit gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUN RECEIPT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', receipt.provided],
|
||
['mode', receipt.mode || 'missing'],
|
||
['passed', receipt.run_receipt_passed],
|
||
['record_key', receipt.writer_catalog_record_key || 'missing'],
|
||
['statement_count', receipt.writer_statement_count || 0],
|
||
['hash_match', receipt.writer_hash_match]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMIT EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['writer_db_write', receipt.writer_database_write_executed],
|
||
['writer_commit', receipt.writer_database_commit_executed],
|
||
['writer_rollback', receipt.writer_database_rollback_executed],
|
||
['smoke_read_only', receipt.smoke_read_only_query_executed],
|
||
['smoke_passed', receipt.smoke_postwrite_smoke_passed],
|
||
['smoke_db_write', receipt.smoke_database_write_executed]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['commit_path', operator.report_catalog_record_commit_artifact_path || 'missing'],
|
||
['receipt_path', operator.report_catalog_record_run_receipt_artifact_path_recorded],
|
||
['backup_path', operator.catalog_record_backup_artifact_path_recorded],
|
||
['commit_gate', operator.operator_confirmed_report_catalog_record_commit_gate],
|
||
['db_commit_seen', operator.operator_confirmed_catalog_record_db_commit_observed],
|
||
['closeout_gate', operator.operator_confirmed_catalog_record_closeout_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_file', data.api_writes_file],
|
||
['api_cli', data.api_executes_cli],
|
||
['api_db', data.api_writes_database],
|
||
['db_write', data.database_write_executed],
|
||
['commit_exec', data.catalog_record_commit_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['closeout_gate', promotion.report_catalog_record_closeout_requires_separate_gate],
|
||
['api_commit', promotion.api_must_not_commit_catalog_record],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_catalog_record', promotion.api_must_not_write_catalog_record]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record commit gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommitEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record commit gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const commit = data.telegram_dispatch_report_catalog_record_commit || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_closeout || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.telegram_dispatch_report_catalog_record_closeout_passed ? 'pass' : 'blocked'}`,
|
||
`commit=${commit.commit_passed ? 'pass' : 'blocked'}`,
|
||
`writer_commit=${commit.writer_database_commit_executed ? 'seen' : 'missing'}`,
|
||
`api_db=${data.api_writes_database ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report catalog record closeout gate;API/UI 不讀 token、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不執行 commit、不派 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMIT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', commit.provided],
|
||
['mode', commit.mode || 'missing'],
|
||
['passed', commit.commit_passed],
|
||
['record_key', commit.writer_catalog_record_key || 'missing'],
|
||
['statement_count', commit.writer_statement_count || 0],
|
||
['hash_match', commit.writer_hash_match]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMIT EVIDENCE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['db_commit', commit.writer_database_commit_executed],
|
||
['smoke_read_only', commit.smoke_read_only_query_executed],
|
||
['smoke_passed', commit.smoke_postwrite_smoke_passed],
|
||
['operator_commit', commit.operator_commit_gate],
|
||
['operator_db_seen', commit.operator_db_commit_observed],
|
||
['closeout_gate', commit.operator_closeout_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['closeout_path', operator.report_catalog_record_closeout_artifact_path || 'missing'],
|
||
['commit_path', operator.report_catalog_record_commit_artifact_path_recorded],
|
||
['receipt_path', operator.report_catalog_record_run_receipt_artifact_path_recorded],
|
||
['backup_path', operator.catalog_record_backup_artifact_path_recorded],
|
||
['closeout_gate', operator.operator_confirmed_report_catalog_record_closeout_gate],
|
||
['archive_gate', operator.operator_confirmed_catalog_record_archive_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_file', data.api_writes_file],
|
||
['api_cli', data.api_executes_cli],
|
||
['api_db', data.api_writes_database],
|
||
['db_write', data.database_write_executed],
|
||
['closeout_exec', data.catalog_record_closeout_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['archive_gate', promotion.report_catalog_record_archive_requires_separate_gate],
|
||
['api_commit', promotion.api_must_not_commit_catalog_record],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record closeout gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseoutEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record closeout gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const closeout = data.telegram_dispatch_report_catalog_record_closeout || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_archive || {};
|
||
const promotion = data.promotion_gate || {};
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`archive=${data.telegram_dispatch_report_catalog_record_archive_passed ? 'pass' : 'blocked'}`,
|
||
`closeout=${closeout.closeout_passed ? 'pass' : 'blocked'}`,
|
||
`summary=${data.ready_for_market_intel_report_catalog_record_archive_summary ? 'ready' : 'blocked'}`,
|
||
`api_file=${data.api_writes_file ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report catalog record archive gate;API/UI 不讀 token、不產報表、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不執行 commit、不派 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">CLOSEOUT</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', closeout.provided],
|
||
['mode', closeout.mode || 'missing'],
|
||
['passed', closeout.closeout_passed],
|
||
['record_key', closeout.writer_catalog_record_key || 'missing'],
|
||
['archive_ready', closeout.ready_for_market_intel_report_catalog_record_archive],
|
||
['safe_boundaries', closeout.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['archive_path', operator.report_catalog_record_archive_artifact_path || 'missing'],
|
||
['closeout_path', operator.report_catalog_record_closeout_artifact_path_recorded],
|
||
['commit_path', operator.report_catalog_record_commit_artifact_path_recorded],
|
||
['receipt_path', operator.report_catalog_record_run_receipt_artifact_path_recorded],
|
||
['archive_gate', operator.operator_confirmed_report_catalog_record_archive_gate],
|
||
['summary_gate', operator.operator_confirmed_catalog_record_archive_summary_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_report', data.ready_for_report_generation],
|
||
['api_file', data.api_writes_file],
|
||
['api_cli', data.api_executes_cli],
|
||
['api_db', data.api_writes_database],
|
||
['archive_exec', data.catalog_record_archive_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['summary_gate', promotion.report_catalog_record_archive_summary_requires_separate_gate],
|
||
['api_commit', promotion.api_must_not_commit_catalog_record],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record archive gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record archive gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const archive = data.telegram_dispatch_report_catalog_record_archive || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_archive_summary || {};
|
||
const promotion = data.promotion_gate || {};
|
||
const sections = data.telegram_dispatch_report_catalog_record_archive_summary_sections || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`summary=${data.telegram_dispatch_report_catalog_record_archive_summary_passed ? 'pass' : 'blocked'}`,
|
||
`archive=${archive.archive_passed ? 'pass' : 'blocked'}`,
|
||
`final=${data.ready_for_market_intel_report_catalog_record_final_closeout ? 'ready' : 'blocked'}`,
|
||
`file=${data.catalog_record_archive_summary_file_written ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report catalog record archive summary gate;API/UI 不讀 token、不產報表、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不執行 commit、不派 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SUMMARY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 archive summary gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', archive.provided],
|
||
['mode', archive.mode || 'missing'],
|
||
['passed', archive.archive_passed],
|
||
['record_key', archive.writer_catalog_record_key || 'missing'],
|
||
['summary_ready', archive.ready_for_market_intel_report_catalog_record_archive_summary],
|
||
['safe_boundaries', archive.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SECTIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
sections.map(section => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</article>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 summary sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['summary_path', operator.report_catalog_record_archive_summary_artifact_path || 'missing'],
|
||
['archive_path', operator.report_catalog_record_archive_artifact_path_recorded],
|
||
['closeout_path', operator.report_catalog_record_closeout_artifact_path_recorded],
|
||
['traceability', operator.operator_confirmed_catalog_record_traceability_reviewed],
|
||
['summary_gate', operator.operator_confirmed_report_catalog_record_archive_summary_gate],
|
||
['final_gate', operator.operator_confirmed_catalog_record_final_closeout_requires_separate_gate]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_report', data.ready_for_report_generation],
|
||
['api_file', data.api_writes_file],
|
||
['api_cli', data.api_executes_cli],
|
||
['api_db', data.api_writes_database],
|
||
['summary_exec', data.catalog_record_archive_summary_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['final_gate', promotion.report_catalog_record_final_closeout_requires_separate_gate],
|
||
['api_commit', promotion.api_must_not_commit_catalog_record],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record archive summary gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummaryEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record archive summary gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const gates = data.gates || [];
|
||
const summary = data.telegram_dispatch_report_catalog_record_archive_summary || {};
|
||
const operator = data.operator_telegram_dispatch_report_catalog_record_final_closeout || {};
|
||
const promotion = data.promotion_gate || {};
|
||
const sections = data.telegram_dispatch_report_catalog_record_final_closeout_sections || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`closeout=${data.telegram_dispatch_report_catalog_record_final_closeout_passed ? 'pass' : 'blocked'}`,
|
||
`summary=${summary.archive_summary_passed ? 'pass' : 'blocked'}`,
|
||
`complete=${data.market_intel_report_catalog_record_pipeline_complete ? 'yes' : 'blocked'}`,
|
||
`file=${data.catalog_record_final_closeout_file_written ? 'written' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只審核 Telegram dispatch report catalog record final closeout gate;API/UI 不讀 token、不產報表、不寫檔、不執行 CLI、不開 DB、不寫 catalog record、不執行 commit、不派 Telegram、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">FINAL GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 final closeout gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">ARCHIVE SUMMARY</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['provided', summary.provided],
|
||
['mode', summary.mode || 'missing'],
|
||
['passed', summary.archive_summary_passed],
|
||
['record_key', summary.writer_catalog_record_key || 'missing'],
|
||
['sections', summary.summary_sections_complete],
|
||
['safe_boundaries', summary.safe_boundaries_complete]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">SECTIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
sections.map(section => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(section.key || 'section')}</strong>
|
||
<small>${escapeHtml((section.facts || []).join(' / '))}</small>
|
||
</article>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 final closeout sections。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">OPERATOR</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['final_path', operator.report_catalog_record_final_closeout_artifact_path || 'missing'],
|
||
['summary_path', operator.report_catalog_record_archive_summary_artifact_path_recorded],
|
||
['traceability', operator.operator_confirmed_catalog_record_traceability_reviewed],
|
||
['pipeline', operator.operator_confirmed_catalog_record_pipeline_complete],
|
||
['no_followup', operator.operator_confirmed_no_pending_catalog_record_followup],
|
||
['read_only', operator.operator_confirmed_catalog_record_final_closeout_read_only]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">RUNTIME</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['api_report', data.ready_for_report_generation],
|
||
['api_file', data.api_writes_file],
|
||
['api_cli', data.api_executes_cli],
|
||
['api_db', data.api_writes_database],
|
||
['final_exec', data.catalog_record_final_closeout_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">PROMOTION</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['next_phase', promotion.next_manual_phase || 'missing'],
|
||
['terminal', promotion.terminal_gate],
|
||
['api_commit', promotion.api_must_not_commit_catalog_record],
|
||
['api_write', promotion.api_must_not_write_database],
|
||
['api_telegram', promotion.api_must_not_dispatch_telegram]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">審核 queue review AI summary Telegram dispatch report catalog record final closeout gate 中...</div>';
|
||
try {
|
||
const response = await fetch(`${sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseoutEndpoint}?execute=false&apply_real_write=false`, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review AI summary Telegram dispatch report catalog record final closeout gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCandidateQueueReviewDecisionWriter = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const summary = data.statement_summary || {};
|
||
const command = data.command_bundle || {};
|
||
const gates = data.approval_gates || [];
|
||
sampleReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_real_write ? 'yes' : 'no'}`,
|
||
`statements=${summary.statement_count || 0}`,
|
||
`api_update=${data.api_updates_review_state ? 'yes' : 'no'}`,
|
||
`impl=${data.writer_implementation_enabled ? 'enabled' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
sampleReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只檢查 queue review decision writer CLI gate;API/UI 不讀 token、不執行 CLI、不連 DB、不更新 review_state。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITER GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(gate => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(gate.key)}</strong>
|
||
<small>${escapeHtml(gate.label)}</small>
|
||
</div>
|
||
<span>${gate.passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 writer gates。</div>'
|
||
}</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">COMMAND SHAPE</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['script_path', data.script_path || 'unknown'],
|
||
['env_var', data.approval_env_var || 'unknown'],
|
||
['dry_run', command.dry_run_command || 'not_ready'],
|
||
['api_must_not_execute', command.api_must_not_execute_command]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">WRITE FLAGS</p>
|
||
<div class="market-intel-check-list">
|
||
${[
|
||
['execute_requested', data.execute_requested],
|
||
['apply_real_write', data.apply_real_write_requested],
|
||
['token_present', data.approval_token_present],
|
||
['token_valid', data.approval_token_valid],
|
||
['db_write', data.database_write_executed],
|
||
['commit', data.database_commit_executed],
|
||
['scheduler', data.scheduler_attached]
|
||
].map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div><strong>${escapeHtml(key)}</strong></div>
|
||
<span>${escapeHtml(String(value))}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="market-intel-deploy-section-title">STATEMENTS</p>
|
||
<div class="market-intel-check-list">${
|
||
(summary.review_state_updates || []).map(row => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(row.dedupe_key || row.idempotency_key || 'unknown')}</strong>
|
||
<small>${escapeHtml(row.expected_current_review_state || 'unknown')} -> ${escapeHtml(row.next_review_state || 'none')}</small>
|
||
</div>
|
||
<span>${escapeHtml(row.write_status || 'blocked')}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 review_state update statements。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCandidateQueueReviewDecisionWriter = async () => {
|
||
if (!sampleReviewMeta || !sampleReviewBody || !sampleReviewInput) return;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(sampleReviewInput.value || '{}');
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">json_error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">JSON 格式錯誤:${escapeHtml(error.message)}</div>`;
|
||
return;
|
||
}
|
||
const body = parsed && parsed.sample_result ? parsed : { sample_result: parsed };
|
||
sampleReviewBody.innerHTML = '<div class="market-intel-empty">檢查 queue review decision writer CLI gate 中...</div>';
|
||
try {
|
||
const response = await fetch(sampleCandidateQueueReviewDecisionWriterEndpoint, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok && !data.mode) throw new Error(`HTTP ${response.status}`);
|
||
renderCandidateQueueReviewDecisionWriter(data);
|
||
} catch (error) {
|
||
sampleReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
sampleReviewBody.innerHTML = `<div class="market-intel-empty">queue review decision writer CLI gate 失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderSchedulerMeta = data => {
|
||
schedulerMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_to_attach_scheduler ? 'yes' : 'no'}`,
|
||
`jobs=${data.job_count || 0}`,
|
||
`scheduler=${data.scheduler_attached ? 'on' : 'off'}`,
|
||
`crawler=${data.crawler_job_started ? 'started' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderSchedulerBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const jobs = data.jobs || [];
|
||
const sequence = data.attach_sequence || [];
|
||
const fallback = data.fallback_plan || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderJob = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)} / ${escapeHtml(item.cadence)}</small>
|
||
<small>${escapeHtml(item.entrypoint)} / max=${escapeHtml(item.max_runtime_minutes)}m</small>
|
||
<small>network=${item.requires_external_network ? 'yes' : 'no'} / write=${item.requires_database_write ? 'yes' : 'no'}</small>
|
||
</article>
|
||
`;
|
||
const renderPlanItem = (item, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || status)}</strong>
|
||
<small>${escapeHtml(item.label || item)}</small>
|
||
</div>
|
||
<span>${escapeHtml(status).toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
schedulerBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">這是排程掛載計畫,不會從 API 註冊 job、不啟動 crawler、不連外、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-scheduler-checks>
|
||
<p class="market-intel-deploy-section-title">SCHEDULER GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供排程 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-scheduler-jobs>
|
||
<p class="market-intel-deploy-section-title">PLANNED JOBS</p>
|
||
<div class="market-intel-operation-list">${
|
||
jobs.length
|
||
? jobs.map(renderJob).join('')
|
||
: '<div class="market-intel-empty">尚未提供排程 job。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-scheduler-sequence>
|
||
<p class="market-intel-deploy-section-title">ATTACH SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map(item => renderPlanItem(item, 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供掛載順序。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-scheduler-fallback>
|
||
<p class="market-intel-deploy-section-title">FALLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
fallback.length
|
||
? fallback.map(item => renderPlanItem(item, 'ready')).join('')
|
||
: '<div class="market-intel-empty">尚未提供備援方案。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadScheduler = async () => {
|
||
if (!schedulerMeta || !schedulerBody) return;
|
||
schedulerBody.innerHTML = '<div class="market-intel-empty">讀取排程掛載計畫中...</div>';
|
||
try {
|
||
const response = await fetch(schedulerEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderSchedulerMeta(data);
|
||
renderSchedulerBody(data);
|
||
} catch (error) {
|
||
schedulerMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
schedulerBody.innerHTML = `<div class="market-intel-empty">排程掛載計畫讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMatchReviewMeta = data => {
|
||
const thresholds = data.thresholds || {};
|
||
matchReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_review_queue ? 'yes' : 'no'}`,
|
||
`signals=${(data.scoring_signals || []).length}`,
|
||
`auto_confirm=${thresholds.auto_confirm_enabled ? 'on' : 'off'}`,
|
||
`writes=${data.writes_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMatchReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const signals = data.scoring_signals || [];
|
||
const actions = data.review_actions || [];
|
||
const sequence = data.operator_sequence || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderSignal = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>weight=${escapeHtml(item.weight)} / ${escapeHtml(item.source)}</small>
|
||
</article>
|
||
`;
|
||
const renderAction = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${item.requires_operator ? 'HITL' : 'AUTO'}</span>
|
||
</div>
|
||
`;
|
||
matchReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">商品比對目前只建立審核規則與 HITL 流程;不建立 queue、不自動確認、不寫 DB、不呼叫外部 MCP。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-match-review-checks>
|
||
<p class="market-intel-deploy-section-title">MATCH GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供比對 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-match-review-signals>
|
||
<p class="market-intel-deploy-section-title">SCORING SIGNALS</p>
|
||
<div class="market-intel-operation-list">${
|
||
signals.length
|
||
? signals.map(renderSignal).join('')
|
||
: '<div class="market-intel-empty">尚未提供比對訊號。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-match-review-actions>
|
||
<p class="market-intel-deploy-section-title">REVIEW ACTIONS</p>
|
||
<div class="market-intel-check-list">${
|
||
actions.length
|
||
? actions.map(renderAction).join('')
|
||
: '<div class="market-intel-empty">尚未提供審核動作。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-match-review-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供操作順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMatchReview = async () => {
|
||
if (!matchReviewMeta || !matchReviewBody) return;
|
||
matchReviewBody.innerHTML = '<div class="market-intel-empty">讀取商品比對審核計畫中...</div>';
|
||
try {
|
||
const response = await fetch(matchReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMatchReviewMeta(data);
|
||
renderMatchReviewBody(data);
|
||
} catch (error) {
|
||
matchReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
matchReviewBody.innerHTML = `<div class="market-intel-empty">商品比對審核計畫讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderOpportunityMeta = data => {
|
||
opportunityMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_opportunity_queue ? 'yes' : 'no'}`,
|
||
`rules=${data.rule_count || 0}`,
|
||
`alerts=${data.threat_alert_dispatched ? 'sent' : 'blocked'}`,
|
||
`ai=${data.ai_summary_generated ? 'generated' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderOpportunityBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const rules = data.rules || [];
|
||
const sequence = data.operator_sequence || [];
|
||
const severity = Object.entries(data.severity_policy || {});
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderRule = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)} / severity=${escapeHtml(item.severity)}</small>
|
||
<small>${escapeHtml((item.minimum_evidence || []).join(' + '))}</small>
|
||
<small>${escapeHtml(item.action_hint)}</small>
|
||
</article>
|
||
`;
|
||
opportunityBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">目前只建立機會與威脅判讀規則;不建立 queue、不派送 Telegram、不產生 AI 摘要、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-opportunity-checks>
|
||
<p class="market-intel-deploy-section-title">OPPORTUNITY GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供機會判讀 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-rules>
|
||
<p class="market-intel-deploy-section-title">RULES</p>
|
||
<div class="market-intel-operation-list">${
|
||
rules.length
|
||
? rules.map(renderRule).join('')
|
||
: '<div class="market-intel-empty">尚未提供規則。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-severity>
|
||
<p class="market-intel-deploy-section-title">SEVERITY POLICY</p>
|
||
<div class="market-intel-check-list">${
|
||
severity.length
|
||
? severity.map(([key, label]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
<small>${escapeHtml(label)}</small>
|
||
</div>
|
||
<span>POLICY</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供分級策略。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供操作順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadOpportunity = async () => {
|
||
if (!opportunityMeta || !opportunityBody) return;
|
||
opportunityBody.innerHTML = '<div class="market-intel-empty">讀取市場機會與威脅計畫中...</div>';
|
||
try {
|
||
const response = await fetch(opportunityEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderOpportunityMeta(data);
|
||
renderOpportunityBody(data);
|
||
} catch (error) {
|
||
opportunityMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
opportunityBody.innerHTML = `<div class="market-intel-empty">市場機會與威脅計畫讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderOpportunityScoringMeta = data => {
|
||
opportunityScoringMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_scoring_job ? 'yes' : 'no'}`,
|
||
`dimensions=${data.dimension_count || 0}`,
|
||
`weight=${data.total_weight || 0}`,
|
||
`score=${data.score_calculation_executed ? 'executed' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderOpportunityScoringBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const dimensions = data.dimensions || [];
|
||
const thresholds = data.thresholds || [];
|
||
const evidenceTables = data.required_evidence_tables || [];
|
||
const sequence = data.operator_sequence || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderDimension = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)} / ${escapeHtml(item.weight)}%</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>${escapeHtml((item.evidence || []).join(' + '))}</small>
|
||
<small>${escapeHtml(item.calculation)}</small>
|
||
</article>
|
||
`;
|
||
opportunityScoringBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">目前只定義機會與威脅分數模型;不計分、不建立 queue、不派送 Telegram、不產生 AI 摘要、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-opportunity-scoring-checks>
|
||
<p class="market-intel-deploy-section-title">SCORING GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供分數 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-scoring-dimensions>
|
||
<p class="market-intel-deploy-section-title">DIMENSIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
dimensions.length
|
||
? dimensions.map(renderDimension).join('')
|
||
: '<div class="market-intel-empty">尚未提供分數欄位。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-scoring-thresholds>
|
||
<p class="market-intel-deploy-section-title">THRESHOLDS</p>
|
||
<div class="market-intel-check-list">${
|
||
thresholds.length
|
||
? thresholds.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.level)}</strong>
|
||
<small>${escapeHtml(item.action)}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.minimum_score)}+</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供門檻。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-scoring-evidence>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE TABLES</p>
|
||
<div class="market-intel-check-list">${
|
||
evidenceTables.length
|
||
? evidenceTables.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item)}</strong>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 evidence tables。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-scoring-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供操作順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadOpportunityScoring = async () => {
|
||
if (!opportunityScoringMeta || !opportunityScoringBody) return;
|
||
opportunityScoringBody.innerHTML = '<div class="market-intel-empty">讀取機會威脅分數模型中...</div>';
|
||
try {
|
||
const response = await fetch(opportunityScoringEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderOpportunityScoringMeta(data);
|
||
renderOpportunityScoringBody(data);
|
||
} catch (error) {
|
||
opportunityScoringMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
opportunityScoringBody.innerHTML = `<div class="market-intel-empty">機會威脅分數模型讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderOpportunityEvidenceMeta = data => {
|
||
opportunityEvidenceMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_evidence_bundle ? 'yes' : 'no'}`,
|
||
`sections=${data.section_count || 0}`,
|
||
`query=${data.evidence_query_executed ? 'yes' : 'no'}`,
|
||
`bundle=${data.evidence_bundle_created ? 'created' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderOpportunityEvidenceBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const sections = data.sections || [];
|
||
const gates = data.escalation_gates || [];
|
||
const tables = data.required_market_tables || [];
|
||
const sequence = data.operator_sequence || [];
|
||
const contract = data.bundle_contract || {};
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderSection = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
<small>${escapeHtml((item.source_tables || []).join(' + '))}</small>
|
||
<small>${escapeHtml((item.required_fields || []).join(' / '))}</small>
|
||
</article>
|
||
`;
|
||
opportunityEvidenceBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">目前只定義 evidence bundle contract;不查 DB、不產生 sample evidence、不建立 alert candidate、不派送 Telegram、不產生 AI 摘要。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">dedupe=${escapeHtml(contract.dedupe_key || '')} / freshness=${escapeHtml(contract.freshness_window_hours || 'n/a')}h</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-opportunity-evidence-checks>
|
||
<p class="market-intel-deploy-section-title">EVIDENCE GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 evidence gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-evidence-sections>
|
||
<p class="market-intel-deploy-section-title">BUNDLE SECTIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
sections.length
|
||
? sections.map(renderSection).join('')
|
||
: '<div class="market-intel-empty">尚未提供 evidence section。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-evidence-escalation>
|
||
<p class="market-intel-deploy-section-title">ESCALATION GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${escapeHtml((item.required_for || []).join('/'))}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供升級 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-evidence-tables>
|
||
<p class="market-intel-deploy-section-title">REQUIRED TABLES</p>
|
||
<div class="market-intel-check-list">${
|
||
tables.length
|
||
? tables.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item)}</strong>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 required tables。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-evidence-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供操作順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadOpportunityEvidence = async () => {
|
||
if (!opportunityEvidenceMeta || !opportunityEvidenceBody) return;
|
||
opportunityEvidenceBody.innerHTML = '<div class="market-intel-empty">讀取機會威脅證據包中...</div>';
|
||
try {
|
||
const response = await fetch(opportunityEvidenceEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderOpportunityEvidenceMeta(data);
|
||
renderOpportunityEvidenceBody(data);
|
||
} catch (error) {
|
||
opportunityEvidenceMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
opportunityEvidenceBody.innerHTML = `<div class="market-intel-empty">機會威脅證據包讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderOpportunityAlertMeta = data => {
|
||
opportunityAlertMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_alert_candidates ? 'yes' : 'no'}`,
|
||
`channels=${data.channel_count || 0}`,
|
||
`review=${data.review_state_count || 0}`,
|
||
`queue_contract=${data.review_queue_contract_defined ? 'defined' : 'missing'}`,
|
||
`telegram=${data.telegram_dispatched ? 'sent' : 'blocked'}`,
|
||
`llm=${data.llm_call_executed ? 'called' : 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderOpportunityAlertBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.gate_checks || {});
|
||
const channels = data.channels || [];
|
||
const gates = data.alert_gates || [];
|
||
const throttle = data.throttle_policy || {};
|
||
const reviewStates = data.review_states || [];
|
||
const reviewActions = data.review_actions || [];
|
||
const queueContract = data.review_queue_contract || {};
|
||
const queueIndexes = data.review_queue_indexes || [];
|
||
const priorityLanes = data.review_priority_lanes || [];
|
||
const approval = data.approval_policy || {};
|
||
const sequence = data.operator_sequence || [];
|
||
const payload = data.payload_contract || {};
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderChannel = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)} / minimum=${escapeHtml(item.minimum_level)}</small>
|
||
<small>approval=${item.requires_operator_approval ? 'required' : 'not_required'} / target=${escapeHtml(item.dispatch_target)}</small>
|
||
</article>
|
||
`;
|
||
const renderReviewState = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)} / ${escapeHtml(item.description)}</small>
|
||
</div>
|
||
<span>${item.dispatch_allowed ? 'DISPATCH' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderReviewAction = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.from || []).join(' / '))} → ${escapeHtml(item.to)}</small>
|
||
<small>reason=${item.requires_reason ? 'required' : 'optional'}</small>
|
||
</article>
|
||
`;
|
||
const renderPriorityLane = item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)} / threshold=${escapeHtml(item.minimum_threshold)}</small>
|
||
<small>max_age=${escapeHtml(item.max_age_hours)}h</small>
|
||
</article>
|
||
`;
|
||
const renderQueueIndex = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml((item.columns || []).join(' / '))} · ${escapeHtml(item.purpose)}</small>
|
||
</div>
|
||
<span>PLANNED</span>
|
||
</div>
|
||
`;
|
||
opportunityAlertBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">目前只定義告警候選、人工審核流程與審核佇列資料契約;不建立 alert queue、不建立 review queue、不建立 review table、不執行審核動作、不派送 Telegram、不呼叫 LLM、不寫 DB。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">dedupe=${escapeHtml(throttle.dedupe_window_hours || 'n/a')}h / telegram_daily=${escapeHtml(throttle.max_telegram_candidates_per_day || 0)} / ai_digest=${escapeHtml(throttle.max_ai_briefing_items_per_digest || 0)}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-opportunity-alert-checks>
|
||
<p class="market-intel-deploy-section-title">ALERT GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供告警 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-channels>
|
||
<p class="market-intel-deploy-section-title">CHANNELS</p>
|
||
<div class="market-intel-operation-list">${
|
||
channels.length
|
||
? channels.map(renderChannel).join('')
|
||
: '<div class="market-intel-empty">尚未提供 channel。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-escalation>
|
||
<p class="market-intel-deploy-section-title">ESCALATION</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.length
|
||
? gates.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${escapeHtml((item.required_for || []).join('/'))}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供升級 gate。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-payload>
|
||
<p class="market-intel-deploy-section-title">PAYLOAD CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>required_fields</strong>
|
||
<small>${escapeHtml((payload.required_fields || []).join(' / '))}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>forbidden_fields</strong>
|
||
<small>${escapeHtml((payload.forbidden_fields || []).join(' / '))}</small>
|
||
</div>
|
||
<span>BLOCKED</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-review>
|
||
<p class="market-intel-deploy-section-title">REVIEW STATES</p>
|
||
<div class="market-intel-check-list">${
|
||
reviewStates.length
|
||
? reviewStates.map(renderReviewState).join('')
|
||
: '<div class="market-intel-empty">尚未提供審核狀態。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-actions>
|
||
<p class="market-intel-deploy-section-title">REVIEW ACTIONS</p>
|
||
<div class="market-intel-operation-list">${
|
||
reviewActions.length
|
||
? reviewActions.map(renderReviewAction).join('')
|
||
: '<div class="market-intel-empty">尚未提供審核操作。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-queue-contract>
|
||
<p class="market-intel-deploy-section-title">REVIEW QUEUE CONTRACT</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(queueContract.table_name || 'market_alert_review_queue')}</strong>
|
||
<small>pk=${escapeHtml(queueContract.primary_key || 'id')}</small>
|
||
</div>
|
||
<span>PREVIEW</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>required_fields</strong>
|
||
<small>${escapeHtml((queueContract.required_fields || []).join(' / '))}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>audit_fields</strong>
|
||
<small>${escapeHtml((queueContract.audit_fields || []).join(' / '))}</small>
|
||
</div>
|
||
<span>AUDIT</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-priority-lanes>
|
||
<p class="market-intel-deploy-section-title">PRIORITY LANES</p>
|
||
<div class="market-intel-operation-list">${
|
||
priorityLanes.length
|
||
? priorityLanes.map(renderPriorityLane).join('')
|
||
: '<div class="market-intel-empty">尚未提供優先級 lane。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-queue-indexes>
|
||
<p class="market-intel-deploy-section-title">QUEUE INDEXES</p>
|
||
<div class="market-intel-check-list">${
|
||
queueIndexes.length
|
||
? queueIndexes.map(renderQueueIndex).join('')
|
||
: '<div class="market-intel-empty">尚未提供索引規劃。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-approval>
|
||
<p class="market-intel-deploy-section-title">APPROVAL POLICY</p>
|
||
<div class="market-intel-check-list">${
|
||
Object.entries(approval).map(([key, value]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(key)}</strong>
|
||
</div>
|
||
<span>${value ? 'REQUIRED' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-opportunity-alert-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.length
|
||
? sequence.map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>REQUIRED</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供操作順序。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadOpportunityAlert = async () => {
|
||
if (!opportunityAlertMeta || !opportunityAlertBody) return;
|
||
opportunityAlertBody.innerHTML = '<div class="market-intel-empty">讀取機會威脅告警候選中...</div>';
|
||
try {
|
||
const response = await fetch(opportunityAlertEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderOpportunityAlertMeta(data);
|
||
renderOpportunityAlertBody(data);
|
||
} catch (error) {
|
||
opportunityAlertMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
opportunityAlertBody.innerHTML = `<div class="market-intel-empty">機會威脅告警候選讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMigrationMeta = data => {
|
||
const seedWriter = data.command_plan && data.command_plan.seed_writer_command
|
||
? data.command_plan.seed_writer_command
|
||
: {};
|
||
migrationMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`tables=${data.table_count || 0}`,
|
||
`file=${data.file_created ? 'created' : 'preview'}`,
|
||
`executed=${data.migration_executed ? 'yes' : 'no'}`,
|
||
`additive=${data.safety_checks && data.safety_checks.forward_sql_additive_only ? 'yes' : 'no'}`,
|
||
`seed_script=${seedWriter.script_created ? 'created' : 'missing'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMigrationBody = data => {
|
||
const operations = (data.table_operations || []).slice(0, 8);
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const commands = data.command_plan || {};
|
||
const migrationCommand = commands.migration_apply_command || {};
|
||
const seedCommand = commands.seed_writer_command || {};
|
||
migrationBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">
|
||
建議檔名:${escapeHtml(data.suggested_filename || '')}。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}
|
||
</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-migration-tables>
|
||
<p class="market-intel-deploy-section-title">TABLE DRAFT</p>
|
||
<div class="market-intel-operation-list">${
|
||
operations.map(item => `
|
||
<article class="market-intel-operation">
|
||
<strong>${escapeHtml(item.table)}</strong>
|
||
<small>${escapeHtml(item.operation)} / ${escapeHtml(item.write_status)}</small>
|
||
</article>
|
||
`).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-migration-commands>
|
||
<p class="market-intel-deploy-section-title">COMMAND DESIGN</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>migration_apply_command</strong>
|
||
<small>${escapeHtml(migrationCommand.command || '')}</small>
|
||
</div>
|
||
<span>${migrationCommand.executed ? 'EXECUTED' : 'BLOCKED'}</span>
|
||
</div>
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>seed_writer_command</strong>
|
||
<small>${escapeHtml(seedCommand.command || '')}</small>
|
||
<small>${escapeHtml(seedCommand.notes || '')}</small>
|
||
</div>
|
||
<span>${seedCommand.executed ? 'EXECUTED' : seedCommand.script_created ? 'SCRIPT' : 'DESIGN'}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMigration = async () => {
|
||
if (!migrationMeta || !migrationBody) return;
|
||
migrationBody.innerHTML = '<div class="market-intel-empty">讀取 migration 草案中...</div>';
|
||
try {
|
||
const response = await fetch(migrationEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMigrationMeta(data);
|
||
renderMigrationBody(data);
|
||
} catch (error) {
|
||
migrationMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
migrationBody.innerHTML = `<div class="market-intel-empty">migration 草案讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderMigrationDrillMeta = data => {
|
||
migrationDrillMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`schema=${data.schema_state || 'unknown'}`,
|
||
`probe=${data.read_only_query_executed ? 'read-only' : 'planned'}`,
|
||
`review=${data.drill_ready_for_operator_review ? 'ready' : 'blocked'}`,
|
||
`apply=${data.ready_to_apply_migration ? 'yes' : 'manual'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderMigrationDrillBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.checks || {});
|
||
const preApply = data.pre_apply_checklist || [];
|
||
const postApply = data.post_apply_verification || [];
|
||
const rollback = data.rollback_drill || {};
|
||
const risks = data.risk_register || [];
|
||
const commands = data.manual_commands || {};
|
||
const schema = data.schema_db_probe_summary || {};
|
||
const seedDiff = data.platform_seed_db_diff_summary || {};
|
||
const renderStep = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || item.label || 'step')}</strong>
|
||
<small>${escapeHtml(item.label || item.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.status || 'required').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
migrationDrillBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此演練只集中正式 migration 前的只讀探測、人工套用清單與回滾演練;API 不執行 psql、不寫 DB、不跑 rollback、不重啟容器。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">schema_missing=${escapeHtml((schema.missing_tables || []).length)} / seed_missing=${escapeHtml((seedDiff.missing_codes || []).length)} / command=${escapeHtml(commands.migration_apply || '')}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-migration-drill-checks>
|
||
<p class="market-intel-deploy-section-title">DRILL CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供 drill check。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-migration-drill-preapply>
|
||
<p class="market-intel-deploy-section-title">PRE-APPLY</p>
|
||
<div class="market-intel-check-list">${
|
||
preApply.length
|
||
? preApply.map(renderStep).join('')
|
||
: '<div class="market-intel-empty">尚未提供套用前清單。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-migration-drill-postapply>
|
||
<p class="market-intel-deploy-section-title">POST-APPLY</p>
|
||
<div class="market-intel-check-list">${
|
||
postApply.length
|
||
? postApply.map(renderStep).join('')
|
||
: '<div class="market-intel-empty">尚未提供套用後驗證。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-migration-drill-rollback>
|
||
<p class="market-intel-deploy-section-title">ROLLBACK DRILL</p>
|
||
<div class="market-intel-check-list">
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(rollback.mode || 'rollback_drill')}</strong>
|
||
<small>${escapeHtml(rollback.manual_command_shape || '')}</small>
|
||
</div>
|
||
<span>${rollback.rollback_executed ? 'EXECUTED' : 'MANUAL'}</span>
|
||
</div>
|
||
${(rollback.fallback_first || []).map((item, index) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(`fallback_${index + 1}`)}</strong>
|
||
<small>${escapeHtml(item)}</small>
|
||
</div>
|
||
<span>FIRST</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
<div data-market-intel-migration-drill-risks>
|
||
<p class="market-intel-deploy-section-title">RISK REGISTER</p>
|
||
<div class="market-intel-check-list">${
|
||
risks.length
|
||
? risks.map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key)}</strong>
|
||
<small>${escapeHtml(item.label)}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.severity || 'medium').toUpperCase()}</span>
|
||
</div>
|
||
`).join('')
|
||
: '<div class="market-intel-empty">尚未提供風險清單。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadMigrationDrill = async () => {
|
||
if (!migrationDrillMeta || !migrationDrillBody) return;
|
||
migrationDrillBody.innerHTML = '<div class="market-intel-empty">讀取 migration 套用演練中...</div>';
|
||
try {
|
||
const response = await fetch(migrationDrillEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderMigrationDrillMeta(data);
|
||
renderMigrationDrillBody(data);
|
||
} catch (error) {
|
||
migrationDrillMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
migrationDrillBody.innerHTML = `<div class="market-intel-empty">migration 套用演練讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderCatalogReviewMeta = data => {
|
||
catalogReviewMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`catalog=${data.catalog_state || 'unknown'}`,
|
||
`risk=${data.risk_level || 'info'}`,
|
||
`tables=${data.table_catalog ? `${data.table_catalog.existing_count || 0}/${data.table_catalog.expected_count || 0}` : '0/0'}`,
|
||
`apply=${data.apply_path || 'blocked'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderCatalogReviewBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.safe_checks || {});
|
||
const tableCatalog = data.table_catalog || {};
|
||
const seedCatalog = data.seed_catalog || {};
|
||
const findings = data.findings || [];
|
||
const nextSteps = data.operator_next_steps || [];
|
||
const probeTargets = data.manual_probe_targets || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderNamedItem = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || item.label || 'item')}</strong>
|
||
<small>${escapeHtml(item.label || item.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.status || item.severity || 'review').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
catalogReviewBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡只判讀正式 DB catalog 與 platform seed diff 的只讀結果;API 不執行 migration、不寫 DB、不跑 rollback、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">tables=${escapeHtml(tableCatalog.existing_count || 0)}/${escapeHtml(tableCatalog.expected_count || 0)} / missing=${escapeHtml(tableCatalog.missing_count || 0)} / seed_missing=${escapeHtml((seedCatalog.missing_codes || []).length)} / seed_changed=${escapeHtml((seedCatalog.changed_codes || []).length)}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-catalog-review-checks>
|
||
<p class="market-intel-deploy-section-title">SAFE CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 safety checks。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-catalog-review-findings>
|
||
<p class="market-intel-deploy-section-title">CATALOG FINDINGS</p>
|
||
<div class="market-intel-check-list">${
|
||
findings.length
|
||
? findings.map(renderNamedItem).join('')
|
||
: '<div class="market-intel-empty">尚未提供 catalog finding。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-catalog-review-tables>
|
||
<p class="market-intel-deploy-section-title">TABLE STATUS</p>
|
||
<div class="market-intel-check-list">${
|
||
(tableCatalog.table_statuses || []).slice(0, 8).map(item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.table)}</strong>
|
||
</div>
|
||
<span>${item.exists ? 'EXISTS' : 'MISSING'}</span>
|
||
</div>
|
||
`).join('') || '<div class="market-intel-empty">尚未提供 table status。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-catalog-review-next>
|
||
<p class="market-intel-deploy-section-title">NEXT STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
nextSteps.length
|
||
? nextSteps.map(renderNamedItem).join('')
|
||
: '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-catalog-review-probes>
|
||
<p class="market-intel-deploy-section-title">READ-ONLY PROBES</p>
|
||
<div class="market-intel-check-list">${
|
||
probeTargets.length
|
||
? probeTargets.map(item => renderNamedItem({ key: item, label: item, status: 'manual' })).join('')
|
||
: '<div class="market-intel-empty">尚未提供 probe targets。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadCatalogReview = async () => {
|
||
if (!catalogReviewMeta || !catalogReviewBody) return;
|
||
catalogReviewBody.innerHTML = '<div class="market-intel-empty">讀取正式 DB catalog 判讀中...</div>';
|
||
try {
|
||
const response = await fetch(catalogReviewEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderCatalogReviewMeta(data);
|
||
renderCatalogReviewBody(data);
|
||
} catch (error) {
|
||
catalogReviewMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
catalogReviewBody.innerHTML = `<div class="market-intel-empty">正式 DB catalog 判讀讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderLiveSmokeMeta = data => {
|
||
liveSmokeMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`result=${data.smoke_result || 'planned'}`,
|
||
`catalog=${data.catalog_state || 'unknown'}`,
|
||
`passed=${data.live_smoke_passed ? 'yes' : 'no'}`,
|
||
`writes=${data.database_write_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderLiveSmokeBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.safety_checks || {});
|
||
const summary = data.catalog_review_summary || {};
|
||
const tableCatalog = summary.table_catalog || {};
|
||
const seedCatalog = summary.seed_catalog || {};
|
||
const findings = summary.findings || [];
|
||
const steps = data.operator_next_steps || [];
|
||
const targets = data.manual_probe_targets || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderNamedItem = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || item.label || 'item')}</strong>
|
||
<small>${escapeHtml(item.label || item.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(item.status || item.severity || 'manual').toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
liveSmokeBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡預設不跑正式 DB 探測;操作員手動啟動只讀 smoke 模式時,只讀查 catalog / seed diff,不執行 migration、不寫 DB、不跑 rollback。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">tables=${escapeHtml(tableCatalog.existing_count || 0)}/${escapeHtml(tableCatalog.expected_count || 0)} / seed_missing=${escapeHtml((seedCatalog.missing_codes || []).length)} / seed_changed=${escapeHtml((seedCatalog.changed_codes || []).length)} / tolerated_seed_error=${data.seed_probe_error_tolerated ? 'yes' : 'no'}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-live-smoke-checks>
|
||
<p class="market-intel-deploy-section-title">SMOKE CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 smoke checks。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-smoke-findings>
|
||
<p class="market-intel-deploy-section-title">FINDINGS</p>
|
||
<div class="market-intel-check-list">${
|
||
findings.length
|
||
? findings.map(renderNamedItem).join('')
|
||
: '<div class="market-intel-empty">尚未提供 findings。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-smoke-next>
|
||
<p class="market-intel-deploy-section-title">NEXT STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.length
|
||
? steps.map(renderNamedItem).join('')
|
||
: '<div class="market-intel-empty">尚未提供下一步。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-smoke-targets>
|
||
<p class="market-intel-deploy-section-title">MANUAL TARGETS</p>
|
||
<div class="market-intel-check-list">${
|
||
targets.length
|
||
? targets.map(item => renderNamedItem({ key: item, label: item, status: 'manual' })).join('')
|
||
: '<div class="market-intel-empty">尚未提供 targets。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadLiveSmoke = async () => {
|
||
if (!liveSmokeMeta || !liveSmokeBody) return;
|
||
liveSmokeBody.innerHTML = '<div class="market-intel-empty">讀取正式 DB 只讀 smoke 中...</div>';
|
||
try {
|
||
const response = await fetch(liveSmokeEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderLiveSmokeMeta(data);
|
||
renderLiveSmokeBody(data);
|
||
} catch (error) {
|
||
liveSmokeMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
liveSmokeBody.innerHTML = `<div class="market-intel-empty">正式 DB 只讀 smoke 讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderLiveInventoryMeta = data => {
|
||
liveInventoryMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`execute=${data.execute_requested ? 'true' : 'false'}`,
|
||
`query=${data.read_only_query_executed ? 'yes' : 'no'}`,
|
||
`tables=${(data.existing_tables || []).length}/${(data.expected_tables || []).length}`,
|
||
`rows=${data.total_rows || 0}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderLiveInventoryBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.safety_checks || {});
|
||
const tables = data.table_statuses || [];
|
||
const platformRows = data.platform_breakdown || [];
|
||
const campaignRows = data.campaign_status_breakdown || [];
|
||
const productRows = data.product_activity_summary || [];
|
||
const matchRows = data.match_status_breakdown || [];
|
||
const alertRows = data.alert_review_state_breakdown || [];
|
||
const crawlerRows = data.crawler_run_summary || [];
|
||
const renderCheck = ([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`;
|
||
const renderTable = item => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.table)}</strong>
|
||
</div>
|
||
<span>${item.row_count == null ? escapeHtml(item.status || 'planned').toUpperCase() : escapeHtml(item.row_count)}</span>
|
||
</div>
|
||
`;
|
||
const renderRow = (label, detail, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(label)}</strong>
|
||
<small>${escapeHtml(detail)}</small>
|
||
</div>
|
||
<span>${escapeHtml(status)}</span>
|
||
</div>
|
||
`;
|
||
liveInventoryBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">此卡預設只顯示 planned;操作員手動啟動只讀庫存模式時,只查 market_* count / group by,不寫 DB、不跑 migration、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-empty mb-3">summary_ready=${data.summary_ready ? 'yes' : 'no'} / missing=${escapeHtml((data.missing_tables || []).length)} / total_rows=${escapeHtml(data.total_rows || 0)}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-live-inventory-checks>
|
||
<p class="market-intel-deploy-section-title">SAFETY CHECKS</p>
|
||
<div class="market-intel-check-list">${
|
||
checks.length
|
||
? checks.map(renderCheck).join('')
|
||
: '<div class="market-intel-empty">尚未提供 safety checks。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-tables>
|
||
<p class="market-intel-deploy-section-title">TABLE COUNTS</p>
|
||
<div class="market-intel-check-list">${
|
||
tables.length
|
||
? tables.map(renderTable).join('')
|
||
: '<div class="market-intel-empty">尚未提供 table count。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-platforms>
|
||
<p class="market-intel-deploy-section-title">PLATFORMS</p>
|
||
<div class="market-intel-check-list">${
|
||
platformRows.length
|
||
? platformRows.map(item => renderRow(item.code, item.name || '', item.enabled ? 'ON' : 'OFF')).join('')
|
||
: '<div class="market-intel-empty">尚未載入平台資料。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-campaigns>
|
||
<p class="market-intel-deploy-section-title">CAMPAIGNS</p>
|
||
<div class="market-intel-check-list">${
|
||
campaignRows.length
|
||
? campaignRows.map(item => renderRow(item.platform_code, item.status || 'unknown', item.campaign_count || 0)).join('')
|
||
: '<div class="market-intel-empty">尚未有活動資料。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-products>
|
||
<p class="market-intel-deploy-section-title">PRODUCTS</p>
|
||
<div class="market-intel-check-list">${
|
||
productRows.length
|
||
? productRows.map(item => renderRow(item.platform_code, `active=${item.is_active ? 'yes' : 'no'} / latest=${item.latest_seen_at || 'n/a'}`, item.product_count || 0)).join('')
|
||
: '<div class="market-intel-empty">尚未有活動商品資料。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-matches>
|
||
<p class="market-intel-deploy-section-title">MATCHES</p>
|
||
<div class="market-intel-check-list">${
|
||
matchRows.length
|
||
? matchRows.map(item => renderRow(item.match_status || 'unknown', `avg=${item.avg_match_score || 0}`, item.match_count || 0)).join('')
|
||
: '<div class="market-intel-empty">尚未有商品比對資料。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-alerts>
|
||
<p class="market-intel-deploy-section-title">ALERT QUEUE</p>
|
||
<div class="market-intel-check-list">${
|
||
alertRows.length
|
||
? alertRows.map(item => renderRow(item.review_state || 'unknown', item.priority_lane || 'watch', item.alert_count || 0)).join('')
|
||
: '<div class="market-intel-empty">尚未有告警審核佇列資料。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-live-inventory-crawler>
|
||
<p class="market-intel-deploy-section-title">CRAWLER RUNS</p>
|
||
<div class="market-intel-check-list">${
|
||
crawlerRows.length
|
||
? crawlerRows.map(item => renderRow(item.platform_code || 'all', `${item.status || 'unknown'} / dry=${item.dry_run ? 'yes' : 'no'}`, item.run_count || 0)).join('')
|
||
: '<div class="market-intel-empty">尚未有 crawler run 資料。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadLiveInventory = async () => {
|
||
if (!liveInventoryMeta || !liveInventoryBody) return;
|
||
liveInventoryBody.innerHTML = '<div class="market-intel-empty">讀取正式 DB 庫存總覽中...</div>';
|
||
try {
|
||
const response = await fetch(liveInventoryEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderLiveInventoryMeta(data);
|
||
renderLiveInventoryBody(data);
|
||
} catch (error) {
|
||
liveInventoryMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
liveInventoryBody.innerHTML = `<div class="market-intel-empty">正式 DB 庫存總覽讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderApprovalMeta = data => {
|
||
approvalMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`ready=${data.ready_for_real_write ? 'yes' : 'no'}`,
|
||
`gates=${(data.approval_gates || []).length}`,
|
||
`blocked=${(data.blocked_reasons || []).length}`,
|
||
`db_commit=${data.database_commit_executed ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderApprovalBody = data => {
|
||
const gates = data.approval_gates || [];
|
||
const sequence = (data.operator_sequence || []).slice(0, 6);
|
||
const rollback = data.rollback_plan || [];
|
||
const renderNamedItem = (item, status) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(item.key || item.label || 'item')}</strong>
|
||
<small>${escapeHtml(item.label || item.key || '')}</small>
|
||
</div>
|
||
<span>${escapeHtml(status).toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
|
||
approvalBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">正式寫入尚未批准;目前沒有 DB session、沒有 commit、沒有 scheduler attach。</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-approval-gates>
|
||
<p class="market-intel-deploy-section-title">APPROVAL GATES</p>
|
||
<div class="market-intel-check-list">${
|
||
gates.map(item => renderNamedItem(item, item.passed ? 'pass' : 'block')).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-approval-sequence>
|
||
<p class="market-intel-deploy-section-title">OPERATOR SEQUENCE</p>
|
||
<div class="market-intel-check-list">${
|
||
sequence.map(item => renderNamedItem(item, 'pending')).join('')
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-approval-rollback>
|
||
<p class="market-intel-deploy-section-title">ROLLBACK</p>
|
||
<div class="market-intel-check-list">${
|
||
rollback.map(item => renderNamedItem(item, 'ready')).join('')
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadApproval = async () => {
|
||
if (!approvalMeta || !approvalBody) return;
|
||
approvalBody.innerHTML = '<div class="market-intel-empty">讀取批准檢查中...</div>';
|
||
try {
|
||
const response = await fetch(approvalEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderApprovalMeta(data);
|
||
renderApprovalBody(data);
|
||
} catch (error) {
|
||
approvalMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
approvalBody.innerHTML = `<div class="market-intel-empty">批准檢查讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
const renderDeployMeta = data => {
|
||
deployMeta.innerHTML = [
|
||
`mode=${data.mode || 'unknown'}`,
|
||
`deployed=${data.production_deployed ? 'yes' : 'no'}`,
|
||
`commit=${data.git_committed ? 'yes' : 'no'}`,
|
||
`push=${data.git_pushed ? 'yes' : 'no'}`,
|
||
`ready=${data.ready_for_production_deploy ? 'yes' : 'no'}`
|
||
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
|
||
};
|
||
|
||
const renderDeployBody = data => {
|
||
const blockers = (data.blocked_reasons || []).join(' / ');
|
||
const checks = Object.entries(data.checks || {});
|
||
const steps = data.required_manual_steps || [];
|
||
const fallback = data.fallback_plan || [];
|
||
const boundaries = data.safe_deploy_boundaries || [];
|
||
const smokeTargets = data.production_smoke_targets || [];
|
||
const renderItem = (item, fallbackStatus) => {
|
||
const normalized = typeof item === 'string' ? { key: item, label: item } : (item || {});
|
||
const status = normalized.status || fallbackStatus || 'required';
|
||
return `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(normalized.key || normalized.label || 'item')}</strong>
|
||
<small>${escapeHtml(normalized.label || normalized.key || '')}</small>
|
||
${normalized.trigger ? `<small>trigger=${escapeHtml(normalized.trigger)}</small>` : ''}
|
||
</div>
|
||
<span>${escapeHtml(status).toUpperCase()}</span>
|
||
</div>
|
||
`;
|
||
};
|
||
deployBody.innerHTML = `
|
||
<div class="market-intel-empty mb-3">API 不執行推版;需由操作員依 app-only SOP 完成備份、同步、重啟與正式 smoke。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
|
||
<div class="market-intel-check-list">${
|
||
checks.map(([name, passed]) => `
|
||
<div class="market-intel-check">
|
||
<div>
|
||
<strong>${escapeHtml(name)}</strong>
|
||
</div>
|
||
<span>${passed ? 'PASS' : 'BLOCK'}</span>
|
||
</div>
|
||
`).join('')
|
||
}</div>
|
||
<div class="market-intel-deploy-grid">
|
||
<div data-market-intel-deploy-steps>
|
||
<p class="market-intel-deploy-section-title">MANUAL STEPS</p>
|
||
<div class="market-intel-check-list">${
|
||
steps.length
|
||
? steps.map(item => renderItem(item, 'pending')).join('')
|
||
: '<div class="market-intel-empty">尚未提供人工步驟。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-deploy-fallback>
|
||
<p class="market-intel-deploy-section-title">備援方案</p>
|
||
<div class="market-intel-check-list">${
|
||
fallback.length
|
||
? fallback.map(item => renderItem(item, 'ready')).join('')
|
||
: '<div class="market-intel-empty">尚未提供備援方案。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-deploy-boundaries>
|
||
<p class="market-intel-deploy-section-title">DEPLOY BOUNDARIES</p>
|
||
<div class="market-intel-check-list">${
|
||
boundaries.length
|
||
? boundaries.map(item => renderItem(item, 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供部署邊界。</div>'
|
||
}</div>
|
||
</div>
|
||
<div data-market-intel-deploy-smoke>
|
||
<p class="market-intel-deploy-section-title">SMOKE TARGETS</p>
|
||
<div class="market-intel-check-list">${
|
||
smokeTargets.length
|
||
? smokeTargets.map(item => renderItem(String(item), 'required')).join('')
|
||
: '<div class="market-intel-empty">尚未提供 smoke 目標。</div>'
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
const loadDeploy = async () => {
|
||
if (!deployMeta || !deployBody) return;
|
||
deployBody.innerHTML = '<div class="market-intel-empty">讀取推版準備中...</div>';
|
||
try {
|
||
const response = await fetch(deployEndpoint, { credentials: 'same-origin' });
|
||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||
const data = await response.json();
|
||
renderDeployMeta(data);
|
||
renderDeployBody(data);
|
||
} catch (error) {
|
||
deployMeta.innerHTML = '<span class="market-intel-pill">error</span>';
|
||
deployBody.innerHTML = `<div class="market-intel-empty">推版準備讀取失敗:${escapeHtml(error.message)}</div>`;
|
||
}
|
||
};
|
||
|
||
if (refresh) {
|
||
refresh.addEventListener('click', loadPreview);
|
||
}
|
||
if (writerRefresh) {
|
||
writerRefresh.addEventListener('click', loadWriter);
|
||
}
|
||
if (cliRefresh) {
|
||
cliRefresh.addEventListener('click', loadCli);
|
||
}
|
||
if (dbProbeRefresh) {
|
||
dbProbeRefresh.addEventListener('click', loadDbProbe);
|
||
}
|
||
if (seedDiffRefresh) {
|
||
seedDiffRefresh.addEventListener('click', loadSeedDiff);
|
||
}
|
||
if (legacyBridgeRefresh) {
|
||
legacyBridgeRefresh.addEventListener('click', loadLegacyBridge);
|
||
}
|
||
if (mcpReadinessRefresh) {
|
||
mcpReadinessRefresh.addEventListener('click', loadMcpReadiness);
|
||
}
|
||
if (mcpPreflightRefresh) {
|
||
mcpPreflightRefresh.addEventListener('click', loadMcpPreflight);
|
||
}
|
||
if (mcpActivationRefresh) {
|
||
mcpActivationRefresh.addEventListener('click', loadMcpActivation);
|
||
}
|
||
if (mcpFetchGateRefresh) {
|
||
mcpFetchGateRefresh.addEventListener('click', loadMcpFetchGate);
|
||
}
|
||
if (mcpCompletionRefresh) {
|
||
mcpCompletionRefresh.addEventListener('click', loadMcpCompletion);
|
||
}
|
||
if (mcpActivationEvidenceRefresh) {
|
||
mcpActivationEvidenceRefresh.addEventListener('click', loadMcpActivationEvidence);
|
||
}
|
||
if (mcpActivationEvidenceReview) {
|
||
mcpActivationEvidenceReview.addEventListener('click', reviewMcpActivationEvidence);
|
||
}
|
||
if (mcpRuntimeSmokeRefresh) {
|
||
mcpRuntimeSmokeRefresh.addEventListener('click', loadMcpRuntimeSmoke);
|
||
}
|
||
if (mcpRuntimeSmokeReview) {
|
||
mcpRuntimeSmokeReview.addEventListener('click', reviewMcpRuntimeSmoke);
|
||
}
|
||
if (mcpRuntimePromotionRefresh) {
|
||
mcpRuntimePromotionRefresh.addEventListener('click', loadMcpRuntimePromotion);
|
||
}
|
||
if (mcpRuntimePromotionReview) {
|
||
mcpRuntimePromotionReview.addEventListener('click', reviewMcpRuntimePromotion);
|
||
}
|
||
if (mcpManualFetchHandoffRefresh) {
|
||
mcpManualFetchHandoffRefresh.addEventListener('click', loadMcpManualFetchHandoff);
|
||
}
|
||
if (mcpManualFetchHandoffReview) {
|
||
mcpManualFetchHandoffReview.addEventListener('click', reviewMcpManualFetchHandoff);
|
||
}
|
||
if (mcpFetchTargetReviewRefresh) {
|
||
mcpFetchTargetReviewRefresh.addEventListener('click', loadMcpFetchTargetReview);
|
||
}
|
||
if (mcpFetchTargetReviewReview) {
|
||
mcpFetchTargetReviewReview.addEventListener('click', reviewMcpFetchTargetReview);
|
||
}
|
||
if (mcpFetchRunPackageRefresh) {
|
||
mcpFetchRunPackageRefresh.addEventListener('click', loadMcpFetchRunPackage);
|
||
}
|
||
if (mcpFetchRunPackageReview) {
|
||
mcpFetchRunPackageReview.addEventListener('click', reviewMcpFetchRunPackage);
|
||
}
|
||
if (mcpFetchRunReadinessRefresh) {
|
||
mcpFetchRunReadinessRefresh.addEventListener('click', loadMcpFetchRunReadiness);
|
||
}
|
||
if (mcpFetchRunReadinessReview) {
|
||
mcpFetchRunReadinessReview.addEventListener('click', reviewMcpFetchRunReadiness);
|
||
}
|
||
if (mcpFetchRunReceiptRefresh) {
|
||
mcpFetchRunReceiptRefresh.addEventListener('click', loadMcpFetchRunReceipt);
|
||
}
|
||
if (mcpFetchRunReceiptReview) {
|
||
mcpFetchRunReceiptReview.addEventListener('click', reviewMcpFetchRunReceipt);
|
||
}
|
||
if (mcpFetchResultParserReviewRefresh) {
|
||
mcpFetchResultParserReviewRefresh.addEventListener('click', loadMcpFetchResultParserReview);
|
||
}
|
||
if (mcpFetchResultParserReviewReview) {
|
||
mcpFetchResultParserReviewReview.addEventListener('click', reviewMcpFetchResultParserReview);
|
||
}
|
||
if (mcpFetchCandidateHandoffReviewRefresh) {
|
||
mcpFetchCandidateHandoffReviewRefresh.addEventListener('click', loadMcpFetchCandidateHandoffReview);
|
||
}
|
||
if (mcpFetchCandidateHandoffReviewReview) {
|
||
mcpFetchCandidateHandoffReviewReview.addEventListener('click', reviewMcpFetchCandidateHandoffReview);
|
||
}
|
||
if (mcpFetchCandidateQueueReviewRefresh) {
|
||
mcpFetchCandidateQueueReviewRefresh.addEventListener('click', loadMcpFetchCandidateQueueReview);
|
||
}
|
||
if (mcpFetchCandidateQueueReviewReview) {
|
||
mcpFetchCandidateQueueReviewReview.addEventListener('click', reviewMcpFetchCandidateQueueReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterPreflightRefresh) {
|
||
mcpFetchCandidateQueueWriterPreflightRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterPreflight);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterPreflightReview) {
|
||
mcpFetchCandidateQueueWriterPreflightReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterPreflight);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterCliReviewRefresh) {
|
||
mcpFetchCandidateQueueWriterCliReviewRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterCliReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterCliReviewReview) {
|
||
mcpFetchCandidateQueueWriterCliReviewReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterCliReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterRunPackageReviewRefresh) {
|
||
mcpFetchCandidateQueueWriterRunPackageReviewRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterRunPackageReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterRunPackageReviewReview) {
|
||
mcpFetchCandidateQueueWriterRunPackageReviewReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterRunPackageReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterRunReadinessRefresh) {
|
||
mcpFetchCandidateQueueWriterRunReadinessRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterRunReadiness);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterRunReadinessReview) {
|
||
mcpFetchCandidateQueueWriterRunReadinessReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterRunReadiness);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterRunReceiptReviewRefresh) {
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterRunReceiptReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterRunReceiptReviewReview) {
|
||
mcpFetchCandidateQueueWriterRunReceiptReviewReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterRunReceiptReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterRunCloseoutReviewRefresh) {
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterRunCloseoutReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterRunCloseoutReviewReview) {
|
||
mcpFetchCandidateQueueWriterRunCloseoutReviewReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterRunCloseoutReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRefresh) {
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterPostCloseoutInventoryReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewReview) {
|
||
mcpFetchCandidateQueueWriterPostCloseoutInventoryReviewReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterPostCloseoutInventoryReview);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterReviewHandoffRefresh) {
|
||
mcpFetchCandidateQueueWriterReviewHandoffRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterReviewHandoff);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterReviewHandoffReview) {
|
||
mcpFetchCandidateQueueWriterReviewHandoffReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterReviewHandoff);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterReviewInventoryRefresh) {
|
||
mcpFetchCandidateQueueWriterReviewInventoryRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterReviewInventory);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterReviewInventoryReview) {
|
||
mcpFetchCandidateQueueWriterReviewInventoryReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterReviewInventory);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterReviewDecisionRefresh) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterReviewDecision);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterReviewDecisionReview) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterReviewDecision);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterReviewDecisionApprovalRefresh) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterReviewDecisionApproval);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterReviewDecisionApprovalReview) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterReviewDecisionApproval);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRefresh) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightRefresh.addEventListener('click', loadMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflight);
|
||
}
|
||
if (mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightReview) {
|
||
mcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflightReview.addEventListener('click', reviewMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflight);
|
||
}
|
||
if (manualSampleRefresh) {
|
||
manualSampleRefresh.addEventListener('click', loadManualSample);
|
||
}
|
||
if (sampleAcceptanceRefresh) {
|
||
sampleAcceptanceRefresh.addEventListener('click', loadSampleAcceptance);
|
||
}
|
||
if (sampleReviewRefresh) {
|
||
sampleReviewRefresh.addEventListener('click', loadSampleReview);
|
||
}
|
||
if (sampleReviewEvaluate) {
|
||
sampleReviewEvaluate.addEventListener('click', evaluateSampleReview);
|
||
}
|
||
if (sampleCandidateHandoff) {
|
||
sampleCandidateHandoff.addEventListener('click', loadCandidateHandoff);
|
||
}
|
||
if (sampleCandidateQueueDraft) {
|
||
sampleCandidateQueueDraft.addEventListener('click', loadCandidateQueueDraft);
|
||
}
|
||
if (sampleCandidateQueueApproval) {
|
||
sampleCandidateQueueApproval.addEventListener('click', loadCandidateQueueApproval);
|
||
}
|
||
if (sampleCandidateQueueTransaction) {
|
||
sampleCandidateQueueTransaction.addEventListener('click', loadCandidateQueueTransaction);
|
||
}
|
||
if (sampleCandidateQueueWriter) {
|
||
sampleCandidateQueueWriter.addEventListener('click', loadCandidateQueueWriter);
|
||
}
|
||
if (sampleCandidateQueuePreflight) {
|
||
sampleCandidateQueuePreflight.addEventListener('click', loadCandidateQueuePreflight);
|
||
}
|
||
if (sampleCandidateQueuePostwriteSmoke) {
|
||
sampleCandidateQueuePostwriteSmoke.addEventListener('click', loadCandidateQueuePostwriteSmoke);
|
||
}
|
||
if (sampleCandidateQueueOperatorDrill) {
|
||
sampleCandidateQueueOperatorDrill.addEventListener('click', loadCandidateQueueOperatorDrill);
|
||
}
|
||
if (sampleCandidateQueueRunPackage) {
|
||
sampleCandidateQueueRunPackage.addEventListener('click', loadCandidateQueueRunPackage);
|
||
}
|
||
if (sampleCandidateQueueRunReadiness) {
|
||
sampleCandidateQueueRunReadiness.addEventListener('click', loadCandidateQueueRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueRunReceipt) {
|
||
sampleCandidateQueueRunReceipt.addEventListener('click', loadCandidateQueueRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueRunCloseout) {
|
||
sampleCandidateQueueRunCloseout.addEventListener('click', loadCandidateQueueRunCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewHandoff) {
|
||
sampleCandidateQueueReviewHandoff.addEventListener('click', loadCandidateQueueReviewHandoff);
|
||
}
|
||
if (sampleCandidateQueueReviewInventory) {
|
||
sampleCandidateQueueReviewInventory.addEventListener('click', loadCandidateQueueReviewInventory);
|
||
}
|
||
if (sampleCandidateQueueReviewDecision) {
|
||
sampleCandidateQueueReviewDecision.addEventListener('click', loadCandidateQueueReviewDecision);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionApproval) {
|
||
sampleCandidateQueueReviewDecisionApproval.addEventListener('click', loadCandidateQueueReviewDecisionApproval);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionTransaction) {
|
||
sampleCandidateQueueReviewDecisionTransaction.addEventListener('click', loadCandidateQueueReviewDecisionTransaction);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionPreflight) {
|
||
sampleCandidateQueueReviewDecisionPreflight.addEventListener('click', loadCandidateQueueReviewDecisionPreflight);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionPostwriteSmoke) {
|
||
sampleCandidateQueueReviewDecisionPostwriteSmoke.addEventListener('click', loadCandidateQueueReviewDecisionPostwriteSmoke);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionOperatorDrill) {
|
||
sampleCandidateQueueReviewDecisionOperatorDrill.addEventListener('click', loadCandidateQueueReviewDecisionOperatorDrill);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionRunPackage) {
|
||
sampleCandidateQueueReviewDecisionRunPackage.addEventListener('click', loadCandidateQueueReviewDecisionRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionRunReadiness) {
|
||
sampleCandidateQueueReviewDecisionRunReadiness.addEventListener('click', loadCandidateQueueReviewDecisionRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionWriter) {
|
||
sampleCandidateQueueReviewDecisionWriter.addEventListener('click', loadCandidateQueueReviewDecisionWriter);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionRunReceipt) {
|
||
sampleCandidateQueueReviewDecisionRunReceipt.addEventListener('click', loadCandidateQueueReviewDecisionRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionRunCloseout) {
|
||
sampleCandidateQueueReviewDecisionRunCloseout.addEventListener('click', loadCandidateQueueReviewDecisionRunCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewDecisionPostCloseoutInventory) {
|
||
sampleCandidateQueueReviewDecisionPostCloseoutInventory.addEventListener('click', loadCandidateQueueReviewDecisionPostCloseoutInventory);
|
||
}
|
||
if (sampleCandidateQueueReviewCompletionArchive) {
|
||
sampleCandidateQueueReviewCompletionArchive.addEventListener('click', loadCandidateQueueReviewCompletionArchive);
|
||
}
|
||
if (sampleCandidateQueueReviewArchiveSummary) {
|
||
sampleCandidateQueueReviewArchiveSummary.addEventListener('click', loadCandidateQueueReviewArchiveSummary);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPreflight) {
|
||
sampleCandidateQueueReviewAiSummaryPreflight.addEventListener('click', loadCandidateQueueReviewAiSummaryPreflight);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryRunPackage) {
|
||
sampleCandidateQueueReviewAiSummaryRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryOutputReceipt) {
|
||
sampleCandidateQueueReviewAiSummaryOutputReceipt.addEventListener('click', loadCandidateQueueReviewAiSummaryOutputReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistencePreflight) {
|
||
sampleCandidateQueueReviewAiSummaryPersistencePreflight.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistencePreflight);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTransaction) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTransaction.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTransaction);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceWriterPreflight) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceWriterPreflight.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceWriterPreflight);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceRunPackage) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceRunReadiness) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceRunReadiness.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceRunReceipt) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceRunReceipt.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceRunCloseout) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceRunCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceRunCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchGate);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchive);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchArchiveSummary);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportInput);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchive);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportArchiveSummary);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogHandoff);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogIndex);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogWritePreflight);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordWrite);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunPackage);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReadiness);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordRunReceipt);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCommit);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordCloseout);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchive);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordArchiveSummary);
|
||
}
|
||
if (sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout) {
|
||
sampleCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout.addEventListener('click', loadCandidateQueueReviewAiSummaryPersistenceTelegramDispatchReportCatalogRecordFinalCloseout);
|
||
}
|
||
if (schedulerRefresh) {
|
||
schedulerRefresh.addEventListener('click', loadScheduler);
|
||
}
|
||
if (matchReviewRefresh) {
|
||
matchReviewRefresh.addEventListener('click', loadMatchReview);
|
||
}
|
||
if (opportunityRefresh) {
|
||
opportunityRefresh.addEventListener('click', loadOpportunity);
|
||
}
|
||
if (opportunityScoringRefresh) {
|
||
opportunityScoringRefresh.addEventListener('click', loadOpportunityScoring);
|
||
}
|
||
if (opportunityEvidenceRefresh) {
|
||
opportunityEvidenceRefresh.addEventListener('click', loadOpportunityEvidence);
|
||
}
|
||
if (opportunityAlertRefresh) {
|
||
opportunityAlertRefresh.addEventListener('click', loadOpportunityAlert);
|
||
}
|
||
if (migrationRefresh) {
|
||
migrationRefresh.addEventListener('click', loadMigration);
|
||
}
|
||
if (migrationDrillRefresh) {
|
||
migrationDrillRefresh.addEventListener('click', loadMigrationDrill);
|
||
}
|
||
if (catalogReviewRefresh) {
|
||
catalogReviewRefresh.addEventListener('click', loadCatalogReview);
|
||
}
|
||
if (liveSmokeRefresh) {
|
||
liveSmokeRefresh.addEventListener('click', loadLiveSmoke);
|
||
}
|
||
if (liveInventoryRefresh) {
|
||
liveInventoryRefresh.addEventListener('click', loadLiveInventory);
|
||
}
|
||
if (approvalRefresh) {
|
||
approvalRefresh.addEventListener('click', loadApproval);
|
||
}
|
||
if (deployRefresh) {
|
||
deployRefresh.addEventListener('click', loadDeploy);
|
||
}
|
||
loadPreview();
|
||
loadWriter();
|
||
loadCli();
|
||
loadDbProbe();
|
||
loadSeedDiff();
|
||
loadLegacyBridge();
|
||
loadMcpReadiness();
|
||
loadMcpPreflight();
|
||
loadMcpActivation();
|
||
loadMcpFetchGate();
|
||
loadMcpCompletion();
|
||
loadMcpActivationEvidence();
|
||
loadMcpRuntimeSmoke();
|
||
loadMcpRuntimePromotion();
|
||
loadMcpManualFetchHandoff();
|
||
loadMcpFetchTargetReview();
|
||
loadMcpFetchRunPackage();
|
||
loadMcpFetchRunReadiness();
|
||
loadMcpFetchRunReceipt();
|
||
loadMcpFetchResultParserReview();
|
||
loadMcpFetchCandidateHandoffReview();
|
||
loadMcpFetchCandidateQueueReview();
|
||
loadMcpFetchCandidateQueueWriterPreflight();
|
||
loadMcpFetchCandidateQueueWriterCliReview();
|
||
loadMcpFetchCandidateQueueWriterRunPackageReview();
|
||
loadMcpFetchCandidateQueueWriterRunReadiness();
|
||
loadMcpFetchCandidateQueueWriterRunReceiptReview();
|
||
loadMcpFetchCandidateQueueWriterRunCloseoutReview();
|
||
loadMcpFetchCandidateQueueWriterPostCloseoutInventoryReview();
|
||
loadMcpFetchCandidateQueueWriterReviewHandoff();
|
||
loadMcpFetchCandidateQueueWriterReviewInventory();
|
||
loadMcpFetchCandidateQueueWriterReviewDecision();
|
||
loadMcpFetchCandidateQueueWriterReviewDecisionApproval();
|
||
loadMcpFetchCandidateQueueWriterReviewDecisionApprovalWriterPreflight();
|
||
loadManualSample();
|
||
loadSampleAcceptance();
|
||
loadSampleReview();
|
||
loadScheduler();
|
||
loadMatchReview();
|
||
loadOpportunity();
|
||
loadOpportunityScoring();
|
||
loadOpportunityEvidence();
|
||
loadOpportunityAlert();
|
||
loadMigration();
|
||
loadMigrationDrill();
|
||
loadCatalogReview();
|
||
loadLiveSmoke();
|
||
loadLiveInventory();
|
||
loadApproval();
|
||
loadDeploy();
|
||
})();
|
||
</script>
|
||
{% endblock %}
|