From a029a51e729416981a0962662c83d3c6b527783e Mon Sep 17 00:00:00 2001 From: thanhnv Date: Wed, 8 Jul 2026 22:58:02 +0900 Subject: [PATCH] feat: add command center chat loop widget --- docs/plans/CASAN_BACKLOG_STATUS.md | 4 +- docs/plans/CASAN_PLAN_00_INDEX.md | 2 +- docs/plans/CASAN_PLAN_13_CONTROL_PLANE.md | 19 +-- docs/plans/CASAN_PLAN_13_NEXT_STEPS.puml | 18 +-- docs/plans/CASAN_PLAN_18_CHAT_CONSOLE.md | 21 ++- docs/plans/CASAN_TEAM_ALLOCATION_3DEV.md | 4 +- packages/casan-control-panel/README.md | 9 +- .../backend/src/common/app-root.ts | 1 + .../src/telemetry/telemetry.service.ts | 123 ++++++++++++++++++ .../backend/test/telemetry.test.ts | 50 ++++++- .../frontend/src/pages/CommandCenter.tsx | 2 +- .../scripts/local-prod-smoke.sh | 3 +- .../scripts/managed-prod-smoke.sh | 3 +- packages/casan-platform/README.md | 4 +- 14 files changed, 225 insertions(+), 38 deletions(-) diff --git a/docs/plans/CASAN_BACKLOG_STATUS.md b/docs/plans/CASAN_BACKLOG_STATUS.md index a11bfa7..28a4668 100644 --- a/docs/plans/CASAN_BACKLOG_STATUS.md +++ b/docs/plans/CASAN_BACKLOG_STATUS.md @@ -55,13 +55,13 @@ | **08 Context compression** | � MVP done+test | **CASAN-native token-killer** (Track 3) đã có: `context-compress.py` (dedup/extractive/structural, must-keep, tee, gate fail-able), `phase08-compression-tests.sh` 7/0 (WSL), nối CI. Còn: Track 1 nén INPUT + Track 2 nén VIEW liên-bước + Track 4 abstractive (gated) + nối H4/H5 trong pipeline thật. | | **12 Domain Pack SDK** | 📋 chưa bắt đầu (🔓 01 done, còn chờ 06) | Onboard bằng khai báo (golden/corpus/policy theo domain). Phụ thuộc 01 ✅, 06. | | **01 Restructure** | ✅ **done+test (2026-07-08, merged main)** | Đã tách: harness code → `packages/casan-harness/`, domain OKR → `apps/okr/domain/`, runtime state ở `.specify/`; **app promote lên git root** (hết wrapper `AINative_OKR_CASAN5/`); facade `.specify` symlink **gỡ sạch** (hard cutoff); path resolve qua `casan-paths.sh` (marker walk-up). Full gate **PASS=64 FAIL=0 SKIP=3** từ cấu trúc mới. CI (`.gitea/workflows/ci.yml`) + docs đã đồng bộ. Mở khoá 06/12. Chi tiết: `CASAN_PLAN_01_RESTRUCTURE.md`. | -| **13 Control Plane** | 🟡 core + **Track 1/2/3 + FinOps/SLO + Command Center + local-prod TLS/OIDC smoke** done+test | **Sửa kiến trúc: là tài sản harness, KHÔNG nằm trong OKR.** Governance core đã dời vào harness: `packages/casan-harness/scripts/bash/control-plane-settings.py` (settings versioned + audit hash-chain + deny-by-default + approval + rollback), `phase-control-plane-tests.sh` 9/0 + `phase-control-plane-hitl-tests.sh` 9/0 (nối CI). Đã gỡ khỏi `apps/okr` (OKR sạch: 46/0/3skip + 16/16). **Track 1 DONE**: Ops Console read-only telemetry. **Track 2 DONE**: settings API/UI tại `packages/casan-control-panel/` bọc `control-plane-settings.py` + `rbac-check.py`. **Track 3 DONE**: kill-switch API/UI; approval inbox/delegation/oversight API/UI bọc harness `approval-inbox.py` + `delegation-policy.yaml`, SoD, apply setting proposal qua governed store; IdP group claim→RBAC role mapping tested. **Track 4 local-prod DONE**: FinOps/SLO page + Docker/nginx/oauth2-proxy deploy scaffold; `local-prod-smoke.sh` passes `CP_LOCAL_SMOKE_PASS https_oidc=true actor=oidc-ops role=org-admin` and invokes authenticated `managed-prod-smoke.sh` on the local-prod endpoint (`CP_MANAGED_SMOKE_PASS actor=oidc-ops role=org-admin widgets=8`); `prod-readiness-check.sh` validates managed-prod TLS/OIDC/nginx prerequisites. **Command Center §8.6 DONE**: `GET /api/v1/command` + `/command` UI, 8 evidence-backed widgets, provenance envelope, executive briefing VI/EN, live ticker, evidence drawer. `npm run console:test` **20/0**, `npm run console:build` xanh. Targeted related suites: RBAC 12/0, C7 15/0. Còn: live managed prod deploy với cert/enterprise IdP/host thật (07 T2); RAI view và Ask CASAN thuộc Plan-15/18 follow-up. | +| **13 Control Plane** | 🟡 core + **Track 1/2/3 + FinOps/SLO + Command Center + local-prod TLS/OIDC smoke** done+test | **Sửa kiến trúc: là tài sản harness, KHÔNG nằm trong OKR.** Governance core đã dời vào harness: `packages/casan-harness/scripts/bash/control-plane-settings.py` (settings versioned + audit hash-chain + deny-by-default + approval + rollback), `phase-control-plane-tests.sh` 9/0 + `phase-control-plane-hitl-tests.sh` 9/0 (nối CI). Đã gỡ khỏi `apps/okr` (OKR sạch: 46/0/3skip + 16/16). **Track 1 DONE**: Ops Console read-only telemetry. **Track 2 DONE**: settings API/UI tại `packages/casan-control-panel/` bọc `control-plane-settings.py` + `rbac-check.py`. **Track 3 DONE**: kill-switch API/UI; approval inbox/delegation/oversight API/UI bọc harness `approval-inbox.py` + `delegation-policy.yaml`, SoD, apply setting proposal qua governed store; IdP group claim→RBAC role mapping tested. **Track 4 local-prod DONE**: FinOps/SLO page + Docker/nginx/oauth2-proxy deploy scaffold; `local-prod-smoke.sh` passes `CP_LOCAL_SMOKE_PASS https_oidc=true actor=oidc-ops role=org-admin` and invokes authenticated `managed-prod-smoke.sh` on the local-prod endpoint (`CP_MANAGED_SMOKE_PASS actor=oidc-ops role=org-admin widgets=9`); `prod-readiness-check.sh` validates managed-prod TLS/OIDC/nginx prerequisites. **Command Center §8.6 DONE**: `GET /api/v1/command` + `/command` UI, 9 evidence-backed widgets (gồm `chat_loop`), provenance envelope, executive briefing VI/EN, live ticker, evidence drawer. `npm run console:test` **26/0**, `npm run console:build` xanh. Targeted related suites: RBAC 12/0, C7 15/0. Còn: live managed prod deploy với cert/enterprise IdP/host thật (07 T2); RAI view thuộc Plan-15 follow-up. | | **14 RBAC** | � core done+test | RBAC decision engine trong harness: `packages/casan-harness/scripts/bash/rbac-check.py` (role×resource:action, scope org/project, deny-by-default, tenant isolation, sensitive→org-admin, SoD), `phase-rbac-tests.sh` 10/0 (WSL), nối CI. **Đã thêm:** tenant data-boundary (SEC-23 23.13, org-admin A không đụng B) + **audit quyết định vào H5** (`CASAN_RBAC_AUDIT_LOG`, `phase-rbac-audit` 5/0). Còn: enforcement trong web app (13), ánh xạ IdP claim→role thật (07-C4). | | **15 Responsible AI & Data Gov** | 🟡 core done+test+enforced | `rai-guard.py` (classify; PII→cloud deny; model-card; **retention** gate/purge-audit; **report** aggregate) + **enforcement** `harness-preflight.sh` chặn PII→cloud trước model-call, `model-router` opt-in `CASAN_PREFLIGHT`. `phase-rai` 12/0 + `phase-preflight` 5/0 (WSL), nối CI. Còn: view trên Control Plane. | | Future B1–B6 | 💤 vision | `CASAN_PLAN_FUTURE_PHASES.md` — approval workflow nâng cao · state machine · model benchmark · governed memory · auto-remediation · platform KPI. | | **17 Loop Engineering** | � T1–T6 done+test (offline) | **Agentic Loop Governance** — đủ 5 primitive + orchestrator (97/0 WSL, nối CI). T1 **Governor** (`loop-governor.py`; deny-by-default, no/corrupt policy→strict/HALT, on_exceed halt/escalate) 15/0; T2 **Convergence** (`loop-convergence.py`; repeat/thrash→OSCILLATING, flat→STALLED, fail-closed) 15/0; T3 **Verify Contract** (`loop-gate.py`; H4→DENY, unmet→FAIL, correction bounded→ESCALATE, no self-declared DONE) 20/0; T4 **Trace/Replay** (`loop-trace.py`; append-only hash-linked, edited→BREAK, tampered artifact→replay DRIFT) 16/0; T5 **Meta-loop** (`loop-metaloop.py`; propose≠apply, SoD, loosen>org_ceiling refused, apply qua governed CP store→đổi thật ceiling + rollback) 15/0; T6 **Orchestrator** (`loop-run.sh`; gate→governor→convergence→trace/turn, secure-by-default opt-out, nén giữa vòng) 16/0. State qua `CASAN_LOOP_STATE_ROOT` (repo `.specify/state` sạch). **Còn (infra):** T4 KMS-anchor head (A7 Vault), T6 widget Command Center (17.22, C5), live H3-judge. Chi tiết: `CASAN_PLAN_17_LOOP_ENGINEERING.md`. | | **16 Security audit remediation** | � P0/P1/P2 phần lớn done+test | **Remediation đã thực thi:** 28 SEC suite (151/0 WSL, nối `ci-harness-gate.sh`). Done: SEC-01..10, 12, 13, **14** (model-digest bỏ env-override ở prod/strict), 15, 16..21, **22** (trusted-time JWT `exp` ARCH-06 + tag proposal nguồn-không-tin ARCH-08), **26** (stored/second-order injection scan), 27..30, **23 Phase 1–5 offline** (multi-tenant: tenant-store+guard · per-tenant CP/audit/telemetry · RBAC data-boundary · tenant kill-switch/quota · ký registry · crypt at-rest per-tenant), **24 offline** (image digest-pin + ký workflow), **25 offline** (artifact attestation tested==deployed); **SEC-11 gộp vào SEC-17** (`CASAN_PROFILE=prod` enforce-by-default). **Còn 📋 planned (hạ tầng/process):** SEC-22 ARCH-10 (attestation ngoài) · SEC-23 23.11 (crypt qua Vault Transit) · **SEC-24 còn** (live CVE/OSV + scan image thật — offline image-pin/ký-workflow đã done) · **SEC-25 còn** (signed-commit enrollment + SLSA chain — offline artifact-attestation đã done). Chi tiết: `CASAN_PLAN_16` §0a/§2d. | -| **18 Chat Console** | 🟡 **MVP-0 + MVP-1 done+test; MVP-2 Track 4 + OPERATOR Track 5/6 + Track 8.1/8.2 done** · target arch còn planned | **Governed Chat Console** (cắt lát MVP chống lan man). **MVP-0 Ask CASAN read-only DONE**: `prompt-mode-router.py`, `chat-readonly.py`, `chat-session.schema.json`, H4 input/output scan, H5 chat audit hash-chain, H6 token telemetry, answer kèm evidence sources. **MVP-1 Operator DONE**: registered actions through `action-gate`, no free-command, action artifacts with provenance. **MVP-2 Track 4 DONE**: `agent-registry.yaml`, `chat-agent-resolver.py`, `chat:select_agent` RBAC, delegation hold, tool allowlist BLOCK, Control Panel agent picker. **MVP-2 OPERATOR Track 5/6 DONE**: `chat-turn.py` certifies UNCERTIFIED draft through Plan-17 `loop-run.sh` + trace verify/replay before side-effect release. **Track 8.1/8.2 DONE**: `chat-replay.py` verifies chat chain, evidence artifact hash, and OPERATOR loop replay; Control Panel `GET /api/v1/chat/replay`. Test: chat suites **42/0** (`phase-chat-prompt-router` 8/0, `phase-chat-readonly` 5/0, `phase-chat-session-audit` 3/0, `phase-chat-operator` 8/0, `phase-chat-agent-select` 8/0, `phase-chat-pipeline` 4/0, `phase-chat-stream-hold` 2/0, `phase-chat-replay` 4/0), Control Panel **25/0** + build xanh. Next: CODEGEN-as-loop + Track 8.3/8.4 replay widget/approvals escalation → MVP-3 multi-tenant. | +| **18 Chat Console** | 🟡 **MVP-0 + MVP-1 done+test; MVP-2 Track 4 + OPERATOR Track 5/6 + Track 8.1/8.2/8.3 done** · target arch còn planned | **Governed Chat Console** (cắt lát MVP chống lan man). **MVP-0 Ask CASAN read-only DONE**: `prompt-mode-router.py`, `chat-readonly.py`, `chat-session.schema.json`, H4 input/output scan, H5 chat audit hash-chain, H6 token telemetry, answer kèm evidence sources. **MVP-1 Operator DONE**: registered actions through `action-gate`, no free-command, action artifacts with provenance. **MVP-2 Track 4 DONE**: `agent-registry.yaml`, `chat-agent-resolver.py`, `chat:select_agent` RBAC, delegation hold, tool allowlist BLOCK, Control Panel agent picker. **MVP-2 OPERATOR Track 5/6 DONE**: `chat-turn.py` certifies UNCERTIFIED draft through Plan-17 `loop-run.sh` + trace verify/replay before side-effect release. **Track 8.1/8.2 DONE**: `chat-replay.py` verifies chat chain, evidence artifact hash, and OPERATOR loop replay; Control Panel `GET /api/v1/chat/replay`. **Track 8.3 DONE**: Command Center `chat_loop` widget reads chat audit/replay evidence, loop ticker, budget gauge, and click-through evidence drawer. Test: chat suites **42/0** (`phase-chat-prompt-router` 8/0, `phase-chat-readonly` 5/0, `phase-chat-session-audit` 3/0, `phase-chat-operator` 8/0, `phase-chat-agent-select` 8/0, `phase-chat-pipeline` 4/0, `phase-chat-stream-hold` 2/0, `phase-chat-replay` 4/0), Control Panel **26/0** + build xanh. Next: CODEGEN-as-loop + Track 8.4 approvals escalation → MVP-3 multi-tenant. | --- ## Trần điểm & điều kiện lên "Strong (81+)" diff --git a/docs/plans/CASAN_PLAN_00_INDEX.md b/docs/plans/CASAN_PLAN_00_INDEX.md index 6ea5e37..e38a2a4 100644 --- a/docs/plans/CASAN_PLAN_00_INDEX.md +++ b/docs/plans/CASAN_PLAN_00_INDEX.md @@ -35,7 +35,7 @@ | 15 | `CASAN_PLAN_15_RESPONSIBLE_AI_DATA_GOV.md` | Responsible AI & Data Governance (FPT §14.3–14.4) | 📋 | | 16 | `CASAN_PLAN_16_SECURITY_AUDIT_REMEDIATION.md` | Security audit core harness + kế hoạch vá (tamper-evidence/injection/fail-open) | � P0/P1/P2 done | | 17 | `CASAN_PLAN_17_LOOP_ENGINEERING.md` | Loop Engineering / Agentic Loop Governance (budget governor, convergence, verify-contract, loop trace/replay, meta-loop) | 📋 | -| 18 | `CASAN_PLAN_18_CHAT_CONSOLE.md` | Governed Chat Console (target arch; **MVP-0 Ask CASAN + MVP-1 Operator done; MVP-2 Track 4/5/6 + Track 8.1/8.2 done** → widget/approvals → multi-tenant) | 🟡 MVP-0/1 + MVP-2 partial done | +| 18 | `CASAN_PLAN_18_CHAT_CONSOLE.md` | Governed Chat Console (target arch; **MVP-0 Ask CASAN + MVP-1 Operator done; MVP-2 Track 4/5/6 + Track 8.1/8.2/8.3 done** → approvals/CODEGEN → multi-tenant) | 🟡 MVP-0/1 + MVP-2 partial done | | Future | `CASAN_PLAN_FUTURE_PHASES.md` | Approval workflow, state machine, benchmark, memory, remediation, KPI | 💤 vision | > **Không có plan số 11:** số 11 được bỏ trống có chủ ý — nhánh eval/traceability đã diff --git a/docs/plans/CASAN_PLAN_13_CONTROL_PLANE.md b/docs/plans/CASAN_PLAN_13_CONTROL_PLANE.md index 0741aaf..72f3d96 100644 --- a/docs/plans/CASAN_PLAN_13_CONTROL_PLANE.md +++ b/docs/plans/CASAN_PLAN_13_CONTROL_PLANE.md @@ -21,8 +21,8 @@ > **Track 4 offline partial**: > FinOps/SLO page reads provider usage + business KPI artifacts and budget cap setting > without fabricating missing values. -> **Command Center §8.6 built:** `GET /api/v1/command` returns eight evidence-backed -> widgets with `{source, artifact_path, commit, run_at, verified}` envelopes; React +> **Command Center §8.6 built:** `GET /api/v1/command` returns nine evidence-backed +> widgets, including Plan-18 `chat_loop`, with `{source, artifact_path, commit, run_at, verified}` envelopes; React > `/command` page has executive briefing VI/EN, live governance ticker, and evidence drawer. > **Deploy scaffold + local-prod smoke:** `Dockerfile.control-panel-api`, > `Dockerfile.control-panel-ui`, `docker-compose.control-panel.yml`, @@ -30,14 +30,14 @@ > `docker compose config`; nginx config passes `nginx -t` with a temp cert/upstream aliases; > `packages/casan-control-panel/scripts/local-prod-smoke.sh` passes > `CP_LOCAL_SMOKE_PASS https_oidc=true actor=oidc-ops role=org-admin` and asserts -> `/api/v1/command` returns all 8 widgets with provenance envelopes. +> `/api/v1/command` returns all 9 widgets with provenance envelopes. > `prod-readiness-check.sh` validates managed-prod host prerequisites (compose, TLS, > oauth2-proxy OIDC env, nginx) without printing secrets. > `managed-prod-smoke.sh` validates deployed endpoint auth enforcement (unauth blocked, > spoofed identity headers blocked) and, with a real IdP cookie jar, authenticated > `/settings` + `/command` behavior. > Local-prod now runs that managed smoke with the mock-IdP authenticated cookie jar and -> emits `CP_MANAGED_SMOKE_PASS actor=oidc-ops role=org-admin widgets=8`. +> emits `CP_MANAGED_SMOKE_PASS actor=oidc-ops role=org-admin widgets=9`. > Nginx `auth_request` overwrites identity headers and API maps IdP groups → RBAC roles. > **Còn:** managed host/cert/enterprise IdP env thật (07 T2). > @@ -190,11 +190,11 @@ monitor + manage + settings"] --> API["Control-Plane API (NestJS)"] - [x] Các key settings đang whitelist trong `control-plane-settings.py` quản lý được qua console (compression/cost/model/security/kill-switch/loop); kill-switch UI có. - [x] FinOps/SLO board đọc provider usage + business KPI artifact thật; thiếu budget cap thì báo chưa cấu hình. - [x] Approval inbox + delegation L0-L5 + oversight log có API/UI và test SoD/apply. -- [x] Command Center §8.6 có API/UI: 8 widget evidence-backed, provenance envelope, executive briefing VI/EN, live ticker, evidence drawer. +- [x] Command Center §8.6 có API/UI: 9 widget evidence-backed (gồm `chat_loop`), provenance envelope, executive briefing VI/EN, live ticker, evidence drawer. - [x] Local production-like deploy có TLS + OIDC + oauth2-proxy + nginx `auth_request`; smoke pass qua HTTPS. - [x] Managed production readiness checker cho cert/host/enterprise IdP env thật. - [x] Managed production endpoint smoke script cho unauth/spoof/authenticated-cookie checks. -- [x] Authenticated managed endpoint smoke pass trên local-prod HTTPS/OIDC endpoint (`CP_MANAGED_SMOKE_PASS actor=oidc-ops role=org-admin widgets=8`). +- [x] Authenticated managed endpoint smoke pass trên local-prod HTTPS/OIDC endpoint (`CP_MANAGED_SMOKE_PASS actor=oidc-ops role=org-admin widgets=9`). - [ ] Managed production deploy với cert/host/enterprise IdP thật (nối Plan-07 TIER 2). - [x] Red-team mục 5 targeted xanh: control-plane 9/0 + HITL 9/0 + RBAC 12/0 + C7 15/0; full gate dài đã pass qua adversarial 44/0 nhưng A6 mất thời gian nên không claim full-gate xanh trong lượt này. @@ -270,7 +270,7 @@ monitor + manage + settings"] --> API["Control-Plane API (NestJS)"] | Task | Việc | Verify | |---|---|---| | 13V.0 | Chuẩn hóa envelope provenance trong Control API; core `--json` bổ sung sau khi có widget cần gọi CLI live | ✅ `commandCenter()` envelope có `source/artifact_path/commit/run_at/verified/status` | -| 13V.1 | API `GET /api/v1/command` gom 8 widget đọc-only, không ghi state | ✅ `console:test` contract 20/0 | +| 13V.1 | API `GET /api/v1/command` gom 9 widget đọc-only, không ghi state | ✅ `console:test` contract 26/0 | | 13V.2 | V1 Maturity gauge + radar H1–H7 (click widget → evidence drawer) | ✅ `/command` widget + drawer | | 13V.3 | V2 HITL panel + approvals inbox/oversight | ✅ đọc approval store; thiếu artifact → `no_data` | | 13V.4 | V3 kill-switch + guardrail badges; V7 certified seal | ✅ đọc incidents + audit-head/audit tail | @@ -278,9 +278,10 @@ monitor + manage + settings"] --> API["Control-Plane API (NestJS)"] | 13V.6 | V8 self-improve pipeline | ✅ hiển thị primitive present + proposal nếu có trong inbox; không fabricate queue | | 13V.7 | Executive Briefing mode (song ngữ VI/EN) + Live ticker + Evidence drawer dùng chung | ✅ `/command` page | | 13V.8 | Provenance guard: thiếu source→`no_data`/`missing`, stale vẫn qua freshness | ✅ contract test + UI status badge | +| 13V.9 | Widget Loop/Chat chuyên sâu | ✅ `chat_loop` widget + ticker/budget/replay evidence drawer (Plan-18 Track 8.3) | -> **Hiện trạng:** Command Center baseline đã có. Widget Loop/Chat chuyên sâu và RAI view -> nằm ở Plan-17/18/15 follow-up, không còn chặn Plan-13 baseline. +> **Hiện trạng:** Command Center baseline và Widget Loop/Chat chuyên sâu đã có. RAI view +> nằm ở Plan-15 follow-up, không còn chặn Plan-13 baseline. --- _Liên quan: `CASAN_PLAN_07_PRODUCTION_HARDENING.md` (D3 dashboard, C4 IdP, C7 kill-switch, TIER 2 deploy) · `CASAN_PLAN_14_RBAC.md` (phân quyền) · `CASAN_PLAN_04_SELFIMPROVE.md` (proposal→approval) · `CASAN_PLAN_08_CONTEXT_COMPRESSION.md` (compression-policy là settings) · `CASAN_PLAN_15_RESPONSIBLE_AI_DATA_GOV.md` (RAI/data view)._ diff --git a/docs/plans/CASAN_PLAN_13_NEXT_STEPS.puml b/docs/plans/CASAN_PLAN_13_NEXT_STEPS.puml index 2010626..8d6b885 100644 --- a/docs/plans/CASAN_PLAN_13_NEXT_STEPS.puml +++ b/docs/plans/CASAN_PLAN_13_NEXT_STEPS.puml @@ -2,7 +2,7 @@ !theme plain top to bottom direction -title CASAN Plan 13 - Baseline Complete and Remaining Follow-ups +title CASAN Plan 13 - Baseline + Chat/Loop Widget Complete and Remaining Follow-ups skinparam backgroundColor #FAFAFA skinparam componentStyle rectangle @@ -20,13 +20,14 @@ package "DONE - Plan 13 Control Panel" #E8F5E9 { component "NestJS Control API\nread telemetry + governed writes" as api_done #A9DFBF component "Harness governance core\nsettings + RBAC + approvals\nkill-switch + H5 audit" as gov_core #A9DFBF component "Local-prod deploy smoke\nTLS + oauth2-proxy + mock OIDC\nnginx auth_request" as local_prod #D5F5E3 - component "Command Center baseline\n8 evidence-backed widgets\nbriefing + ticker + evidence drawer" as command_done #D5F5E3 + component "Command Center baseline\n9 evidence-backed widgets\nbriefing + ticker + evidence drawer" as command_done #D5F5E3 + component "Loop/Chat widget\nbudget + replay drawer + chat ticker\nextends Command Center contract" as loop_widgets_done #D5F5E3 } package "NEXT - offline platform follow-ups" #FFF8E1 { component "RAI / Data Gov view\nretention + model-card + PII report" as rai #F9E79F - component "Ask CASAN MVP-0\nread-only prompt router\nsource-cited answers + H4/H5/H6" as chat_ro #F9E79F - component "Loop/Chat widgets\nbudget + replay drawer + chat ticker\nextends Command Center contract" as loop_widgets #FAD7A0 + component "Approvals escalation\nturn ESCALATED -> inbox\nJWT + SoD + reason" as approvals #F9E79F + component "CODEGEN-as-loop\nH4 guarded codegen\nloop-certified artifacts" as codegen_loop #F9E79F } package "EXTERNAL / MANAGED PROD" #FDEDEC { @@ -41,11 +42,12 @@ api_done --> gov_core : wraps harness CLIs api_done --> local_prod : verified behind auth proxy api_done --> command_done : GET /api/v1/command command_done --> gov_core : provenance + audit state +loop_widgets_done --> command_done : specialized widget lead --> rai : next if staying offline rai --> gov_core : read RAI + policy artifacts -chat_ro --> gov_core : read-only, audited, guarded -loop_widgets --> command_done : add specialized widgets +approvals --> gov_core : proposals + SoD +codegen_loop --> gov_core : guarded loop-run managed_oidc ..> local_prod : replace mock IdP/cert alerts ..> gov_core : route real incidents @@ -53,9 +55,9 @@ billing ..> command_done : ground-truth FinOps note right of rai Recommended next: -1. If no external infra: build RAI view or Ask CASAN MVP-0. +1. If no external infra: build RAI view or Plan-18 approvals/CODEGEN slice. 2. If infra is available: promote local-prod OIDC to managed prod. -3. Loop/Chat widgets should extend the existing Command envelope. +3. Keep chat widgets on the existing Command envelope. 4. Keep every metric evidence-backed with provenance. end note diff --git a/docs/plans/CASAN_PLAN_18_CHAT_CONSOLE.md b/docs/plans/CASAN_PLAN_18_CHAT_CONSOLE.md index be1a74b..f6b1970 100644 --- a/docs/plans/CASAN_PLAN_18_CHAT_CONSOLE.md +++ b/docs/plans/CASAN_PLAN_18_CHAT_CONSOLE.md @@ -1,6 +1,6 @@ # KẾ HOẠCH 18 — Governed Chat Console (Chat-as-Loop qua Control Plane) -> Status 2026-07-08: **🟡 MVP-0 + MVP-1 done+test; MVP-2 Track 4 + Operator Track 5/6 done — target architecture còn planned.** +> Status 2026-07-08: **🟡 MVP-0 + MVP-1 done+test; MVP-2 Track 4 + Operator Track 5/6 + Track 8.1/8.2/8.3 done — target architecture còn planned.** > Đã implement **Ask CASAN — Read-only Evidence Assistant** qua harness + Control > Panel (`/api/v1/chat/ask`, `/chat` UI): Prompt Router deterministic > `READ_ONLY/OPERATOR/BLOCK/NOT_SUPPORTED`, context whitelist, H4 scan in/out, H5 @@ -11,7 +11,8 @@ > gate chọn agent, delegation hold, tool allowlist block, Control Panel agent picker. > **Operator Track 5/6** đã bind mỗi side-effect turn vào Plan-17 `loop-run.sh`, > verify trace/replay, H4 preflight/context/tool-output scan, và chỉ release action -> sau khi draft được certify. +> sau khi draft được certify. **Track 8.1/8.2/8.3** đã có replay/verify-chain +> foundation và Command Center Chat/Loop widget đọc chat audit/replay evidence thật. > Mục tiêu > tổng thể vẫn là thêm **cửa sổ chat** vào Control Plane (Plan-13) như một > **bề mặt tương tác của core harness** — mỗi lượt chat là **một loop-run được @@ -288,7 +289,7 @@ flowchart TD |---|---|---|---| | 18.8.1 | ✅ **Replay** một turn (verify lại artifact đã ghi) phát hiện tamper/non-determinism | `chat-replay.py` + `loop-trace.py replay` | sửa artifact turn → replay lệch | | 18.8.2 | ✅ `verify-chain` lịch sử chat (tamper-evidence, SEC-01/02; KMS khi TIER-2 là follow-up) | `chat-replay.py verify-chain` | sửa 1 turn → chain BREAK | -| 18.8.3 | Widget **Chat/Loop** trên Command Center (13 §8.6): ticker per-iteration, budget gauge, click → Evidence drawer | nối Plan-13 §8.6 | số khớp fixture; click → evidence | +| 18.8.3 | ✅ Widget **Chat/Loop** trên Command Center (13 §8.6): ticker per-iteration, budget gauge, click → Evidence drawer | `GET /api/v1/command` + `/command` | số khớp fixture; click → evidence | | 18.8.4 | **Approvals/Escalation panel**: turn `ESCALATED` vào approvals inbox (13 §3.4), duyệt/từ chối (JWT + SoD + lý do) | nối Plan-13 §3.4 | escalate → pending; JWT giả → DENY | ### Track 9 — Multi-tenant hardening `[MVP-3]` @@ -361,6 +362,12 @@ cập nhật tổng test ở `CASAN_HARDENING_STATUS.md`. - [x] Control Plane `GET /api/v1/chat/replay` wraps harness replay; no verdict reimplementation in NestJS. - [x] Tampered operator evidence artifact → `DRIFT`; tampered chat audit → `BREAK`. Verify: `phase-chat-replay` 4/0, `console:test` 25/0, `console:build` xanh. +**MVP-2 Track 8.3 (Command Center Chat/Loop widget):** +- [x] `GET /api/v1/command` exposes `chat_loop` widget backed by `.specify/logs/chat/chat-turns.jsonl` and harness `chat-replay.py`. +- [x] Widget metrics include turns, answered/action-completed counts, loop-run count, replay records, replayed loops, and token budget gauge from real chat metrics. +- [x] `/command` renders the Chat/Loop card in the existing evidence drawer flow; ticker includes recent chat/loop turns. +- [x] Fixture test proves counts and budget gauge match evidence. Verify: `console:test` 26/0, `console:build` xanh, `phase-chat-replay` 4/0. + **Full (target architecture) — DoD tổng:** - [ ] Turn chạy **đúng như một loop-run Plan-17** (không định nghĩa vòng lặp riêng). - [ ] **Không đường vòng:** test chứng minh bỏ bất kỳ gate nào (preflight/verify) → FAIL. @@ -374,11 +381,11 @@ cập nhật tổng test ở `CASAN_HARDENING_STATUS.md`. --- ## 7. Ghi chú trung thực & Non-goals (không lan man) -- **[MVP-0 + MVP-1 + MVP-2 Track 4 + Operator Track 5/6 + Track 8.1/8.2] — 🟡 done+test.** +- **[MVP-0 + MVP-1 + MVP-2 Track 4 + Operator Track 5/6 + Track 8.1/8.2/8.3] — 🟡 done+test.** Ask CASAN read-only, Operator registered actions, agent/skill governance foundation, - OPERATOR chat-as-loop/draft-hold, và replay/verify-chain foundation đã có harness - CLI + Control Panel API. Các phase CODEGEN-as-loop / replay widget / approvals - escalation / multi-tenant production vẫn chưa mở. + OPERATOR chat-as-loop/draft-hold, replay/verify-chain foundation, và Command Center + Chat/Loop widget đã có harness CLI + Control Panel API/UI. Các phase CODEGEN-as-loop / + approvals escalation / multi-tenant production vẫn chưa mở. - **MVP-first (chống lan man):** **MVP-0 (Ask CASAN read-only) KHÔNG phụ thuộc Plan-17/ 14/SEC-23** — làm được ngay trên nền H4/H5/H6 hiện có. Chỉ **MVP-2 trở đi** (chat-as- loop + agent) mới cần Plan-17 (T1–T3) + Plan-14 RBAC; **MVP-3** mới cần SEC-23 tenant. diff --git a/docs/plans/CASAN_TEAM_ALLOCATION_3DEV.md b/docs/plans/CASAN_TEAM_ALLOCATION_3DEV.md index e075fce..6bd88f0 100644 --- a/docs/plans/CASAN_TEAM_ALLOCATION_3DEV.md +++ b/docs/plans/CASAN_TEAM_ALLOCATION_3DEV.md @@ -77,8 +77,8 @@ approval JWT thật ([16 SEC-07](CASAN_PLAN_16_SECURITY_AUDIT_REMEDIATION.md)), |---|---|---|:--:|:--:|---| | B9 | ✅ **done** — **MVP-0 Ask CASAN read-only**: Prompt Router (Track 0) + Read-only Ask CASAN (Track 1) + session/audit (Track 2) + Control Panel API/UI (Track 7). H4 in/out, H5 audit hash-chain, H6 token, evidence sources; chat suites 14/0, Control Panel 23/0 + build xanh. Real model provider binding remains Track M follow-up when a provider is enabled. | [18 §1b, Track 0/1/2/7/M](CASAN_PLAN_18_CHAT_CONSOLE.md) | L | ✅ | done | | B10 | ✅ **done** — **MVP-1 Operator mode**: registered actions `run tests` / `build pack` / `verify pack`, no free-command, all through `action-gate`, action artifacts with provenance, Control Panel quick actions. `phase-chat-operator` 8/0; total chat suites 24/0; Control Panel 24/0 + build xanh. | [18 Track 3](CASAN_PLAN_18_CHAT_CONSOLE.md) | M | ✅ | done | -| B11 | 🟡 **partial done** — Track 4 Agent/Skill governance + OPERATOR Track 5/6 chat-as-loop/draft-hold + Track 8.1/8.2 replay foundation. `chat-turn.py` certifies OPERATOR draft through Plan-17 `loop-run.sh`; `chat-replay.py` detects evidence artifact drift and chat-chain tamper. Chat suites 42/0; Control Panel 25/0 + build xanh. **Remaining MVP-2:** CODEGEN-as-loop and Track 8.3/8.4 replay widget/approvals escalation. | [18 Track 4/5/6/8](CASAN_PLAN_18_CHAT_CONSOLE.md) | L | 🔗 | B6, C7(RBAC), B10 | -| B12 | **Widget Loop/Chat** trên Command Center (loop ticker/budget/replay drawer) | [17 (17.22)](CASAN_PLAN_17_LOOP_ENGINEERING.md) · [18 Track 8](CASAN_PLAN_18_CHAT_CONSOLE.md) | M | 🔗 | C5 ✅ baseline | +| B11 | 🟡 **partial done** — Track 4 Agent/Skill governance + OPERATOR Track 5/6 chat-as-loop/draft-hold + Track 8.1/8.2 replay foundation + Track 8.3 Command Center Chat/Loop widget. `chat-turn.py` certifies OPERATOR draft through Plan-17 `loop-run.sh`; `chat-replay.py` detects evidence artifact drift and chat-chain tamper; `/command` shows chat loop ticker/budget/evidence. Chat suites 42/0; Control Panel 26/0 + build xanh. **Remaining MVP-2:** CODEGEN-as-loop and Track 8.4 approvals escalation. | [18 Track 4/5/6/8](CASAN_PLAN_18_CHAT_CONSOLE.md) | L | 🔗 | B6, C7(RBAC), B10 | +| B12 | ✅ **done** — Widget Loop/Chat trên Command Center (`chat_loop` widget, loop ticker, budget gauge, replay evidence drawer) | [17 (17.22)](CASAN_PLAN_17_LOOP_ENGINEERING.md) · [18 Track 8](CASAN_PLAN_18_CHAT_CONSOLE.md) | M | ✅ | C5 ✅ baseline | | B13 | **MVP-3** multi-tenant chat hardening (Track 9) | [18 Track 9](CASAN_PLAN_18_CHAT_CONSOLE.md) | M | 🔗 | A3(SEC-23) | --- diff --git a/packages/casan-control-panel/README.md b/packages/casan-control-panel/README.md index 093bb69..3c55062 100644 --- a/packages/casan-control-panel/README.md +++ b/packages/casan-control-panel/README.md @@ -56,7 +56,7 @@ Approval inbox / HITL: - `POST /api/v1/approvals/decide` — approve/reject with SoD and reason; approved settings proposals apply through `control-plane-settings.py`. -Governed Chat (Plan-18 MVP-0/1 + MVP-2 Track 4 and Operator Track 5/6): +Governed Chat (Plan-18 MVP-0/1 + MVP-2 Track 4, Operator Track 5/6, and Track 8.1-8.3): - `POST /api/v1/chat/ask` — Ask CASAN endpoint. The API only wraps harness `chat-turn.py`; router verdicts, H4 input/output scan, action-gate decisions, @@ -71,11 +71,14 @@ Governed Chat (Plan-18 MVP-0/1 + MVP-2 Track 4 and Operator Track 5/6): - `GET /api/v1/chat/replay?chatId=` — verifies chat-chain integrity, evidence artifact hashes, and OPERATOR loop trace replay through harness `chat-replay.py`. +- `GET /api/v1/command` — includes the `chat_loop` Command Center widget backed + by chat audit/replay evidence, loop ticker rows, and token budget gauge. - `/chat` UI shows actor/role scope, `mode/risk/decision` badges, certified answer, evidence sources, registered operator actions, agent binding, loop certification, action-gate status, router details, and audit hash. Side-effect requests outside registered actions return governed `BLOCK` or `NOT_SUPPORTED` responses; operator side effects are held until the loop draft is certified. +- `/command` UI renders the Chat/Loop widget in the existing evidence drawer flow. FinOps/SLO: @@ -113,10 +116,10 @@ The scaffold includes `Dockerfile.control-panel-api`, `Dockerfile.control-panel- overwrites browser-supplied `X-CASAN-*` headers, and passes IdP group claims to the API for RBAC mapping. The local smoke starts a self-signed HTTPS stack with a mock OIDC IdP and expects `CP_LOCAL_SMOKE_PASS https_oidc=true actor=oidc-ops role=org-admin`; it also -asserts the Command Center returns all eight widgets with provenance envelopes and invokes +asserts the Command Center returns all nine widgets with provenance envelopes and invokes `managed-prod-smoke.sh` with the authenticated mock-IdP cookie jar. A passing local run therefore emits both `CP_LOCAL_SMOKE_PASS ...` and -`CP_MANAGED_SMOKE_PASS actor=oidc-ops role=org-admin widgets=8`. +`CP_MANAGED_SMOKE_PASS actor=oidc-ops role=org-admin widgets=9`. Managed production readiness, once the host has real TLS files and an enterprise OIDC env file: diff --git a/packages/casan-control-panel/backend/src/common/app-root.ts b/packages/casan-control-panel/backend/src/common/app-root.ts index be1b12b..188dbfc 100644 --- a/packages/casan-control-panel/backend/src/common/app-root.ts +++ b/packages/casan-control-panel/backend/src/common/app-root.ts @@ -48,6 +48,7 @@ export const PATHS = { benignFp: env('CASAN_CP_BENIGN_FP', 'docs/output/casan/benign-fp-report.json'), scoringReport: env('CASAN_CP_SCORING_REPORT', 'docs/output/casan/phase3-real-run-scoring.md'), approvalInbox: env('CASAN_CP_APPROVAL_INBOX', '.specify/level5/approval-inbox.json'), + chatAudit: env('CASAN_CP_CHAT_AUDIT', '.specify/logs/chat/chat-turns.jsonl'), delegationPolicy: env('CASAN_CP_DELEGATION_POLICY', 'packages/casan-harness/config/delegation-policy.yaml'), selfImprove: env('CASAN_CP_SELF_IMPROVE', 'packages/casan-harness/scripts/bash/self-improve.py'), }; diff --git a/packages/casan-control-panel/backend/src/telemetry/telemetry.service.ts b/packages/casan-control-panel/backend/src/telemetry/telemetry.service.ts index a583a11..77d8a61 100644 --- a/packages/casan-control-panel/backend/src/telemetry/telemetry.service.ts +++ b/packages/casan-control-panel/backend/src/telemetry/telemetry.service.ts @@ -2,12 +2,24 @@ // packages/casan-harness/tests/generate-agentops-dashboard.py; all numbers come from real // on-disk feeds. Missing feeds degrade to zero/empty + a `stale` flag — never fabricated. import { existsSync, readFileSync, statSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; import { join, relative } from 'node:path'; import { Injectable } from '@nestjs/common'; import { APP_ROOT, PATHS, isStale, metricsAgeSeconds, STALE_AFTER_S } from '../common/app-root.js'; import { readJsonl, readJson, readHead, readTrace } from './telemetry.reader.js'; type Row = Record; +type ChatReplayResult = { + ok: boolean; + decision: string; + records: number; + loop_replayed?: number; + diffs?: any[]; + audit_path?: string; + error?: string; +}; + +const CHAT_REPLAY_CLI = join(APP_ROOT, 'packages', 'casan-harness', 'scripts', 'bash', 'chat-replay.py'); const num = (v: any) => (typeof v === 'number' && isFinite(v) ? v : 0); const sum = (rows: Row[], k: string) => rows.reduce((a, r) => a + num(r[k]), 0); const count = (rows: Row[], pred: (r: Row) => boolean) => rows.reduce((a, r) => a + (pred(r) ? 1 : 0), 0); @@ -53,6 +65,108 @@ function widget( return { id, title, status, summary, metrics, envelope, evidence }; } +function parseJson(raw: string): T | null { + if (!raw) return null; + try { + return JSON.parse(raw) as T; + } catch { + return null; + } +} + +function runChatReplay(chatAuditPath: string): ChatReplayResult { + if (!existsSync(chatAuditPath)) { + return { ok: true, decision: 'NO_DATA', records: 0, loop_replayed: 0, diffs: [], audit_path: chatAuditPath }; + } + try { + const stdout = execFileSync('python3', [CHAT_REPLAY_CLI, 'replay'], { + cwd: APP_ROOT, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + env: { ...process.env, CASAN_CHAT_AUDIT_LOG: chatAuditPath }, + timeout: 15000, + }); + const parsed = parseJson>(stdout.trim()); + return { ok: true, decision: String(parsed?.decision ?? 'UNKNOWN'), records: num(parsed?.records), ...parsed }; + } catch (err: any) { + const stdout = String(err?.stdout ?? '').trim(); + const stderr = String(err?.stderr ?? '').trim(); + const parsed = parseJson>(stdout); + if (parsed) { + return { ok: false, decision: String(parsed.decision ?? 'ERROR'), records: num(parsed.records), ...parsed }; + } + return { ok: false, decision: 'ERROR', records: 0, loop_replayed: 0, diffs: [], audit_path: chatAuditPath, error: stderr || stdout || 'CHAT_REPLAY_FAILED' }; + } +} + +function isChatMetric(row: Row) { + const agent = String(row.agent ?? ''); + const step = String(row.step ?? ''); + return agent.startsWith('chat.') || step === 'ask-casan-readonly' || step.startsWith('operator:'); +} + +export function buildChatLoopWidget( + chatAudit: Row[], + metrics: Row[], + chatAuditPath = PATHS.chatAudit, + replay: ChatReplayResult = runChatReplay(chatAuditPath), +) { + const latest = chatAudit[chatAudit.length - 1] ?? null; + const chatMetrics = metrics.filter(isChatMetric); + const tokenBudget = Math.max(Number(process.env.CASAN_CHAT_TOKEN_BUDGET ?? 100000) || 100000, 1); + const totalTokens = sum(chatMetrics, 'total_tokens'); + const status = replay.decision === 'BREAK' + ? 'fail' + : replay.decision === 'DRIFT' || replay.decision === 'ERROR' + ? 'warn' + : chatAudit.length > 0 + ? 'ok' + : 'no_data'; + const loopRuns = count(chatAudit, (r) => Boolean(r.loop_run?.run_id)); + const verifiedReplay = replay.decision === 'MATCH' || replay.decision === 'NO_DATA'; + + return widget( + 'chat_loop', + 'Chat / Loop console', + status, + `${chatAudit.length} turn(s), ${loopRuns} loop-run(s), replay ${replay.decision}`, + { + turns: chatAudit.length, + answered: count(chatAudit, (r) => r.decision === 'ANSWERED'), + action_completed: count(chatAudit, (r) => r.decision === 'ACTION_COMPLETED'), + loop_runs: loopRuns, + replay_records: replay.records, + replay_loop_replayed: num(replay.loop_replayed), + budget_used_pct: pct(totalTokens, tokenBudget), + }, + artifact(chatAuditPath, 'chat-turns.jsonl + chat-replay.py', verifiedReplay), + { + replay, + budget: { total_tokens: totalTokens, token_budget: tokenBudget, used_pct: pct(totalTokens, tokenBudget) }, + latest_turn: latest ? { + seq: latest.seq ?? null, + timestamp: latest.timestamp ?? null, + chat_id: latest.chat_id ?? null, + turn_id: latest.turn_id ?? null, + tenant_id: latest.tenant_id ?? null, + mode: latest.mode ?? null, + decision: latest.decision ?? null, + loop_run: latest.loop_run ?? null, + record_hash: latest.record_hash ?? null, + } : null, + recent_turns: recent(chatAudit, 5).map((r) => ({ + seq: r.seq ?? null, + timestamp: r.timestamp ?? null, + chat_id: r.chat_id ?? null, + turn_id: r.turn_id ?? null, + decision: r.decision ?? null, + mode: r.mode ?? null, + loop_run_id: r.loop_run?.run_id ?? null, + })), + }, + ); +} + @Injectable() export class TelemetryService { private freshness() { @@ -184,6 +298,7 @@ export class TelemetryService { const security = readJsonl(PATHS.security); const incidents = readJsonl(PATHS.incidents); const actionGate = readJsonl(PATHS.actionGate); + const chatAudit = readJsonl(PATHS.chatAudit); const traceability = readJson(PATHS.traceability) as any; const kpi = readJson(PATHS.businessKpi) as any; const drift = readJson(PATHS.drift) as any; @@ -313,12 +428,19 @@ export class TelemetryService { artifact(PATHS.selfImprove, 'self-improve.py'), { proposals: recent(selfImproveRelated, 5) }, ), + chat_loop: buildChatLoopWidget(chatAudit, metrics), }; const ticker = [ ...recent(oversight, 10).map((e) => ({ at: e.at ?? e.created_at ?? null, kind: 'oversight', text: `${e.event ?? 'oversight'} ${e.proposal_id ?? ''}`.trim(), status: e.status ?? 'event' })), ...recent(incidents, 10).map((e) => ({ at: e.timestamp ?? null, kind: 'incident', text: `${e.event ?? 'incident'} ${e.scope ?? ''}`.trim(), status: e.severity ?? 'event' })), ...recent(audit, 10).map((e) => ({ at: e.timestamp ?? null, kind: 'audit', text: `${e.action ?? 'action'} ${e.decision ?? ''}`.trim(), status: e.decision ?? 'event' })), + ...recent(chatAudit, 10).map((e) => ({ + at: e.timestamp ?? null, + kind: 'chat_loop', + text: `${e.decision ?? 'turn'} ${e.chat_id ?? ''} ${e.loop_run?.run_id ? `loop:${e.loop_run.run_id}` : ''}`.trim(), + status: e.decision ?? 'event', + })), ].sort((a, b) => String(b.at ?? '').localeCompare(String(a.at ?? ''))).slice(0, 20); return { @@ -331,6 +453,7 @@ export class TelemetryService { { label: 'Safety', value: widgets.security.summary, status: widgets.security.status, widget: 'security' }, { label: 'Economy', value: widgets.finops.summary, status: widgets.finops.status, widget: 'finops' }, { label: 'Certification', value: widgets.certification.summary, status: widgets.certification.status, widget: 'certification' }, + { label: 'Chat/Loop', value: widgets.chat_loop.summary, status: widgets.chat_loop.status, widget: 'chat_loop' }, ], ticker, }; diff --git a/packages/casan-control-panel/backend/test/telemetry.test.ts b/packages/casan-control-panel/backend/test/telemetry.test.ts index a055721..a6b6fc3 100644 --- a/packages/casan-control-panel/backend/test/telemetry.test.ts +++ b/packages/casan-control-panel/backend/test/telemetry.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { readJsonl, readJson, readHead } from '../src/telemetry/telemetry.reader.js'; import { APP_ROOT, PATHS, isStale, metricsAgeSeconds } from '../src/common/app-root.js'; -import { TelemetryService } from '../src/telemetry/telemetry.service.js'; +import { TelemetryService, buildChatLoopWidget } from '../src/telemetry/telemetry.service.js'; test('readJsonl parses valid lines and skips malformed', () => { const d = mkdtempSync(join(tmpdir(), 'cp-')); @@ -53,6 +53,7 @@ test('commandCenter() returns evidence-backed widget contract', () => { const ids = Object.keys(command.widgets); assert.deepEqual(ids.sort(), [ 'certification', + 'chat_loop', 'finops', 'hitl', 'kill_switch', @@ -71,6 +72,53 @@ test('commandCenter() returns evidence-backed widget contract', () => { assert.ok(Array.isArray(command.ticker)); }); +test('buildChatLoopWidget summarizes chat replay fixture and evidence', () => { + const d = mkdtempSync(join(tmpdir(), 'cp-chat-')); + const auditPath = join(d, 'chat-turns.jsonl'); + writeFileSync(auditPath, '{"seq":1}\n{"seq":2}\n'); + const prevBudget = process.env.CASAN_CHAT_TOKEN_BUDGET; + process.env.CASAN_CHAT_TOKEN_BUDGET = '200'; + try { + const widget = buildChatLoopWidget( + [ + { seq: 1, timestamp: '2026-07-08T00:00:00Z', chat_id: 'chat1', turn_id: 't1', mode: 'READ_ONLY', decision: 'ANSWERED' }, + { + seq: 2, + timestamp: '2026-07-08T00:01:00Z', + chat_id: 'chat1', + turn_id: 't2', + mode: 'OPERATOR', + decision: 'ACTION_COMPLETED', + loop_run: { run_id: 'loop1', decision: 'CERTIFIED' }, + record_hash: 'abc123', + }, + ], + [ + { agent: 'chat.ask-casan', step: 'ask-casan-readonly', total_tokens: 40 }, + { agent: 'chat.operator', step: 'operator:generate_report', total_tokens: 60 }, + { agent: 'planner', step: 'unrelated', total_tokens: 900 }, + ], + auditPath, + { ok: true, decision: 'MATCH', records: 2, loop_replayed: 1, diffs: [], audit_path: auditPath }, + ) as any; + + assert.equal(widget.id, 'chat_loop'); + assert.equal(widget.status, 'ok'); + assert.equal(widget.metrics.turns, 2); + assert.equal(widget.metrics.answered, 1); + assert.equal(widget.metrics.action_completed, 1); + assert.equal(widget.metrics.loop_runs, 1); + assert.equal(widget.metrics.replay_loop_replayed, 1); + assert.equal(widget.metrics.budget_used_pct, 50); + assert.equal(widget.envelope.verified, true); + assert.equal(widget.evidence.latest_turn.loop_run.run_id, 'loop1'); + assert.equal(widget.evidence.replay.decision, 'MATCH'); + } finally { + if (prevBudget === undefined) delete process.env.CASAN_CHAT_TOKEN_BUDGET; + else process.env.CASAN_CHAT_TOKEN_BUDGET = prevBudget; + } +}); + test('freshness helpers behave (age is number|null, isStale is boolean)', () => { const age = metricsAgeSeconds(); assert.ok(age === null || typeof age === 'number'); diff --git a/packages/casan-control-panel/frontend/src/pages/CommandCenter.tsx b/packages/casan-control-panel/frontend/src/pages/CommandCenter.tsx index efbf77e..fafb9f5 100644 --- a/packages/casan-control-panel/frontend/src/pages/CommandCenter.tsx +++ b/packages/casan-control-panel/frontend/src/pages/CommandCenter.tsx @@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'; import { api, CommandWidget } from '../lib/api'; import { Card, StatTile, StatusBadge } from '../components/ui/Card'; -const ORDER = ['maturity', 'hitl', 'kill_switch', 'traceability', 'security', 'finops', 'certification', 'self_improve']; +const ORDER = ['maturity', 'hitl', 'kill_switch', 'traceability', 'security', 'finops', 'certification', 'chat_loop', 'self_improve']; const COPY = { en: { diff --git a/packages/casan-control-panel/scripts/local-prod-smoke.sh b/packages/casan-control-panel/scripts/local-prod-smoke.sh index bc33a35..2512a30 100755 --- a/packages/casan-control-panel/scripts/local-prod-smoke.sh +++ b/packages/casan-control-panel/scripts/local-prod-smoke.sh @@ -88,7 +88,8 @@ assert data.get('success') is True command = json.load(open('/tmp/casan-cp-local-command.json')) widgets = command.get('data', {}).get('widgets', {}) assert command.get('success') is True -assert len(widgets) == 8, widgets.keys() +assert len(widgets) == 9, widgets.keys() +assert 'chat_loop' in widgets, widgets.keys() for widget in widgets.values(): envelope = widget.get('envelope', {}) assert {'source', 'artifact_path', 'commit', 'run_at', 'verified', 'status'} <= set(envelope), envelope diff --git a/packages/casan-control-panel/scripts/managed-prod-smoke.sh b/packages/casan-control-panel/scripts/managed-prod-smoke.sh index 97edf6c..cc2050b 100755 --- a/packages/casan-control-panel/scripts/managed-prod-smoke.sh +++ b/packages/casan-control-panel/scripts/managed-prod-smoke.sh @@ -85,7 +85,8 @@ assert actor.get("role") and actor.get("role") != "unknown", actor command = json.load(open(sys.argv[2])) assert command.get("success") is True, command widgets = command.get("data", {}).get("widgets", {}) -assert len(widgets) == 8, widgets.keys() +assert len(widgets) == 9, widgets.keys() +assert "chat_loop" in widgets, widgets.keys() for widget in widgets.values(): envelope = widget.get("envelope", {}) required = {"source", "artifact_path", "commit", "run_at", "verified", "status"} diff --git a/packages/casan-platform/README.md b/packages/casan-platform/README.md index 3451c71..3939036 100644 --- a/packages/casan-platform/README.md +++ b/packages/casan-platform/README.md @@ -19,7 +19,7 @@ Optional layer for teams that want UI / dashboard / visibility. Packages: `casan | RBAC + approval inbox | ✅ local-prod done | Settings + kill-switch API RBAC enforcement, approval inbox/delegation/oversight, SoD, governed setting proposal apply, and local OIDC claim→role mapping smoke are done. Enterprise IdP rollout remains production follow-up. | | Evidence Pack Viewer | 📋 planned | reads `docs/output/casan/evidence-packs/` | | Attack Battery Viewer | 📋 planned | reads red-team corpus + H4 recall results | -| Read-only Ask CASAN + Operator + agent selection + operator loop-hold + replay | 🟡 MVP-0/1 done+test; MVP-2 Track 4 + OPERATOR Track 5/6 + Track 8.1/8.2 done | Plan-18: `POST /api/v1/chat/ask`, `GET /api/v1/chat/actions`, `GET /api/v1/chat/agents`, `GET /api/v1/chat/replay` + `/chat`, backed by harness `chat-turn.py`, `chat-agent-resolver.py`, `chat-replay.py`, and Plan-17 `loop-run.sh` | +| Read-only Ask CASAN + Operator + agent selection + operator loop-hold + replay/widget | 🟡 MVP-0/1 done+test; MVP-2 Track 4 + OPERATOR Track 5/6 + Track 8.1/8.2/8.3 done | Plan-18: `POST /api/v1/chat/ask`, `GET /api/v1/chat/actions`, `GET /api/v1/chat/agents`, `GET /api/v1/chat/replay`, Command Center `chat_loop` widget + `/chat` + `/command`, backed by harness `chat-turn.py`, `chat-agent-resolver.py`, `chat-replay.py`, and Plan-17 `loop-run.sh` | | Gitea webhook integration | 📋 planned | trigger gate / publish evidence on push | ## Build (preview) @@ -29,6 +29,6 @@ scripts/package-release.sh platform # → dist/casan-platform-preview-vX.Y.Z The bundle includes a `PREVIEW-INCOMPLETE.txt` marker. Do not treat it as a finished product. ## To implement later -Start from Plan-15 RAI view or Plan-18 MVP-2 Track 8.3/8.4 (replay widget / approvals escalation) or CODEGEN-as-loop. Keep new numbers +Start from Plan-15 RAI view, Plan-18 MVP-2 Track 8.4 approvals escalation, or CODEGEN-as-loop. Keep new numbers evidence-backed with provenance; any write/governed action must continue to route through harness RBAC, approval, and audit primitives.