From bfebfa9d390a694814310e018f1301a7eb920734 Mon Sep 17 00:00:00 2001 From: thanhnv Date: Fri, 24 Jul 2026 12:30:37 +0700 Subject: [PATCH] feat: add production init wizard UX --- README.md | 622 ++++++++++++------ bin/casan | 2 +- docs/casan/CASAN_AGENTIC_CLIENTS_WINDOWS.md | 16 +- docs/casan/CASAN_INSTALL_HYBRID.md | 29 +- packages/casan-devkit/casan-init.py | 146 +++- .../tests/adoption-install-tests.sh | 4 +- .../tests/hybrid-install-tests.sh | 33 +- 7 files changed, 597 insertions(+), 255 deletions(-) diff --git a/README.md b/README.md index 0db2037..3b10ef3 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,45 @@ # CASAN -CASAN là governance harness cho agentic coding. CASAN được cài một lần trên máy -developer, sau đó được liên kết vào từng repository bằng project hooks. CASAN -không thay thế IDE, coding agent hoặc workflow phát triển của project. +CASAN là governance harness cho agentic coding. CASAN bổ sung policy gates, +audit trail, evidence và kiểm tra integrity vào lifecycle của coding agent, +nhưng không thay thế IDE, model, agent, skill, slash command hay workflow hiện +có của repository. -Project có thể tiếp tục dùng nguyên trạng slash commands, agents, skills, review -loops và cấu trúc source hiện hữu. CASAN không áp đặt một pipeline hoặc số bước -cố định. +Mô hình production mặc định: + +- Cài **DevKit một lần trên máy** để có launcher và lệnh quản trị. +- Chạy `casan init` trong từng repository. +- Project mới dùng **Core level + Managed runtime + Enforce mode** nếu người + dùng không chọn khác. +- Project offline, air-gapped hoặc cần tự chứa runtime có thể chọn + **Vendored runtime**. + +`casan init` chỉ thêm lớp tích hợp cần thiết. Runtime phát hành không chứa test +suite, test scripts, internal CI runners, `level5`, dashboard lab, source docs +hay release tooling. ## Trạng thái sản phẩm | Thành phần | Trạng thái | Phạm vi | |---|---|---| | Core — Level 1 | Implemented | H1–H7 harness, hooks, policy gates, audit, evidence và CLI | -| DevKit — Level 2 | Implemented | Core + `casan init`, domain-pack và CI template | -| Platform — Level 3 | Preview | Control Panel được deploy riêng, không được cài vào project bằng `casan init` | -| Enterprise — Level 4 | Chưa phát hành | Installer chủ động từ chối | +| DevKit — Level 2 | Implemented | Core + adoption tooling, domain-pack và CI template | +| Platform — Level 3 | Preview | Control Panel deploy riêng; `casan init` chỉ áp dụng nền Level 2 | +| Enterprise — Level 4 | Chưa phát hành | CLI chủ động từ chối, không giả lập tính năng | -Đối với repository đã có sẵn vỏ dự án, nên dùng **Level 1 cho project**. Chỉ -chọn Level 2 khi project thực sự cần domain-pack và CI template của CASAN. +## Bốn quyết định cần phân biệt + +CASAN tách riêng bốn khái niệm để cấu hình rõ ràng: + +| Quyết định | Lựa chọn | Mặc định production | +|---|---|---| +| Gói cài trên máy | `core`, `devkit`, `platform` | `devkit`, vì `casan init` thuộc DevKit | +| Capability của project | `--level core`, `devkit`, `platform` | `core` | +| Vị trí Core runtime | `--runtime managed`, `vendored` | `managed` | +| Cách thực thi policy | `--mode enforce`, `observe` | `enforce` | + +`--level core` không có nghĩa Core phải nằm trong repository. Level mô tả +capability; runtime mô tả vị trí. Đây là hai quyết định độc lập. ## Quick start @@ -28,15 +49,15 @@ chọn Level 2 khi project thực sự cần domain-pack và CI template của C - Windows: PowerShell 5.1+, Python 3 và Git for Windows/Git Bash. - Client tương ứng nếu cần: Claude Code, Codex hoặc VS Code. -### 1. Cài CASAN một lần trên máy +### 1. Cài DevKit một lần trên máy -Từ checkout hoặc release bundle của CASAN: +Từ checkout hoặc release bundle đã được duyệt: ```bash # macOS/Linux sh install.sh --level devkit -# Nếu launcher chưa nằm trên PATH +# Chỉ cần nếu terminal hiện tại chưa nhận launcher export PATH="${CASAN_HOME:-$HOME/.casan}/bin:$PATH" casan version @@ -50,74 +71,195 @@ pwsh .\install.ps1 casan version ``` -Gói global `devkit` được dùng vì nó chứa lệnh adoption `casan init`. Harness -được cài mặc định tại: +Vị trí mặc định: - macOS/Linux: `~/.casan` - Windows: `%LOCALAPPDATA%\casan` -Bản cài là runtime allowlist tối giản: không mang theo test suites, internal CI -runners, thư mục legacy `level5`, Platform dashboard/local lab, source docs hay -release tooling. Source repository vẫn giữ tests để kiểm chứng chính CASAN. +Có thể đặt `CASAN_HOME` trước khi cài nếu tổ chức dùng một vị trí quản lý khác. +`casan init` không hỏi một đường dẫn runtime tùy ý: Managed luôn dùng +installation đã resolve từ `CASAN_HOME`; Vendored luôn dùng +`.casan/runtime/casan-core` trong project. -### 2. Adopt vào repository hiện hữu - -CASAN tách rõ hai quyết định: - -| Phạm vi | Ý nghĩa | -|---|---| -| `--level core` (mặc định) | Capability áp dụng cho project: governance Core, không thêm domain-pack/CI | -| `--runtime managed` (mặc định project mới) | Dùng Core global đã pin version/hash; repo nhẹ, nâng cấp tập trung | -| `--runtime vendored` | Copy Core production-only vào `.casan/runtime/casan-core`; phù hợp offline/air-gapped/self-contained | +### 2. Khởi tạo CASAN trong repository ```bash cd - -# Production mặc định: managed Core -casan init --client claude,codex --mode enforce - -casan doctor -casan verify-harness +casan init ``` -`casan init` có menu chọn client khi chạy tương tác. Trong automation nên chỉ -định rõ `--client`. Project hiện hữu mặc định dùng Level 1 (`core`); chỉ truyền -`--level devkit` khi muốn CASAN bổ sung CI template và domain-pack: +Với project mới và terminal tương tác, wizard hỏi hai lựa chọn: + +```text +Core runtime placement + + 1) Managed (Recommended) + Use the shared CASAN installation, pinned by version and hash. + Best for developer workstations and managed CI. + + 2) Vendored + Copy production-only Core into .casan/runtime/casan-core. + Best for offline, air-gapped, or self-contained repositories. + +Select runtime [1]: + +Client integrations + + 1) Claude Code + 2) Codex + 3) GitHub Copilot in VS Code via explicit @casan route + +Select clients (comma-separated) [1,2]: +``` + +Nhấn Enter để dùng Managed và Claude Code + Codex. Nhập sai sẽ được hướng dẫn +chọn lại. Sau khi hoàn tất, output terminal là bản tóm tắt dễ đọc gồm project, +level, runtime, mode, client, file thay đổi và next steps. + +Project đã init không bị hỏi lại runtime: CASAN giữ nguyên mode hiện tại. Muốn +đổi, truyền rõ `--runtime managed` hoặc `--runtime vendored`. + +### 3. Kiểm tra readiness ```bash -casan init --level core --client claude -casan init --level core --client codex -casan init --level core --client claude,codex -casan init --level core --client vscode-copilot --vscode-install yes -casan init --level core --client all -casan init --level devkit --client claude,codex - -# Project phải tự chứa Core (offline/air-gapped) -casan init --runtime vendored --client claude,codex +casan doctor +casan verify-harness +casan level show ``` -Output `init` và `casan level show` luôn hiển thị runtime mode cùng đường dẫn -thực tế. Chạy lại `init` giữ mode hiện tại; chỉ đổi khi truyền rõ -`--runtime managed` hoặc `--runtime vendored`. +Với Codex, mở `/hooks`, review và trust đúng project hook sau lần init hoặc khi +bootstrap hash thay đổi. -Với Codex, sau init phải mở `/hooks`, kiểm tra và trust đúng project hook hash. +### 4. Dùng workflow hiện có -### 3. Dùng project bình thường +Tiếp tục dùng chat, agents, skills và slash commands của project như bình +thường. Không cần gọi một “CASAN agent” hoặc chạy một pipeline CASAN riêng. -Không cần gọi CASAN agent hoặc CASAN pipeline. Tiếp tục dùng workflow hiện hữu, -ví dụ `/bd:boss`, `/bd:generation`, `/bd:review`, hoặc chat bình thường không -chỉ định agent. - -CASAN tự tham gia vào lifecycle của client đã enable: +CASAN tham gia tại lifecycle của client đã enable: 1. `UserPromptSubmit`: admission và quét prompt. -2. `PreToolUse`: kiểm tra tool input và chặn side effect không hợp lệ. +2. `PreToolUse`: kiểm tra tool input, chặn side effect không hợp lệ. 3. `PostToolUse`: ghi evidence của tool result. 4. `Stop`: finalize trace, telemetry và trạng thái certification. -CASAN bridge không gọi model lần thứ hai. Claude Code/Codex vẫn là model +CASAN bridge không gọi model lần thứ hai. Claude Code hoặc Codex vẫn là model executor duy nhất. +## Dùng trong script và CI + +Automation phải khai báo quyết định rõ ràng: + +```bash +casan init \ + --non-interactive \ + --level core \ + --runtime managed \ + --mode enforce \ + --client claude,codex +``` + +`--non-interactive` và `--json` không bao giờ chờ input. Nếu không truyền +`--runtime`, project mới mặc định Managed; nếu không truyền `--client`, mặc +định Claude Code + Codex để tương thích các bản trước. + +Output mặc định dành cho người đọc. Chỉ dùng JSON khi một chương trình cần xử +lý dữ liệu: + +```bash +casan init --non-interactive --client codex --json +casan doctor --json +casan verify-harness --json +casan level show --json +``` + +## Chọn runtime + +### Managed — khuyến nghị cho đa số tổ chức + +Managed dùng Core trong global installation và pin version/hash theo từng +project. + +Ưu điểm: + +- Repository nhẹ, không nhân bản runtime. +- Nâng cấp và rollback tập trung. +- Phù hợp workstation và CI runner được quản lý. +- Nhiều project có thể dùng chung một CASAN installation. + +Yêu cầu: + +- Máy developer/runner phải cài release CASAN mà project đã pin. +- Không thay runtime global tùy tiện; luôn chạy `casan verify-harness`. + +```bash +casan init --runtime managed +``` + +### Vendored — cho môi trường tự chứa + +Vendored copy Core production-only vào +`.casan/runtime/casan-core`. Bootstrap và launcher trong project resolve runtime +này trước, không âm thầm fallback sang global nếu runtime bị thiếu. + +Ưu điểm: + +- Có thể hoạt động offline hoặc air-gapped. +- Runtime đi cùng đúng project. +- Phù hợp repository được đóng gói thành một deliverable độc lập. + +Đánh đổi: + +- Repository/artifact lớn hơn. +- Mỗi project phải chủ động nhận bản vá và upgrade. +- Tổ chức phải quản lý quyền phân phối runtime theo license. + +```bash +casan init --runtime vendored +``` + +### Bảng quyết định + +| Tình huống | Runtime nên dùng | +|---|---| +| Máy developer và CI do công ty quản lý | Managed | +| Nhiều repository dùng chung CASAN | Managed | +| Cần rollout/rollback tập trung | Managed | +| Offline hoặc air-gapped | Vendored | +| Deliverable phải tự chứa toàn bộ Core | Vendored | +| Không thể đảm bảo CASAN đã cài trên runner | Vendored | + +## Chọn project level + +| Level | Dùng khi | Nội dung thêm vào project | +|---|---|---| +| `core` | Mặc định cho repository đã có cấu trúc | Governance Core, bootstrap, hooks và config | +| `devkit` | Cần CASAN domain-pack và CI template | Core + `.gitea/workflows/casan-ci.yml` + domain scaffold | +| `platform` | Đánh giá Platform preview | Áp dụng nền Level 2; Control Panel vẫn deploy riêng | +| `enterprise` | Chưa khả dụng | CLI từ chối | + +```bash +casan init --level core +casan init --level devkit +``` + +Level 1 Core không đưa test folders, test scripts, `level5` hoặc source-only +components vào project. Chỉ chọn DevKit khi thực sự cần scaffold bổ sung. + +## Enforcement mode + +| Mode | Dùng cho | Hành vi | +|---|---|---| +| `enforce` | Production | Side effect fail-closed; turn đủ evidence có thể certified | +| `observe` | Pilot và thu telemetry | Ghi nhận nhưng không chặn như production; luôn `observed_only` | + +```bash +casan init --mode enforce +casan init --mode observe +``` + +Không gọi một turn là CASAN-certified nếu không có trace tương ứng hoặc trace bị +đánh dấu `observed_only`/`non_certified`. + ## Client support | Client | Chat bình thường tự qua CASAN | Bước bắt buộc | @@ -126,224 +268,300 @@ executor duy nhất. | Codex CLI/extension | Có | Mở `/hooks`, review và trust hook hash | | GitHub Copilot Chat | Không | Cài CASAN VSIX và gửi `@casan ` | +Các cách chọn client: + +```bash +casan init --client claude +casan init --client codex +casan init --client claude,codex +casan init --client vscode-copilot --vscode-install yes +casan init --client all +casan init --client none +``` + GitHub Copilot không cung cấp public API để extension intercept toàn bộ built-in -chat. Chỉ route explicit `@casan` mới là CASAN-owned. Một backend tự gọi LLM API +chat. Chỉ route explicit `@casan` mới là CASAN-owned. Backend tự gọi LLM API cũng không đi qua IDE hooks và cần adapter riêng. ## Kiến trúc runtime ```mermaid flowchart TB - U["Developer"] --> C1["Claude Code"] - U --> C2["Codex"] - U --> C3["VS Code: @casan"] - - C1 --> E1["Project hook events"] - C2 --> E1 - C3 --> E2["CASAN-owned VSIX route"] - - E1 --> B[".casan/casan-hook.py"] - E2 --> B - B --> V{"Global harness
matches version.lock?"} - V -- "No" --> D["Deny or degrade
according to mode"] + U["Developer"] --> C["Claude Code / Codex / @casan"] + C --> B["Project bootstrap
.casan/casan-hook.py"] + B --> R{"Runtime mode"} + R -- "Managed" --> GM["CASAN_HOME/current"] + R -- "Vendored" --> GV[".casan/runtime/casan-core"] + GM --> V{"Version + hash
match version.lock?"} + GV --> V + V -- "No" --> D["Deny or degrade
according to enforcement mode"] V -- "Yes" --> A["Client adapter"] A --> G["Agentic bridge"] - - subgraph TURN["Per-turn lifecycle"] - direction LR - L1["Admission
H1 + H4"] --> L2["Pre-tool gate
H2 + H4"] - L2 --> L3["Post-tool evidence
H5"] - L3 --> L4["Finalize
H3 + H5 + H6 + H7"] - end - - G --> L1 - L4 --> S["Project runtime state
.specify/logs + state"] - L4 --> R["Native client result"] + G --> L1["Admission"] + L1 --> L2["Pre-tool gate"] + L2 --> L3["Post-tool evidence"] + L3 --> L4["Finalize trace"] + L4 --> S[".specify/logs + state"] + L4 --> O["Native client result"] ``` -## Cấu trúc cài đặt thực tế +Runtime resolution fail-closed: một project pin Vendored nhưng thiếu +`.casan/runtime/casan-core` sẽ báo lỗi và yêu cầu restore bằng release DevKit đã +duyệt, không tự chuyển sang Managed. -CASAN mặc định dùng managed runtime: policy code nằm ở global installation, -project giữ bootstrap, pin và state riêng. Với `--runtime vendored`, cùng Core -production-only được đặt tại `.casan/runtime/casan-core`. +## Cấu trúc cài đặt -```mermaid -flowchart TB - subgraph M["Developer machine"] - H["CASAN_HOME"] - CUR["current
symlink hoặc junction"] - VER["versions/<version>"] - CLI["bin/casan"] - HAR["packages/casan-harness"] - DEV["packages/casan-devkit"] +### Global installation - H --> CUR --> VER - H --> CLI - VER --> HAR - VER --> DEV - end +```text +CASAN_HOME/ +├── bin/casan +├── current -> versions/ +└── versions// + ├── VERSION + ├── bin/casan + ├── packages/casan-harness/ + └── packages/casan-devkit/ +``` - INIT["casan init"] --> CFG - CLI --> INIT - DEV --> INIT +Release runtime là allowlist production-only. Tests và release tooling vẫn nằm +trong source repository để kiểm chứng CASAN, nhưng không được copy vào +installation hoặc project runtime. - subgraph P["Existing project"] - CFG[".casan/
config.json
version.lock
agentic.env
init-manifest.json"] - BOOT[".casan/casan-hook.py"] - STATE[".specify/
logs/
state/
.gitignore"] - CLIENTS["Client config khi được chọn
.claude/settings.json
.codex/hooks.json
.vscode/extensions.json"] - L2["Level 2 only
.gitea/workflows/casan-ci.yml
apps/<project-id>/domain/"] - OWNED["Project-owned
source, agents, skills,
commands, hooks và CI khác"] - end +### Project sau `casan init` - INIT --> BOOT - INIT --> STATE - INIT --> CLIENTS - INIT -. "chỉ khi --level devkit" .-> L2 - INIT -. "không thay đổi" .-> OWNED - CFG --> BOOT - HAR -. "runtime policy" .-> BOOT +```text +/ +├── .casan/ +│ ├── config.json +│ ├── version.lock +│ ├── casan-hook.py +│ ├── agentic.env +│ ├── init-manifest.json +│ └── runtime/casan-core/ # chỉ khi --runtime vendored +├── .specify/ +│ ├── .gitignore +│ ├── logs/ # runtime, không commit +│ └── state/ # runtime, không commit +├── .claude/settings.json # khi enable Claude +├── .codex/hooks.json # khi enable Codex +├── .vscode/extensions.json # merge theo client +└── .gitea/workflows/casan-ci.yml # chỉ Level 2, nếu chưa có ``` ### File nào được thay đổi | Path | Hành vi | |---|---| -| `.casan/config.json` | Lưu project id, mode và danh sách client | -| `.casan/version.lock` | Pin version và SHA-256 của global harness | -| `.casan/casan-hook.py` | Bootstrap stdlib, verify pin rồi dispatch adapter | +| `.casan/config.json` | Project id, level, runtime mode, enforcement mode và clients | +| `.casan/version.lock` | Pin version và SHA-256 của Core runtime đã resolve | +| `.casan/casan-hook.py` | Stdlib bootstrap, verify pin rồi dispatch adapter | | `.casan/agentic.env` | Compatibility/reference flags; runtime đọc `config.json` | -| `.casan/init-manifest.json` | Ghi file đã tạo và backup | -| `.specify/logs`, `.specify/state` | Runtime trace, audit và state; không commit | +| `.casan/init-manifest.json` | Danh sách file CASAN quản lý, checksum và backup | +| `.casan/runtime/casan-core/` | Core production-only; chỉ có ở Vendored | +| `.specify/logs`, `.specify/state` | Trace, audit và state runtime; không commit | | `.claude/settings.json` | Merge CASAN handlers khi enable Claude | | `.codex/hooks.json` | Merge CASAN handlers khi enable Codex | | `.vscode/extensions.json` | Merge extension recommendations theo client | -| `.gitea/workflows/casan-ci.yml` | Chỉ Level 2, chỉ tạo khi chưa có | -| `apps//domain/` | Chỉ Level 2, chỉ bổ sung file còn thiếu | +| `.gitea/workflows/casan-ci.yml` | Chỉ Level 2; chỉ tạo khi chưa có | +| `apps//domain/` | Chỉ Level 2; chỉ bổ sung file còn thiếu | -Trước lần thay đổi đầu tiên, init tạo backup `.casan-bak` cho file hiện -hữu. `init-manifest.json` ghi lại các file và backup liên quan. +Trước lần thay đổi đầu tiên, init tạo `.casan-bak` cho file hiện hữu. +`init-manifest.json` giúp re-init và uninstall chỉ quản lý đúng tài sản CASAN. -### Nội dung luôn được giữ nguyên +### Nội dung luôn được bảo toàn - Source code và cấu trúc ứng dụng. - `.claude/agents`, `.claude/skills`, `.claude/commands`. - Agents, skills, prompts và instructions trong `.github/`. -- Hook và JSON key không thuộc CASAN. +- Hook, JSON key và VS Code recommendation không thuộc CASAN. - CI/workflow hiện hữu. -- Vendored `packages/casan-harness` của project cũ; chỉ xóa sau khi đã migration - toàn bộ CI và scripts sang global harness. +- Scaffold file đã được người dùng chỉnh sửa. +- Backup `.casan-bak`. -Nếu target chính là CASAN source hub, init từ chối để tránh self-adoption. Không -dùng `--force` trừ khi chủ động muốn kiểm thử trường hợp này. +CASAN source hub tự từ chối self-adoption. Không dùng `--force` trừ khi chủ động +kiểm thử trường hợp này. -## Chọn mode +## Nên commit gì -| Mode | Dùng cho | Certification | -|---|---|---| -| `enforce` | Mặc định production | Side effect fail-closed; turn đủ evidence có thể certified | -| `observe` | Pilot và thu telemetry | Không chặn như production; luôn `observed_only` | +Thông thường nên commit: + +- `.casan/config.json` +- `.casan/version.lock` +- `.casan/casan-hook.py` +- `.casan/agentic.env` +- `.casan/init-manifest.json` +- các client config đã merge +- Level 2 workflow/domain files sau khi review + +Không commit: + +- `.specify/logs/` +- `.specify/state/` +- file `.casan-bak` + +Với Managed, không có runtime để commit. Với Vendored, tổ chức phải đưa +`.casan/runtime/casan-core` vào cùng deliverable bằng Git hoặc artifact channel +đã duyệt; nếu bỏ runtime này, project sẽ fail-closed. Quyết định phân phối phải +tuân theo license và policy nội bộ. + +## Cấu hình lại + +Chạy lại init với **toàn bộ danh sách client mong muốn**: ```bash -casan init --level core --client claude,codex --mode enforce +# Chỉ giữ Claude +casan init --client claude + +# Tắt CASAN IDE integrations nhưng giữ config và evidence +casan init --client none + +# Chuyển runtime có chủ đích +casan init --runtime vendored +casan init --runtime managed ``` -Không gọi một turn là CASAN-certified nếu không có trace tương ứng hoặc trace bị -đánh dấu `observed_only`/`non_certified`. +Client bị bỏ khỏi danh sách sẽ được gỡ CASAN handler; hook và config không thuộc +CASAN vẫn được giữ. `--client none` không tự gỡ VS Code extension dùng chung. -## Kiểm tra, cấu hình lại và nâng cấp +## Nâng cấp và rollback -Kiểm tra project: +### Managed -```bash -casan doctor -casan verify-harness -casan level show -``` - -- Output mặc định được tối ưu để đọc trực tiếp trong terminal. Thêm `--json` - sau command khi cần payload đầy đủ cho CI hoặc script, ví dụ - `casan doctor --json`. -- `doctor`: kiểm tra config, bootstrap, hook schema, adapter smoke test, VSIX và - cảnh báo trust. -- `verify-harness`: tính lại live hash và so với project pin; drift trả exit - code `3`. -- `level show`: hiển thị package level đã cài và target level của project. - -Đổi danh sách client bằng cách chạy lại init với **toàn bộ danh sách mong muốn**. -CASAN handler của client bị bỏ khỏi danh sách sẽ được gỡ, còn hook khác được giữ: - -```bash -casan init --level core --client claude - -# Tắt toàn bộ IDE integration của CASAN nhưng giữ config/state -casan init --level core --client none -``` - -`--client none` không uninstall VSIX đã cài trên máy; nếu không còn dùng route -`@casan`, gỡ extension `fpt-casan.casan-governed-chat` trong VS Code. - -Gỡ CASAN khỏi project: - -```bash -# Gỡ project hooks và config CASAN; giữ hook người dùng và runtime evidence -casan uninstall - -# Đồng thời xóa .specify/logs và .specify/state -casan uninstall --purge - -# Chỉ dùng khi extension dùng chung không còn cần trên máy -casan uninstall --remove-vscode-extension -``` - -`uninstall` xóa workflow CASAN trong `.gitea`, xóa các scaffold file CASAN còn -nguyên checksum và tự dọn thư mục cha khi đã rỗng. Workflow/file của project, -scaffold file đã chỉnh sửa và `.casan-bak` được giữ lại để tránh mất dữ liệu. -Vendored Core trong `.casan/runtime/casan-core` cũng được xóa. Lệnh không mặc -định gỡ VS Code extension dùng chung cho các project khác. - -Khi nâng cấp CASAN: - -1. Chạy lại installer từ release đã duyệt. -2. Chạy lại `casan init` trong từng project để cập nhật bootstrap và pin. +1. Cài release CASAN đã duyệt trên workstation/runner. +2. Chạy lại `casan init --runtime managed` trong từng project để cập nhật + bootstrap và pin. 3. Chạy `casan doctor` và `casan verify-harness`. 4. Với Codex, review/trust lại hook nếu hash thay đổi. -Trong production, không bỏ qua `HARNESS_INTEGRITY_DRIFT`. +### Vendored -## CI +1. Cài hoặc giải nén DevKit release đã duyệt trên máy thực hiện upgrade. +2. Chạy `casan init --runtime vendored`; CASAN refresh Core production-only + trong project. +3. Review thay đổi runtime, chạy `doctor` và `verify-harness`. +4. Phát hành lại deliverable Vendored. -Runner phải cài cùng release CASAN mà project đã pin. Gate tối thiểu: +Rollback dùng đúng release đã duyệt trước đó, chạy lại init và verify. Không bỏ +qua `HARNESS_INTEGRITY_DRIFT` trong production. + +## Uninstall + +```bash +# Gỡ hooks, config, CASAN workflow/scaffold còn nguyên checksum và Vendored Core +casan uninstall + +# Đồng thời xóa runtime evidence +casan uninstall --purge + +# Chỉ khi VSIX dùng chung không còn cần trên máy +casan uninstall --remove-vscode-extension +``` + +`casan uninstall`: + +- gỡ CASAN handlers nhưng giữ handlers của project; +- xóa CASAN-owned `.gitea` workflow và dọn thư mục cha nếu đã rỗng; +- xóa `.casan/runtime/casan-core` nếu dùng Vendored; +- chỉ xóa scaffold file còn đúng checksum; +- giữ file người dùng đã sửa và `.casan-bak`; +- giữ `.specify/logs` và `.specify/state` trừ khi có `--purge`; +- không mặc định gỡ VSIX vì extension có thể được project khác dùng chung. + +Sau uninstall, output liệt kê rõ mục đã xóa và mục được giữ lại. + +## CI production + +Gate tối thiểu: ```bash casan verify-harness casan gate ``` +Managed runner phải cài đúng approved release trước khi chạy gate. Vendored +runner phải nhận `.casan/runtime/casan-core` cùng checkout/artifact; global +launcher vẫn có thể được dùng, nhưng runtime policy được resolve từ project. + Level 2 tạo `.gitea/workflows/casan-ci.yml` như một template nếu file chưa tồn -tại. Template phải được review theo runner và mô hình cài đặt của tổ chức trước -khi enable; CASAN không ghi đè workflow hiện hữu. +tại. Luôn review template theo runner, secret model và runtime mode của tổ chức +trước khi enable. CASAN không ghi đè workflow hiện hữu. Đảm bảo `.specify/logs/` và `.specify/state/` không được commit. Init chỉ tạo `.specify/.gitignore` khi file đó chưa tồn tại. +## CLI tham khảo + +| Lệnh | Mục đích | +|---|---| +| `casan init` | Adopt hoặc cấu hình lại CASAN trong project | +| `casan uninstall` | Gỡ CASAN-owned project integration an toàn | +| `casan doctor` | Kiểm tra config, bootstrap, pin, adapters và client readiness | +| `casan verify-harness` | So live Core hash với project pin | +| `casan level show` | Hiển thị installed level, project level và runtime | +| `casan gate` | Chạy production checks từ project manifest | +| `casan verify` | Verify audit chain, tool audit và policy bundle | +| `casan prompt verify` | Verify prompt-enforcement contract | +| `casan prompt trace ` | Verify H1–H7 certification của một turn | +| `casan version` | Hiển thị phiên bản | +| `casan help` | Hiển thị toàn bộ command | + +Các trạng thái quan trọng: + +- `doctor` trả non-zero khi project chưa sẵn sàng. +- `verify-harness` trả exit code `3` khi phát hiện drift. +- Cú pháp/giá trị CLI không hợp lệ trả exit code `64`. +- JSON là opt-in qua `--json`; lỗi vận hành vẫn được viết ngắn gọn ra stderr. + +## Troubleshooting + +### `casan` không có trên PATH + +Mở terminal mới hoặc thêm `${CASAN_HOME:-$HOME/.casan}/bin` vào `PATH`. + +### Managed báo integrity drift + +Cài đúng release đã pin, chạy lại `casan init --runtime managed`, sau đó +`casan verify-harness`. Không sửa `version.lock` thủ công để né kiểm tra. + +### Vendored báo runtime missing + +Từ một approved DevKit installation, chạy: + +```bash +casan init --runtime vendored +casan verify-harness +``` + +### Codex chưa chạy hook + +Mở `/hooks`, review project hook và trust hash hiện tại. + +### GitHub Copilot chat không tự qua CASAN + +Dùng explicit route `@casan ` và kiểm tra +`fpt-casan.casan-governed-chat` đã được cài. + ## Cấu trúc source repository CASAN | Path | Trách nhiệm | |---|---| | `bin/casan` | CLI entrypoint | | `install.sh`, `install.ps1` | Global installers | -| `packages/casan-harness/` | Runtime controls, adapters, policies, evidence và tests | -| `packages/casan-devkit/` | Hybrid adoption, project bootstrap và templates | +| `packages/casan-harness/` | Runtime controls, adapters, policies, evidence và source tests | +| `packages/casan-devkit/` | Project adoption tooling và templates | | `packages/casan-control-panel/` | Platform UI/API preview, deploy riêng | | `packaging/levels.json` | Nguồn sự thật cho package level và maturity | | `infra/` | Local/production deployment references | | `docs/` | Security, operations, packaging và design records | | `apps/` | Demo/validation applications; không phải runtime dependency của `casan init` | +Source tree có tests để phát triển sản phẩm. Release packaging dùng allowlist để +chỉ đưa production runtime cần thiết vào global install hoặc Vendored Core. + ## Tài liệu chi tiết -- [Hybrid installation và migration](docs/casan/CASAN_INSTALL_HYBRID.md) +- [Production installation và migration](docs/casan/CASAN_INSTALL_HYBRID.md) - [Agentic client security boundary](docs/casan/CASAN_AGENTIC_CLIENT_SECURITY.md) - [Windows client setup](docs/casan/CASAN_AGENTIC_CLIENTS_WINDOWS.md) - [Packaging levels](docs/packaging/CASAN_PACKAGING_PLAN.md) diff --git a/bin/casan b/bin/casan index 5913ef5..f819605 100755 --- a/bin/casan +++ b/bin/casan @@ -72,7 +72,7 @@ casan — CASAN governance harness CLI ($(version)) Usage: casan [args] Commands: - init [--runtime managed|vendored] Adopt CASAN Core into this project + init [--runtime managed|vendored] Adopt/reconfigure CASAN (interactive wizard by default) uninstall [--purge] Remove CASAN from this project (preserves user config) doctor [--client ...] Verify configured hooks, pin, adapters, and VS Code route level Show / change the project's packaging level diff --git a/docs/casan/CASAN_AGENTIC_CLIENTS_WINDOWS.md b/docs/casan/CASAN_AGENTIC_CLIENTS_WINDOWS.md index 6900dd8..b9482d5 100644 --- a/docs/casan/CASAN_AGENTIC_CLIENTS_WINDOWS.md +++ b/docs/casan/CASAN_AGENTIC_CLIENTS_WINDOWS.md @@ -60,17 +60,19 @@ pwsh .\install.ps1 cd C:\work\my-project -# Menu chọn Claude / Codex / VS Code Copilot +# Project mới: menu chọn Managed/Vendored, sau đó chọn client casan init -# Hoặc non-interactive, chọn chính xác client -casan init --client claude,codex --mode enforce -casan init --client vscode-copilot --mode enforce --vscode-install yes +# Hoặc non-interactive, chọn chính xác runtime và client +casan init --non-interactive --runtime managed --client claude,codex --mode enforce +casan init --non-interactive --runtime vendored --client vscode-copilot --mode enforce --vscode-install yes ``` `init` merge CASAN handlers vào config hiện hữu, ghi bootstrap -`.casan\casan-hook.py`, pin harness global, và tạo VSIX `@casan` khi Copilot -được chọn. Bootstrap tự load `config.json`; không cần source env thủ công. +`.casan\casan-hook.py`, pin Core runtime đã resolve, và tạo VSIX `@casan` khi +Copilot được chọn. Managed dùng global installation; Vendored copy Core +production-only vào `.casan\runtime\casan-core`. Bootstrap tự load +`config.json`; không cần source env thủ công. > Đường dẫn có dấu cách được xử lý đúng (ví dụ `C:\Users\Nguyen Van A\project`). @@ -117,5 +119,5 @@ cho automation tương thích cũ. | Codex không chạy hook | Chưa qua trust review | Mở repo trong Codex, chấp nhận trust; chạy lại doctor | | Turn hiện `observed_only` | Đang ở observe mode hoặc client ngoài allowlist | Chạy lại `casan init --mode enforce --client ...` | | Token/cost là `null` | Client chưa cấp nguồn usage đáng tin | Đúng theo thiết kế — không bịa số; `telemetry_quality=partial` | -| `casan doctor` báo integrity/bootstrap missing | Global install hoặc project init chưa đầy đủ | Chạy lại `install.ps1`, rồi `casan init` | +| `casan doctor` báo integrity/bootstrap missing | Runtime đã chọn hoặc project init chưa đầy đủ | Managed: chạy lại `install.ps1`; Vendored: chạy `casan init --runtime vendored`; sau đó chạy doctor | | Mọi turn hiện `observed_only` trên Windows | Không có `bash` (thiếu Git Bash) → gate H4/H2 không chạy | Cài Git for Windows hoặc set `CASAN_AGENTIC_BASH`; `doctor` sẽ báo `gates_runnable=true` | diff --git a/docs/casan/CASAN_INSTALL_HYBRID.md b/docs/casan/CASAN_INSTALL_HYBRID.md index ec7139b..b9b698d 100644 --- a/docs/casan/CASAN_INSTALL_HYBRID.md +++ b/docs/casan/CASAN_INSTALL_HYBRID.md @@ -64,7 +64,7 @@ cục bộ), `CASAN_DIST_URL` (tải tarball), `CASAN_NO_PATH_LINK=1` (không t ```bash cd -casan init # mặc định project Level 1/core; interactive chọn client +casan init # project mới: interactive chọn runtime rồi client # hoặc chọn level áp dụng cho project: casan init --level 1 --project my-app --client claude casan init --level 2 --project my-app --client claude,codex @@ -74,8 +74,14 @@ casan level show # xem level đã cài + level project casan level set 2 # đổi level project (không cần init lại) ``` +Project mới chạy trong terminal tương tác sẽ được hỏi vị trí Core trước: +`Managed (Recommended)` hoặc `Vendored`, sau đó mới chọn client. Nhấn Enter dùng +Managed và Claude+Codex. Nhập sai sẽ được hỏi lại thay vì kết thúc bằng payload +khó đọc. + `init` và `level show` luôn in runtime mode/path. Project mới mặc định -`managed`; chạy lại init giữ nguyên mode đã chọn. Chuyển mode phải explicit: +`managed`; chạy lại init giữ nguyên mode đã chọn và không hỏi lại runtime. +Chuyển mode phải explicit: ```bash casan init --runtime managed @@ -111,7 +117,7 @@ file đã chỉnh sửa được giữ lại. Thêm `--purge` để xóa cả ru | `.casan/config.json` | project id, enforcement/integration mode, clients | | `.casan/version.lock` | **pin** harness version + hash gate-code | | `.casan/agentic.env` | feature flags bridge Plan-20 | -| `.casan/casan-hook.py` | bootstrap stdlib: load config, resolve + verify harness global, dispatch adapter | +| `.casan/casan-hook.py` | bootstrap stdlib: load config, resolve + verify Core managed/vendored, dispatch adapter | | `.specify/` | thư mục state runtime (logs/trace/admission) | | `.claude/settings.json` | hook Claude Code (Plan-20) | | `.codex/hooks.json` | hook Codex theo schema hiện hành; cần review/trust bằng `/hooks` | @@ -120,8 +126,16 @@ file đã chỉnh sửa được giữ lại. Thêm `--purge` để xóa cả ru Tham số `--client` có thể lặp hoặc comma-separated: `claude`, `codex`, `vscode-copilot`, `all`, `none`. Khi chạy `casan init` trực -tiếp trong terminal, CLI hiển thị menu chọn. Trong automation không có TTY, -mặc định tương thích ngược là `claude,codex`. +tiếp trong terminal, CLI hiển thị menu chọn. Trong automation, dùng +`--non-interactive` và khai báo rõ runtime/client: + +```bash +casan init --non-interactive --level core --runtime managed \ + --mode enforce --client claude,codex +``` + +`--non-interactive`, `--json` hoặc môi trường không có TTY không bao giờ chờ +input. Khi không truyền lựa chọn, mặc định là Managed và `claude,codex`. Các command dành cho người vận hành (`init`, `doctor`, `verify-harness`, `level show`, `uninstall`) mặc định in bản tóm tắt dễ đọc. Thêm `--json` sau @@ -139,8 +153,9 @@ telemetry-only. `--integration-mode` nhận hiện tại. Bootstrap `.casan/casan-hook.py` tự đọc `config.json`; developer không còn phải -source `.casan/agentic.env` trước khi mở IDE. Mỗi invocation kiểm tra live hash -của harness global so với `version.lock` trước khi chạy adapter. +source `.casan/agentic.env` trước khi mở IDE. Mỗi invocation resolve Core theo +runtime mode rồi kiểm tra live hash so với `version.lock` trước khi chạy +adapter. ### Project đã có `.claude`, `.github`, agents, skills hoặc CASAN vendored diff --git a/packages/casan-devkit/casan-init.py b/packages/casan-devkit/casan-init.py index f6363df..866f792 100755 --- a/packages/casan-devkit/casan-init.py +++ b/packages/casan-devkit/casan-init.py @@ -17,9 +17,9 @@ What init writes into the target repo: optional self-contained Core (--runtime vendored) `verify` recomputes the resolved harness gate-code hash and compares it to -version.lock — the pin+VERIFY half. Drift/tamper of the global harness relative -to what the project pinned is caught here (preserves the Plan-16 "gates are -trusted code" guarantee even though the harness lives outside the repo). +version.lock — the pin+VERIFY half. Drift/tamper of the managed or vendored +runtime relative to what the project pinned is caught here (preserves the +Plan-16 "gates are trusted code" guarantee). stdlib-only. Resolves the harness via CASAN_HARNESS_ROOT (set by the global launcher) or --harness. @@ -64,6 +64,7 @@ CLIENT_LABELS = { "codex": "Codex", "vscode-copilot": "VS Code / @casan", } +RUNTIME_MODES = ("managed", "vendored") def _color(code, text): @@ -806,25 +807,61 @@ def migrate_vendored_prompt_contract(target, clients, backups): } -def select_clients(values, interactive): - """Normalize repeatable/comma-separated selections. +def _normalize_runtime_mode(value): + aliases = { + "1": "managed", + "global": "managed", + "shared": "managed", + "2": "vendored", + "local": "vendored", + "offline": "vendored", + "self-contained": "vendored", + "selfcontained": "vendored", + } + normalized = aliases.get(str(value).strip().lower(), + str(value).strip().lower()) + if normalized not in RUNTIME_MODES: + raise ValueError( + "unknown runtime %r (choose managed or vendored)" % value) + return normalized - A plain interactive `casan init` presents the requested IDE menu. In - non-interactive automation, the historical Claude+Codex default is kept. + +def select_runtime_mode(explicit, previous, interactive): + """Resolve Core placement without surprising existing projects. + + An explicit flag always wins. Re-init keeps a valid existing selection. + Only a new interactive adoption opens the placement wizard; automation + defaults to managed and never waits for input. """ - if not values: - if interactive: + if explicit: + return _normalize_runtime_mode(explicit) + if previous in RUNTIME_MODES: + return previous + if not interactive: + return "managed" + + while True: + sys.stderr.write( + "\nCore runtime placement\n\n" + " 1) Managed (Recommended)\n" + " Use the shared CASAN installation, pinned by version and hash.\n" + " Best for developer workstations and managed CI.\n\n" + " 2) Vendored\n" + " Copy production-only Core into .casan/runtime/casan-core.\n" + " Best for offline, air-gapped, or self-contained repositories.\n\n" + "Select runtime [1]: ") + sys.stderr.flush() + answer = sys.stdin.readline() + if not answer or not answer.strip(): + return "managed" + try: + return _normalize_runtime_mode(answer) + except ValueError: sys.stderr.write( - "\nEnable CASAN integrations (comma-separated numbers):\n" - " 1) Claude Code (CLI + official VS Code extension)\n" - " 2) Codex (CLI + official VS Code extension)\n" - " 3) GitHub Copilot in VS Code via explicit @casan route\n" - "Selection [1,2]: ") - sys.stderr.flush() - answer = sys.stdin.readline().strip() or "1,2" - values = [answer] - else: - values = ["claude,codex"] + "Invalid selection. Enter 1 for Managed or 2 for Vendored.\n") + + +def _normalize_clients(values): aliases = { "1": "claude", "2": "codex", @@ -852,6 +889,35 @@ def select_clients(values, interactive): return selected +def select_clients(values, interactive): + """Normalize repeatable/comma-separated selections. + + A plain interactive `casan init` presents the requested IDE menu. In + non-interactive automation, the historical Claude+Codex default is kept. + """ + if values: + return _normalize_clients(values) + if not interactive: + return _normalize_clients(["claude,codex"]) + + while True: + sys.stderr.write( + "\nClient integrations\n\n" + " 1) Claude Code (CLI + official VS Code extension)\n" + " 2) Codex (CLI + official VS Code extension)\n" + " 3) GitHub Copilot in VS Code via explicit @casan route\n\n" + "Select clients (comma-separated) [1,2]: ") + sys.stderr.flush() + answer = sys.stdin.readline() + values = [answer.strip() or "1,2"] if answer else ["1,2"] + try: + return _normalize_clients(values) + except ValueError: + sys.stderr.write( + "Invalid selection. Enter 1, 2, 3, a comma-separated list, " + "`all`, or `none`.\n") + + def merge_vscode_recommendations(target_file, clients, backups): existed = os.path.exists(target_file) doc = _load_json_or(target_file, {}) if existed else {} @@ -965,20 +1031,18 @@ def cmd_init(args): preview = (lvl == 3) # platform is a separate preview SERVICE; init applies the L2 base apply_devkit = (lvl >= 2) - try: - clients = select_clients(args.client, sys.stdin.isatty() and not args.non_interactive) - except ValueError as error: - sys.stderr.write("casan init: %s\n" % error) - return 64 - previous_config = _load_json_or( os.path.join(target, ".casan", "config.json"), {}) previous_runtime_mode = previous_config.get("runtime_mode") - runtime_mode = ( - args.runtime or - (previous_runtime_mode - if previous_runtime_mode in ("managed", "vendored") else "managed") - ) + interactive = ( + sys.stdin.isatty() and not args.non_interactive and not args.json) + try: + runtime_mode = select_runtime_mode( + args.runtime, previous_runtime_mode, interactive) + clients = select_clients(args.client, interactive) + except ValueError as error: + sys.stderr.write("casan init: %s\n" % error) + return 64 runtime_removed = False runtime_files = 0 if runtime_mode == "vendored": @@ -1734,10 +1798,25 @@ def cmd_uninstall(args): def main(argv=None): - parser = argparse.ArgumentParser(prog="casan-init", description="CASAN hybrid adoption") + parser = argparse.ArgumentParser( + prog="casan-init", + description="Adopt CASAN governance into an existing project") sub = parser.add_subparsers(dest="cmd") - pi = sub.add_parser("init", help="adopt CASAN into the current project (config only)") + pi = sub.add_parser( + "init", + help="adopt or reconfigure CASAN in the current project", + description=( + "Adopt CASAN governance into an existing project. New interactive " + "projects are guided through runtime and client selection; existing " + "projects preserve their current runtime unless --runtime is set."), + epilog=( + "examples:\n" + " casan init\n" + " casan init --runtime managed --client claude,codex\n" + " casan init --runtime vendored --client codex\n" + " casan init --non-interactive --runtime managed --client none"), + formatter_class=argparse.RawDescriptionHelpFormatter) pi.add_argument("--target", help="project root (default: cwd)") pi.add_argument("--project", help="project id (^[a-z][a-z0-9-]{1,62}$; default: dir name)") pi.add_argument( @@ -1746,7 +1825,8 @@ def main(argv=None): "vscode-copilot, all, none. Interactive init shows a menu.")) pi.add_argument( "--non-interactive", action="store_true", - help="do not prompt; defaults to the backward-compatible claude,codex set") + help=("do not prompt; new projects default to managed runtime and the " + "backward-compatible claude,codex client set")) pi.add_argument( "--vscode-install", choices=["auto", "yes", "no"], default="auto", help=("install the local CASAN @casan VSIX when vscode-copilot is selected " diff --git a/packages/casan-devkit/tests/adoption-install-tests.sh b/packages/casan-devkit/tests/adoption-install-tests.sh index 99ef515..37dc064 100755 --- a/packages/casan-devkit/tests/adoption-install-tests.sh +++ b/packages/casan-devkit/tests/adoption-install-tests.sh @@ -41,8 +41,8 @@ assert not (root / "packages/casan-harness/level5").exists() assert not (root / "packages/casan-harness/scripts/bash/ci-harness-gate.sh").exists() for relative in ("docs/casan/CASAN_ADOPTION_WINDOWS.md", "docs/casan/CASAN_PROMPT_ENFORCEMENT.md"): text = (root / relative).read_text(encoding="utf-8") - assert "sample-project" in text - assert "Sample Project" in text or relative.endswith("CASAN_PROMPT_ENFORCEMENT.md") + assert "casan init" in text + assert "managed" in text.lower() and "vendored" in text.lower() assert "__PROJECT_ID__" not in text and "__PROJECT_NAME__" not in text PY diff --git a/packages/casan-devkit/tests/hybrid-install-tests.sh b/packages/casan-devkit/tests/hybrid-install-tests.sh index 10b28b5..c7f6212 100755 --- a/packages/casan-devkit/tests/hybrid-install-tests.sh +++ b/packages/casan-devkit/tests/hybrid-install-tests.sh @@ -80,11 +80,38 @@ assert registry["projects"] == [] PY "$CASAN" version >/dev/null 2>&1 && pass "casan version works via launcher" || fail "casan version failed" python3 - "$CASAN_HOME/current/packages/casan-devkit/casan-init.py" <<'PY' \ - && pass "client selector accepts menu numbers, aliases, repeats, and all" \ - || fail "client selector normalization failed" -import importlib.util,sys + && pass "init wizard resolves runtime and client choices safely" \ + || fail "init wizard selection failed" +import contextlib +import importlib.util +import io +import sys + spec=importlib.util.spec_from_file_location("casan_init",sys.argv[1]) m=importlib.util.module_from_spec(spec); spec.loader.exec_module(m) + +# Explicit flags and existing project configuration never open a prompt. +assert m.select_runtime_mode("vendored",None,True)=="vendored" +assert m.select_runtime_mode(None,"vendored",True)=="vendored" +assert m.select_runtime_mode(None,None,False)=="managed" + +# Interactive defaults, aliases, validation, and retry behavior. +old_stdin=m.sys.stdin +try: + m.sys.stdin=io.StringIO("\n") + with contextlib.redirect_stderr(io.StringIO()): + assert m.select_runtime_mode(None,None,True)=="managed" + m.sys.stdin=io.StringIO("9\n2\n") + with contextlib.redirect_stderr(io.StringIO()) as errors: + assert m.select_runtime_mode(None,None,True)=="vendored" + assert "Invalid selection" in errors.getvalue() + m.sys.stdin=io.StringIO("bogus\n3\n") + with contextlib.redirect_stderr(io.StringIO()) as errors: + assert m.select_clients(None,True)==["vscode-copilot"] + assert "Invalid selection" in errors.getvalue() +finally: + m.sys.stdin=old_stdin + assert m.select_clients(["1,3"],False)==["claude","vscode-copilot"] assert m.select_clients(["codex","copilot"],False)==["codex","vscode-copilot"] assert m.select_clients(["all"],False)==["claude","codex","vscode-copilot"]