feat: add production init wizard UX

This commit is contained in:
thanhnv
2026-07-24 12:30:37 +07:00
parent eb3525456f
commit bfebfa9d39
7 changed files with 597 additions and 255 deletions
+420 -202
View File
@@ -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 <project-root>
# 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 <prompt>` |
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<br/>matches version.lock?"}
V -- "No" --> D["Deny or degrade<br/>according to mode"]
U["Developer"] --> C["Claude Code / Codex / @casan"]
C --> B["Project bootstrap<br/>.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<br/>match version.lock?"}
GV --> V
V -- "No" --> D["Deny or degrade<br/>according to enforcement mode"]
V -- "Yes" --> A["Client adapter"]
A --> G["Agentic bridge"]
subgraph TURN["Per-turn lifecycle"]
direction LR
L1["Admission<br/>H1 + H4"] --> L2["Pre-tool gate<br/>H2 + H4"]
L2 --> L3["Post-tool evidence<br/>H5"]
L3 --> L4["Finalize<br/>H3 + H5 + H6 + H7"]
end
G --> L1
L4 --> S["Project runtime state<br/>.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<br/>symlink hoặc junction"]
VER["versions/&lt;version&gt;"]
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/<version>
└── versions/<version>/
├── 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/<br/>config.json<br/>version.lock<br/>agentic.env<br/>init-manifest.json"]
BOOT[".casan/casan-hook.py"]
STATE[".specify/<br/>logs/<br/>state/<br/>.gitignore"]
CLIENTS["Client config khi được chọn<br/>.claude/settings.json<br/>.codex/hooks.json<br/>.vscode/extensions.json"]
L2["Level 2 only<br/>.gitea/workflows/casan-ci.yml<br/>apps/&lt;project-id&gt;/domain/"]
OWNED["Project-owned<br/>source, agents, skills,<br/>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
<project>/
├── .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/<project-id>/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/<project-id>/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 `<file>.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 `<file>.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 <trace-id>` | 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 <prompt>` 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)
+1 -1
View File
@@ -72,7 +72,7 @@ casan — CASAN governance harness CLI ($(version))
Usage: casan <command> [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|set 1..4> Show / change the project's packaging level
+9 -7
View File
@@ -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` |
+22 -7
View File
@@ -64,7 +64,7 @@ cục bộ), `CASAN_DIST_URL` (tải tarball), `CASAN_NO_PATH_LINK=1` (không t
```bash
cd <dự-án-của-bạn>
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
+112 -32
View File
@@ -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(
"\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]: ")
"\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().strip() or "1,2"
values = [answer]
else:
values = ["claude,codex"]
answer = sys.stdin.readline()
if not answer or not answer.strip():
return "managed"
try:
return _normalize_runtime_mode(answer)
except ValueError:
sys.stderr.write(
"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 "
@@ -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
@@ -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"]