chore(freeze): snapshot demo state before Plan-07 hardening work

Freeze current submission/demo baseline:
- casan-next-plans/: full task-level plan set (Plan 00 index + 02/04/06/07/08/09/12, QA, slide deck)
- optimize-docs/video-steps/: per-vector scene breakdown (commands/screen-text/script) + start-tmux
- run-all.sh / scorecard.sh / map-live.sh: REAL=1 live-battery wiring
- regenerated evidence + audit/telemetry logs from live REAL=1 run
- submission README + video recording guide updates
- dry-run pipeline logs for 001-okr-web-app

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
thanhnv
2026-07-03 22:03:44 +09:00
co-authored by Claude Opus 4.8
parent fabd5f8783
commit fbcef967e5
182 changed files with 3865 additions and 341 deletions
+395
View File
@@ -0,0 +1,395 @@
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CASAN Harness — FPT · AI-SDLC có kiểm soát</title>
<style>
:root{
--fpt-orange:#F37021; --fpt-blue:#00AEEF; --fpt-green:#7DBB42;
--bg:#070B16; --ink:#eaf0ff; --muted:#93a0c2;
--glass:rgba(255,255,255,.045); --glass-brd:rgba(255,255,255,.10);
--h4:#ff5a5a; --h5:#3ba7ff; --h6:#5fd08a; --h7:#b98bff;
--bad:#ff6b6b; --ok:#5fd08a;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
font-family:"Segoe UI",system-ui,-apple-system,"Noto Sans",sans-serif;
color:var(--ink); overflow:hidden; background:var(--bg); position:relative;
}
/* AI-era ambient background */
body::before{content:"";position:fixed;inset:0;z-index:0;
background:
radial-gradient(900px 520px at 8% -8%, rgba(243,112,33,.20), transparent 60%),
radial-gradient(900px 520px at 100% 0%, rgba(0,174,239,.18), transparent 55%),
radial-gradient(700px 500px at 60% 120%, rgba(125,187,66,.12), transparent 60%);}
body::after{content:"";position:fixed;inset:0;z-index:0;opacity:.35;
background-image:radial-gradient(rgba(255,255,255,.05) 1px,transparent 1px);
background-size:26px 26px;}
.deck{position:fixed;inset:0;z-index:1}
.slide{position:absolute;inset:0;display:none;flex-direction:column;
justify-content:center;padding:6vh 7vw;animation:fade .4s ease}
.slide.active{display:flex}
@keyframes fade{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
.eyebrow{font-family:"Cascadia Code",Consolas,monospace;color:var(--fpt-orange);
font-weight:700;letter-spacing:3px;text-transform:uppercase;
font-size:clamp(11px,1.2vw,15px);margin-bottom:1em;display:flex;align-items:center;gap:.6em}
.eyebrow::before{content:"";width:26px;height:2px;background:var(--fpt-orange)}
h1{font-size:clamp(30px,5.2vw,64px);line-height:1.06;letter-spacing:-1px;font-weight:800}
h2{font-size:clamp(23px,3.6vw,44px);line-height:1.12;font-weight:800;margin-bottom:.4em}
.grad{background:linear-gradient(100deg,var(--fpt-orange),#ffb46b 40%,var(--fpt-blue));
-webkit-background-clip:text;background-clip:text;color:transparent}
.sub{color:var(--muted);font-size:clamp(15px,1.9vw,23px);margin-top:.8em;line-height:1.55}
ul{margin-top:1.1em;list-style:none;display:grid;gap:.7em}
li{font-size:clamp(15px,1.95vw,24px);line-height:1.42;padding-left:1.7em;position:relative}
li::before{content:"›";position:absolute;left:0;color:var(--fpt-orange);font-weight:800}
b,strong{color:#fff}
code{font-family:"Cascadia Code",Consolas,monospace;background:rgba(255,255,255,.06);
padding:.05em .4em;border-radius:6px;font-size:.9em;color:#ffd8b8}
.tag{display:inline-block;font-family:monospace;font-size:.6em;font-weight:800;padding:.2em .7em;
border-radius:999px;vertical-align:middle;letter-spacing:.5px}
.tag.real{background:rgba(125,187,66,.18);color:#b6f08a;border:1px solid #7dbb4266}
.tag.warn{background:rgba(243,112,33,.16);color:#ffc07a;border:1px solid #f3702166}
/* glass card */
.cards{display:grid;gap:1rem;margin-top:1.3em}
.c4{grid-template-columns:repeat(4,1fr)} .c3{grid-template-columns:repeat(3,1fr)}
.c2{grid-template-columns:repeat(2,1fr)}
.card{background:var(--glass);border:1px solid var(--glass-brd);border-radius:18px;
padding:1.1em 1.15em;backdrop-filter:blur(10px);box-shadow:0 10px 40px rgba(0,0,0,.3)}
.card h3{font-size:clamp(14px,1.7vw,21px);margin-bottom:.4em;display:flex;align-items:center;gap:.4em}
.card p{color:var(--muted);font-size:clamp(12px,1.35vw,16px);line-height:1.42}
.card.on4{border-top:3px solid var(--h4)} .card.on5{border-top:3px solid var(--h5)}
.card.on6{border-top:3px solid var(--h6)} .card.on7{border-top:3px solid var(--h7)}
/* before/after */
.ba{display:grid;grid-template-columns:1fr auto 1fr;gap:1.1rem;align-items:stretch;margin-top:1.2em}
.col{border-radius:18px;padding:1.15em 1.25em;backdrop-filter:blur(8px)}
.before{background:rgba(255,90,90,.07);border:1px solid rgba(255,90,90,.28)}
.after{background:rgba(125,187,66,.08);border:1px solid rgba(125,187,66,.32)}
.col .lbl{font-family:monospace;font-weight:800;letter-spacing:1px;font-size:.8em;margin-bottom:.6em}
.before .lbl{color:var(--bad)} .after .lbl{color:var(--ok)}
.col ul{margin-top:.2em;gap:.5em} .col li{font-size:clamp(13px,1.55vw,20px);padding-left:1.4em}
.before li::before{content:"✕";color:var(--bad)} .after li::before{content:"✓";color:var(--ok)}
.mid{display:flex;align-items:center;font-size:clamp(22px,3vw,40px);color:var(--fpt-orange)}
table{width:100%;border-collapse:collapse;margin-top:1.2em;font-size:clamp(13px,1.65vw,21px)}
th,td{text-align:left;padding:.52em .7em;border-bottom:1px solid var(--glass-brd)}
th{color:var(--fpt-blue);font-family:monospace;font-size:.82em;text-transform:uppercase;letter-spacing:1px}
td.big{font-weight:800;color:#fff} td.was{color:var(--muted)}
.flow{display:flex;align-items:center;gap:.6rem;margin-top:1.4em;flex-wrap:wrap}
.node{background:var(--glass);border:1px solid var(--glass-brd);border-radius:14px;
padding:.75em 1.05em;font-weight:700;font-size:clamp(13px,1.55vw,20px);text-align:center;backdrop-filter:blur(8px)}
.node.in{border-color:rgba(0,174,239,.5)} .node.out{border-color:rgba(125,187,66,.5);background:rgba(125,187,66,.08)}
.arrow{color:var(--fpt-orange);font-size:clamp(18px,2.3vw,30px)}
.chips{display:flex;gap:.5rem;margin-top:1.1em;flex-wrap:wrap}
.chip{padding:.5em .9em;border-radius:11px;font-weight:700;font-size:clamp(11px,1.3vw,17px);
border:1px solid var(--glass-brd);background:var(--glass)}
.chip.s{border-color:var(--h4);color:#ffb4b4} .chip.g{border-color:var(--h5);color:#a9d5ff}
.chip.o{border-color:var(--h6);color:#a6f0c0} .chip.r{border-color:var(--h7);color:#dcc0ff}
.chip.y{border-color:var(--fpt-orange);color:#ffc99b}
.term{margin-top:1.2em;background:rgba(3,5,12,.7);border:1px solid var(--glass-brd);border-radius:14px;
padding:1em 1.25em;font-family:"Cascadia Code",Consolas,monospace;
font-size:clamp(12px,1.45vw,19px);line-height:1.75;backdrop-filter:blur(8px)}
.term .dim{color:#5a678c} .term .ok{color:#8ff0b0;font-weight:700} .term .no{color:#ff9a9a;font-weight:700}
.quote{font-size:clamp(21px,3.3vw,44px);line-height:1.22;font-weight:800}
.quote .hl{color:transparent;background:linear-gradient(100deg,var(--fpt-orange),var(--fpt-blue));
-webkit-background-clip:text;background-clip:text}
/* score meters */
.meters{display:grid;grid-template-columns:1fr 1fr;gap:.7em 1.6em;margin-top:1.2em;max-width:1050px}
.meter{display:grid;grid-template-columns:180px 1fr 44px;align-items:center;gap:.8em}
.meter .m-l{font-size:clamp(12px,1.4vw,18px);color:var(--muted)}
.track{height:10px;border-radius:999px;background:rgba(255,255,255,.07);overflow:hidden}
.fill{height:100%;border-radius:999px;background:linear-gradient(90deg,var(--fpt-orange),var(--fpt-blue))}
.meter .m-v{font-weight:800;text-align:right;font-family:monospace}
/* chrome */
.bar{position:fixed;top:0;left:0;height:4px;z-index:9;transition:width .35s;
background:linear-gradient(90deg,var(--fpt-orange),var(--fpt-blue),var(--fpt-green))}
.brand{position:fixed;top:20px;right:26px;z-index:9;display:flex;align-items:center;gap:10px}
.brand .fpt{font-weight:900;font-size:20px;letter-spacing:1px;
background:linear-gradient(100deg,var(--fpt-orange),var(--fpt-blue),var(--fpt-green));
-webkit-background-clip:text;background-clip:text;color:transparent}
.brand .ai{font-family:monospace;font-size:11px;color:var(--muted);border:1px solid var(--glass-brd);
padding:2px 8px;border-radius:999px}
.foot{position:fixed;bottom:16px;left:0;right:0;display:flex;justify-content:space-between;
align-items:center;padding:0 26px;color:var(--muted);font-size:13px;z-index:9;font-family:monospace}
.dots{display:flex;gap:6px} .dot{width:8px;height:8px;border-radius:50%;background:#2a3358;cursor:pointer;transition:.2s}
.dot.on{background:var(--fpt-orange);transform:scale(1.3)}
.hint{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);color:#48557d;font-size:11px;z-index:9;font-family:monospace}
.kbd{border:1px solid var(--glass-brd);border-radius:6px;padding:1px 7px}
</style>
</head>
<body>
<div class="bar" id="bar"></div>
<div class="brand"><span class="fpt">FPT</span><span class="ai">AI-SDLC · CASAN</span></div>
<div class="deck" id="deck">
<!-- 1 TITLE -->
<section class="slide active">
<div class="eyebrow">FPT · CASAN Harness · Thời đại AI</div>
<h1>Cải thiện <span class="grad">H4 · H5 · H6</span><br/>cho AI-SDLC <span class="grad">có kiểm soát</span></h1>
<p class="sub">Chúng tôi không hỏi "AI viết code nhanh cỡ nào" — mà hỏi:
<b>khi AI làm bậy, ai chặn? và làm sao chứng minh đã chặn?</b></p>
<p class="sub">Chạy có bằng chứng · Chặn thật · Trung thực về giới hạn · Hướng đóng gói production.</p>
</section>
<!-- 2 GOAL / 3 AXES -->
<section class="slide">
<div class="eyebrow">Mục tiêu trình bày</div>
<h2>Ba trục chúng tôi chứng minh</h2>
<div class="cards c3">
<div class="card on4"><h3>🔴 1 · Cải thiện H4·H5·H6</h3><p>Before → After cho từng harness, kèm <b>bằng chứng đo thật</b> (exit-code, số test).</p></div>
<div class="card on5"><h3>🧠 2 · Hiểu hệ thống sâu</h3><p>Truy đến <b>gốc lỗi</b> (fail-open), threat-model đường lọt, nguyên tắc "harness thấp nhất quyết định trần".</p></div>
<div class="card on6"><h3>📦 3 · Hướng production</h3><p>Thang điểm sẵn sàng, <b>đóng gói package</b> tái dùng, lộ trình hardening có test.</p></div>
</div>
<p class="sub">Mọi con số gắn nhãn <span class="tag real">ĐO THẬT</span> — giám khảo chạy lại ra đúng.</p>
</section>
<!-- 3 DEPTH: root cause -->
<section class="slide">
<div class="eyebrow">Hiểu sâu · Truy gốc lỗi</div>
<h2>Điểm mù chí mạng: <span style="color:var(--h4)">fail-open</span></h2>
<ul>
<li>Bản trước dùng <code>grep "[:space:]"</code> — cú pháp <b>sai</b> trên grep hiện đại → thoát <b style="color:var(--bad)">rc=4</b>.</li>
<li>Hệ quả: bộ lọc "tưởng đang chặn" nhưng thực chất <b style="color:var(--bad)">chặn = 0</b> — lọt cả injection kinh điển.</li>
<li>Bài học: <b>"có prompt-filter.yaml" ≠ "chặn được thật"</b>. Chỉ tính điểm khi chặn được tấn công thật.</li>
</ul>
<p class="sub">Phát hiện được lỗi fail-open này = hiểu hệ thống ở tầng thực thi, không dừng ở tài liệu.</p>
</section>
<!-- 4 OVERVIEW -->
<section class="slide">
<div class="eyebrow">Kiến trúc CASAN</div>
<h2>Mọi lời gọi agent đi xuyên <span class="grad">7 harness</span></h2>
<div class="flow">
<div class="node in">📥 INPUT</div><div class="arrow">→</div>
<div class="node">🧠 Boss · 13 bước AI-SDLC</div><div class="arrow">→</div>
<div class="node out">📤 code + BẰNG CHỨNG</div>
</div>
<div class="chips">
<span class="chip y">H1 Context</span><span class="chip y">H2 Tool</span><span class="chip y">H3 Eval</span>
<span class="chip s">H4 Security</span><span class="chip g">H5 Governance</span>
<span class="chip o">H6 AgentOps</span><span class="chip r">H7 Orchestration</span>
</div>
<p class="sub">Gate trả <b>APPROVE / REJECT / BLOCK</b> — kiểm soát bọc mọi bước, không đứng bên lề.</p>
</section>
<!-- 5 H4 -->
<section class="slide">
<div class="eyebrow">Trục 1 · 🔴 H4 Security</div>
<h2>Từ fail-open → <span class="grad">chặn thật đa lớp</span></h2>
<div class="ba">
<div class="col before"><div class="lbl">BEFORE</div><ul>
<li>grep sai → fail-open rc=4, chặn = 0</li>
<li>chỉ regex tiếng Anh</li>
<li>mù với câu diễn đạt mới</li></ul></div>
<div class="mid">→</div>
<div class="col after"><div class="lbl">AFTER</div><ul>
<li>BLOCK <b>rc=2</b> + normalize leetspeak</li>
<li>+ <b>semantic model-as-judge</b></li>
<li>artifact-scan (indirect) · secret/PII block</li></ul></div>
</div>
<p class="sub"><span class="tag real">ĐO THẬT</span> Adversarial <b>43/43 PASS</b> · injection paraphrase → <code>rc=2</code>.</p>
</section>
<!-- 6 H5 -->
<section class="slide">
<div class="eyebrow">Trục 1 · 🔵 H5 Governance</div>
<h2>Từ log sửa được → <span class="grad">audit bất biến, ký số</span></h2>
<div class="ba">
<div class="col before"><div class="lbl">BEFORE</div><ul>
<li>log thường, sửa được</li>
<li>không chống giả mạo</li>
<li>không truy vết ký số</li></ul></div>
<div class="mid">→</div>
<div class="col after"><div class="lbl">AFTER</div><ul>
<li><b>hash-chain + ký RSA</b> HEAD</li>
<li>sửa 1 ký tự → <b>AUDIT_HASH_MISMATCH</b></li>
<li>secrets-scan · quét no-bypass</li></ul></div>
</div>
<p class="sub"><span class="tag real">ĐO THẬT</span> tamper 1 ký tự → <code>AUDIT_HASH_MISMATCH line=1</code> · chain hợp lệ → <code>AUDIT_CHAIN_VALID</code>.</p>
</section>
<!-- 7 H6 -->
<section class="slide">
<div class="eyebrow">Trục 1 · 🟢 H6 AgentOps</div>
<h2>Từ mù chi phí → <span class="grad">giám sát & chặn spike</span></h2>
<div class="ba">
<div class="col before"><div class="lbl">BEFORE</div><ul>
<li>không đo chi phí</li>
<li>không telemetry token</li>
<li>không phát hiện bất thường</li></ul></div>
<div class="mid">→</div>
<div class="col after"><div class="lbl">AFTER</div><ul>
<li><b>cost-spike 3× → exit=2</b></li>
<li>telemetry token thật · drift-detect</li>
<li>circuit-breaker chặn đốt tiền</li></ul></div>
</div>
<p class="sub"><span class="tag real">ĐO THẬT</span> spike → <code>COST_SPIKE_DETECTED exit=2</code> · bình thường → <code>exit=0</code>.</p>
</section>
<!-- 8 EVIDENCE -->
<section class="slide">
<div class="eyebrow">Bằng chứng tổng hợp <span class="tag real">ĐO THẬT</span></div>
<h2>Kiểm chứng ngay trên máy — không nói suông</h2>
<div class="term">
<div><span class="dim">$</span> run-casan4-harness-tests.sh → <span class="ok">35/35 PASS</span></div>
<div><span class="dim">$</span> adversarial-harness-tests.sh → <span class="ok">43/43 PASS</span></div>
<div><span class="dim">$</span> security-check (injection) → <span class="no">BLOCKED rc=2</span></div>
<div><span class="dim">$</span> tamper audit → <span class="no">AUDIT_HASH_MISMATCH line=1</span></div>
<div><span class="dim">$</span> cost 3× → <span class="no">COST_SPIKE_DETECTED exit=2</span></div>
</div>
<p class="sub">👉 Chiếu terminal thật 1–2 dòng này khi trình bày để tạo sức nặng.</p>
</section>
<!-- 9 DEPTH: threat model + principle -->
<section class="slide">
<div class="eyebrow">Trục 2 · Hiểu sâu</div>
<h2>Chúng tôi biết <span class="grad">còn lọt ở đâu</span></h2>
<div class="cards c3">
<div class="card on4"><h3>🔴 H4</h3><p>Semantic SKIP khi vắng model · injection đa ngôn ngữ · homoglyph/encoding.</p></div>
<div class="card on5"><h3>🔵 H5</h3><p>Telemetry chưa vào chuỗi ký · mutation ngoài wrapper · quản lý khóa.</p></div>
<div class="card on6"><h3>🟢 H6</h3><p>Slow-boil (median trôi) · lạm dụng dưới ngưỡng · cold-start.</p></div>
</div>
<p class="sub">Chuẩn tham chiếu: OWASP LLM/Agentic · CSA MAESTRO · MITRE ATLAS.
Nguyên tắc: <b>"harness thấp nhất quyết định trần"</b> — cái yếu nhất quyết định cả pipeline.</p>
</section>
<!-- 10 MODEL -->
<section class="slide">
<div class="eyebrow">Triết lý model · local-first</div>
<h2 class="quote">"Model sinh <span class="hl">bản nháp</span>; harness quyết định bản nháp có được <span class="hl">tin</span> hay không."</h2>
<ul>
<li>Local (Ollama) đủ cho harness — <b>không API key, offline, dữ liệu không rời máy</b> (Sovereign AI).</li>
<li>Đổi model <b>không đổi</b> mức an toàn — harness giữ đảm bảo, bất kể model.</li>
<li>H6 cost-spike chặn đốt token → điểm cộng là <b>kiểm soát chi phí</b>, không phải "model xịn nhất".</li>
</ul>
</section>
<!-- 11 PRODUCTION READINESS -->
<section class="slide">
<div class="eyebrow">Trục 3 · Hướng production</div>
<h2>Thang sẵn sàng: <span class="grad">~3.0/5</span> → mục tiêu ≥ 4.0</h2>
<div class="meters">
<div class="meter"><div class="m-l">Phủ phát hiện</div><div class="track"><div class="fill" style="width:60%"></div></div><div class="m-v">3</div></div>
<div class="meter"><div class="m-l">Fail-safe</div><div class="track"><div class="fill" style="width:80%"></div></div><div class="m-v">4</div></div>
<div class="meter"><div class="m-l">Chống giả mạo</div><div class="track"><div class="fill" style="width:60%"></div></div><div class="m-v">3</div></div>
<div class="meter"><div class="m-l">Kiểm soát chi phí</div><div class="track"><div class="fill" style="width:60%"></div></div><div class="m-v">3</div></div>
<div class="meter"><div class="m-l">Đa domain / i18n</div><div class="track"><div class="fill" style="width:40%"></div></div><div class="m-v">2</div></div>
<div class="meter"><div class="m-l">Phủ kiểm thử</div><div class="track"><div class="fill" style="width:80%"></div></div><div class="m-v">4</div></div>
</div>
<p class="sub"><span class="tag warn">TRUNG THỰC</span> Demo ✅ đủ mạnh · Track A → <b>~3.8–4.0</b> (production nội bộ) · <b>Track B+C</b> → production nghiêm túc. Mỗi vá kèm test đối kháng.</p>
</section>
<!-- 11b TRACK C-1 -->
<section class="slide">
<div class="eyebrow">Trục 3 · Track C-MVP · Kín đòn phản biện</div>
<h2>Kiểm soát <span class="grad">ngoài</span> H4·H5·H6 — nhóm ưu tiên</h2>
<p class="sub">"Scan prompt tốt — nhưng agent vẫn có thể <b>thêm dependency độc</b> / <b>ghi file nhạy cảm</b>?" → đây là <b>minimum bar</b> trước khi cho agent ghi code trong môi trường production-like.</p>
<div class="cards c4">
<div class="card on4"><h3>🔐 Tool authorization</h3><p>Gate cả <b>hành động</b> (ghi file/network/lệnh nguy hiểm), không chỉ tên tool.</p></div>
<div class="card on6"><h3>📦 Supply-chain</h3><p>Scan dependency mới · chặn typo-squat/postinstall · SBOM + CVE.</p></div>
<div class="card on4"><h3>🕵️ Data exfiltration</h3><p>Chặn secret/PII rò qua cloud model · log · artifact.</p></div>
<div class="card on7"><h3>🧪 Runtime sandbox</h3><p>Cô lập code/test sinh ra: file/network/CPU/mem/timeout.</p></div>
</div>
<p class="sub">Outcome chuẩn hoá: <code>ALLOW · WARN · REQUIRE_APPROVAL · BLOCK</code> (thêm dependency → duyệt; ghi <code>.env</code> → chặn).</p>
</section>
<!-- 11c TRACK C-2 -->
<section class="slide">
<div class="eyebrow">Trục 3 · Track C · Governance & Ops</div>
<h2>Quản trị & vận hành <span class="grad">mức enterprise</span></h2>
<div class="cards c4">
<div class="card on5"><h3>📝 Policy approval</h3><p>Versioning + reviewer bắt buộc · audit actor · rollback policy.</p></div>
<div class="card on5"><h3>🗄️ External audit</h3><p>Append-only (WORM) ngoài runtime · alert khi chain đứt.</p></div>
<div class="card on6"><h3>🚨 Incident response</h3><p>Severity CRIT→MED · owner · kill-switch theo project/model.</p></div>
<div class="card"><h3>✓ Benign / FP</h3><p>≥30 mẫu/ngôn ngữ · FP ≤ 3% · adversarial block ≥ 95%.</p></div>
</div>
<p class="sub">Reviewer role: Security · Tech lead · Ops owner · Project owner — mỗi loại policy có người duyệt riêng.</p>
</section>
<!-- 12 PACKAGING -->
<section class="slide">
<div class="eyebrow">Trục 3 · Đóng gói & tái sử dụng</div>
<h2>Giá trị ở <span class="grad">harness tái dùng</span>, không ở app OKR</h2>
<ul>
<li>Đóng gói <b>package có version</b> — <code>fpt-casan-sdd-harness</code>.</li>
<li>Registry + <code>verify-harness-reuse</code> → nhiều dự án dùng chung.</li>
<li>Áp dự án mới = cắm golden/corpus/input + đăng ký, <b>không sửa gate</b>.</li>
<li>App OKR chỉ là <b>testbed cố định</b> để so sánh công bằng.</li>
</ul>
</section>
<!-- 13 ROADMAP -->
<section class="slide">
<div class="eyebrow">Lộ trình · kế hoạch task-level</div>
<h2>Tư duy sản phẩm, không chỉ demo</h2>
<div class="cards c4">
<div class="card"><h3>01</h3><p>Tái cấu trúc thư mục</p></div>
<div class="card"><h3>02</h3><p>Nối LLM sinh source</p></div>
<div class="card"><h3>03</h3><p>Patch cloud (bỏ stub)</p></div>
<div class="card"><h3>04</h3><p>Khép vòng tự cải tiến</p></div>
<div class="card"><h3>05</h3><p>CI/CD + phát hành</p></div>
<div class="card"><h3>06</h3><p>Onboard dự án 2</p></div>
<div class="card on4"><h3>07</h3><p>Hardening H4·H5·H6</p></div>
<div class="card"><h3>✓</h3><p>Verify từng task</p></div>
</div>
</section>
<!-- 14 CLOSE -->
<section class="slide">
<div class="eyebrow">Chốt</div>
<h1 class="quote">Không phải AI <span class="hl">xịn nhất</span> —<br/>mà AI <span class="hl">có kiểm soát, có bằng chứng, tái dùng được</span>.</h1>
<ul>
<li>H4·H5·H6 cải thiện có <b>before/after + exit-code đo thật</b>.</li>
<li>Hiểu hệ thống tới gốc lỗi + threat-model đường lọt.</li>
<li>Hướng production: đóng gói package + lộ trình hardening có test.</li>
</ul>
</section>
</div>
<div class="foot">
<div id="who">FPT · CASAN Harness</div>
<div class="dots" id="dots"></div>
<div id="num">1 / 16</div>
</div>
<div class="hint"><span class="kbd">←</span> <span class="kbd">→</span> / <span class="kbd">Space</span> chuyển slide · <span class="kbd">F</span> toàn màn hình</div>
<script>
const slides=[...document.querySelectorAll('.slide')];
const dotsBox=document.getElementById('dots');
const bar=document.getElementById('bar'),num=document.getElementById('num');
let i=0;
slides.forEach((_,k)=>{const d=document.createElement('div');d.className='dot'+(k===0?' on':'');
d.onclick=()=>go(k);dotsBox.appendChild(d);});
const dots=[...document.querySelectorAll('.dot')];
function go(n){
i=Math.max(0,Math.min(slides.length-1,n));
slides.forEach((s,k)=>s.classList.toggle('active',k===i));
dots.forEach((d,k)=>d.classList.toggle('on',k===i));
bar.style.width=((i+1)/slides.length*100)+'%';
num.textContent=(i+1)+' / '+slides.length;
}
document.addEventListener('keydown',e=>{
if(['ArrowRight','ArrowDown',' ','PageDown'].includes(e.key)){e.preventDefault();go(i+1);}
else if(['ArrowLeft','ArrowUp','PageUp'].includes(e.key)){e.preventDefault();go(i-1);}
else if(e.key==='Home')go(0); else if(e.key==='End')go(slides.length-1);
else if(e.key.toLowerCase()==='f'){if(!document.fullscreenElement)document.documentElement.requestFullscreen();else document.exitFullscreen();}
});
let sx=0;
document.addEventListener('touchstart',e=>sx=e.touches[0].clientX,{passive:true});
document.addEventListener('touchend',e=>{const dx=e.changedTouches[0].clientX-sx;
if(Math.abs(dx)>50)go(i+(dx<0?1:-1));},{passive:true});
go(0);
</script>
</body>
</html>