# Claude Project Instructions

## Source of Truth
- **Boot instructions**: this file (`CLAUDE.md`)
- **Architecture reference**: `./docs/architecture/PLATFORM.md` — authoritative map of every framework + abstraction that's already built (integration drivers, provisioning queue, offboarding transfer, notification outbox, scheduled jobs, audit, idempotency, …). **READ THIS BEFORE ASKING THE USER STRUCTURAL QUESTIONS.** Most "does Helix have X?" questions are answered here.
- **Design decisions**: `./docs/architecture/DECISIONS.md` — ADR-style log of user-approved choices (role deprecations, schema changes, scope calls, etc.). **READ THIS BEFORE PROPOSING A REDESIGN** — if the user has already decided something, don't re-litigate it.
- **Roadmap**: `./ROADMAP.md` — current state, completed milestones, and next priorities
- **Optional external references**: stakeholder Word/PDF artifacts may be provided out-of-band. Treat them as read-only context when present, but the tracked Markdown files above are the source of truth for this worktree.

**Knowledge file discipline**: when you discover a non-obvious "already exists" (an abstraction you almost re-asked about), append it to `PLATFORM.md`. When the user approves a structural decision, append it to `DECISIONS.md`. Future-you will thank present-you.

**Doc-update rule (NON-NEGOTIABLE, 2026-05-01; digest split out 2026-06-04)**: every PR that ships a feature / fix / migration / new domain / new driver / new cron / new RBAC gate MUST update these files in the SAME PR:

1. `docs/CHANGELOG.md` — **prepend** the dated per-PR digest (newest first). This is where the changelog lives; ROADMAP no longer carries it.
2. `ROADMAP.md` — only when the PR changes **current state**: milestone status, what's-shipped-by-domain, in-flight focus, or open-work list. A pure bug-fix usually touches CHANGELOG only. **Do NOT append PR digests here** — that is exactly what bloated ROADMAP to 1.8 MB and forced this split.
3. `docs/architecture/PLATFORM.md` — domain map / scheduled jobs / drivers / migration index / new abstractions
4. `docs/ops/cloud-lifecycle-ops.md` — only when the PR touches cloud lifecycle (decommission / IAM cleanup / scope sync / cloud drivers)
5. `docs/api/external-api.md` (+ `external-api.zh-CN.md`) — only when the PR adds/changes an external API surface (a new `/api/v1/open/...` endpoint, a new consumer scope in `KNOWN_SCOPES`, or a contract change). This is the integrator-facing reference; a new scope/endpoint that isn't documented here is invisible to partners.

Why: future AI agents (and future-you) read these files to bootstrap context. Stale docs make every next session re-derive state from scratch and propose work that's already done. Stale docs cost real implementation time.

Bar is "kept current," not "exhaustive." The CHANGELOG digest is a paragraph, not a full PR writeup; the ROADMAP edit (if any) is a status line or table row. Before opening the PR, run `git diff docs/CHANGELOG.md ROADMAP.md docs/architecture/PLATFORM.md docs/ops/cloud-lifecycle-ops.md` — empty CHANGELOG diff on a non-trivial change = you forgot.

**MCP tool-count hint rule (NON-NEGOTIABLE, 2026-05-04)**: any PR that adds or removes an `@mcp_server.tool(...)`-decorated function in `server/app/mcp/tools.py` MUST bump the `_REGISTERED_TOOLS_COUNT_HINT` constant at the bottom of that file in the SAME PR (and append a one-line comment naming the new tools). The hint is the source of truth tests / PLATFORM.md / ROADMAP.md cite when describing platform breadth — forgetting to bump it lands a small lie about platform state and costs the next reader / next audit a real `len(@mcp_server.tool)` grep round to re-discover the truth. PR #158 added 7 cloud tools without bumping the hint and was caught a day later — codified to prevent recurrence. Pairs with the existing **MCP-sync rule** (every new self-service HTTP flow ships with its 1:1 MCP tool in the same PR — MCP is a first-class surface for AI clients, not an afterthought).

**Curated-card rule (NON-NEGOTIABLE, 2026-09-06)**: any PR that adds an `@mcp_server.tool(...)` whose body opens `tool_runtime(..., mutates=True)` MUST decide, in the SAME PR, where that tool lands on the assistant's employee-facing card surface — because it lands there **by itself, on the day it ships**. `app/assistant/derived_actions.derived_catalogue()` derives proposable actions straight from the MCP write registry (`mutates=True`), so a new write tool becomes a confirmation card an employee can be handed in chat with zero assistant-side changes. Pick one of two outcomes deliberately:
- **Something an employee does FOR THEMSELVES** → add a `card_labels.TITLES` entry (zh + en + ja) and a `card_labels.FIELDS` entry for every parameter, then raise the bound in `test_the_catalogue_cannot_grow_unnoticed` with a paragraph saying why it belongs. TITLES is not cosmetic: `derived_actions.search()` ranks on tool name + localized titles, so a missing zh title leaves an English string in the zh haystack and the flow becomes **unfindable in Chinese** — the assistant offers a support ticket for a flow it already holds (measured on #3283).
- **Anything else** — it calls `_require_global_admin` / `_require_asset_admin` in its own body, or it decides/acts on someone else's behalf → add it to `derived_actions.OPT_OUT` with a comment naming which category. An admin-only tool left proposable hands an employee a card that **refuses after they tap confirm**, which reads as a platform bug and gives them no way to know the action was never theirs.

The four gates go red **together** and must be fixed together: `test_no_proposable_tool_gates_itself_on_an_admin_role`, `test_every_card_has_a_curated_heading`, `test_every_parameter_row_has_a_label`, and the `test_the_catalogue_cannot_grow_unnoticed` bound. **Bumping the bound alone silently ships the missing cards** — and the bound is deliberately a ceiling, not a count, precisely so growth has to be explained. This has now drifted four times (#3184 bounty owner tools; #3283 `describe_my_approval_flow`; 2026-09-04 `acknowledge_security_awareness`; 访问对象闭环 PR1/PR2's 11 derived tools, cleaned up in #3474) — each time a Chinese-speaking employee was handed a card whose heading was a raw snake_case tool name. Note what #3474 established as the default shape of the fix: of its 11, **7 did not belong in the catalogue at all** (OPT_OUT), only 4 earned cards — so the first question is "should this be on the chat surface", not "what should its title be". Mechanism detail: `docs/architecture/PLATFORM.md` 助手派生动作段.

## Project Context
- **Company size**: ~500 employees
- **Developer**: solo (AI-assisted)
- **Architecture**: Domain-modular backend with extraction seams; do not over-split early
- **North Star**: unified IT platform for people, accounts, permissions, assets, workflows, and later CMDB/cloud objects
- **Phase 1 domains**: HR lifecycle + Asset management (built from scratch)
- **Phase 2**: Software access (built from scratch)
- **Phase 3**: CMDB + Cloud management (migrate from existing platforms)

## Authentication & Identity
- **Login**: Google SSO (production). Frontend uses @react-oauth/google to get id_token -> POST to backend /auth/google/verify -> backend verifies id_token -> match Person -> issue JWT.
- **Password login (local dev only)**: `POST /api/v1/auth/login` with `{"email": "...", "password": "..."}` — uses bcrypt `hashed_password` on `identity_persons`. Enabled only when `ALLOW_PASSWORD_LOGIN=true` in `.env`. Do NOT use in production.
- **Identity source**: SuccessFactors HRIS via OData API. Helix pulls people and org data from HRIS, not the other way around.
- **Sync mode**: IT Admin manually triggers full HRIS sync in Helix admin console. Each sync compares current vs previous data, surfaces new hires and leavers for IT Admin confirmation before processing.
- **Real integrations in Phase 1**: Google SSO, SuccessFactors HRIS sync, and Feishu approval must be real.
- **Other Phase 1 business integrations**: JML, software permission, asset downstream actions, and cloud actions may be mocked first.
- **Future**: add scheduled automatic sync after manual sync is stable.

## RBAC Model (10 canonical roles + manager-derived scope)
- **Global Admin** (`global_admin`; legacy alias `it_admin` accepted): full read/write everywhere.
- **Employee** (`employee`): self scope; can view own assets/accounts/permissions, submit requests, and read selected cloud/network resources through `can_view_cloud_resources()`.
- **Auditor** (`auditor`; legacy alias `security_auditor` accepted): audit/security governance read scope, with explicit review-decision mutations.
- **Asset Admin** (`asset_admin`): asset-domain admin, auto-derived from `asset_office_admins` and region-scoped for writes.
- **Global Asset Admin** (`asset_global_admin`, 2026-06-11): manually-granted global tier of the asset domain — every office-region gate bypasses via `rbac.has_global_asset_scope`; no authority outside the asset domain. Holders co-derive `asset_admin` (office_admin_sync derivation + daily reconciler), so entry gates and frontend surfaces admit them without enumerating a second role name.
- **Cloud Admin** (`cloud_admin`): cloud/network-domain write authority through `can_manage_cloud`; irreversible scope decommission and connector credential edits still stay `global_admin` only. Also drives perpetual cloud-grant semantics through `is_cloud_admin`.
- **HR** (`hr`): HRIS / directory / HR tooling surface.
- **Finance** (`finance`): billing-adjacent read surfaces such as procurement, software costs, cloud costs, and AI spending. Since 2026-09-04 this also covers the **whole cloud/network READ surface** (servers, domains, certificates, DNS, CDN, LBs, object storage, IAM users, subscriptions + their request queue, leased lines, leaver residue) — cost attribution needs the resource catalogue underneath a charge. Read-only: writes stay on `can_manage_cloud`, and the three cloud pages whose READ endpoint is itself `can_manage_cloud` (回收站 / 角色目录 / 阿里云百炼) are excluded from both the route gate and the menu.
- **Management** (`management`): universal admin-tier read-only leadership view; excluded from operational admin-broadcast notification fan-out.
- **Security Officer** (`security_officer`, 2026-08-25): 安全员 —— the security operations desk (external vulnerability triage, severity, bounty, replies, compliance controls, evidence). Passes `can_view_security` + `can_manage_security`, so **inside the security domain its authority equals `auditor`'s, including access-certification item decisions**; it grants nothing outside that domain and joins no `global_admin` alert fan-out. Adding it required four frontend touch points (`ViewRole` union, `VIEW_ROLE_PRIORITY`, `HOME_BY_VIEW_ROLE`, the security routes' `RoleRoute` union) — a role missing from `ViewRole` can never appear in `effectiveUser.roles`.
- **Manager**: derived from `identity_persons.manager_person_id`; can see subordinates' assets/accounts/permissions. It is not a stored DB role.

> **Role naming note**: `server/app/core/rbac.py` canonicalizes legacy `it_admin` → `global_admin` and `security_auditor` → `auditor`. Frontend view roles use `global_admin` as the IT-admin view name.

## Current Progress
All original phases are shipped. Helix now covers foundation/auth, HR lifecycle, assets/procurement, software seats, API keys, provisioning, onboarding/offboarding, Leaver Blocked, approval, CMDB/cloud lifecycle, identity sync, notifications, compliance/auditor, feed, mobile H5, MCP, and the official CLI. `server/app/main.py` mounts the current router set; use `docs/architecture/PLATFORM.md` for the domain map and `ROADMAP.md` for recent/in-flight work.

## Historical Phase Plan
- **M0**: Foundation — shipped
- **M1**: HR domain + frontend — shipped
- **M2**: Asset domain + frontend — shipped
- **M3**: Events/workflow automation — shipped via workflow journal, provisioning queue, scheduled jobs, and coordinators
- **M4**: Feishu approval integration + Leaver Blocked + governance views — shipped
- **M5**: Self-service portal + i18n — shipped
- **M6**: CI + deployment — GitHub Actions + EC2/Docker Compose shipped; EKS remains an optional future migration

## Non-Negotiable Rules
1. Output working code/config, not only proposals.
2. Every implementation step must include verification commands.
3. Domain modules do NOT import from each other — cross-domain coordination goes through `app/core/coordinators/`, workflow journal, scheduled jobs, and provisioning tasks.
4. All API responses use the standard envelope (`success`, `data`, `meta`, `errors`).
5. All state-changing operations must be idempotent.
6. Audit by design: every key action traceable with trace_id.
7. Frontend is pure presentation + API calls. No business logic in frontend.
8. Follow test-first development (TDD-lite) for critical behavior.
9. Product delivery is phased, but platform-level invariants must be preserved from day 1.

## Architecture Rules
- **Backend**: start with one deployable FastAPI backend (`server/`) plus a separate async worker process (`app/worker.py`); keep domain boundaries clean enough to extract later.
- **Database**: MySQL 8.0 (local Docker) / TiDB Cloud (production), single database `helix`, table prefixes per domain (Phase 1: `auth_`, `identity_`, `hr_`, `asset_`, `approval_`, `audit_`).
- **Migrations**: single Alembic migration chain; current head is `519` (as of 2026-08-19 — this number moves often; `ls server/alembic/versions | sort -n | tail -1` is the truth). **Before writing a new migration, claim its number against `origin/main`, not your worktree** — two branches that both took `462` landed on main hours apart and broke the chain (duplicate revision id ⇒ 2 heads ⇒ every backend test shard and the image build fail).
- **Workflow**: workflow journal + scheduled jobs + provisioning tasks are the shipped saga/compensation primitives. Do not introduce a parallel workflow engine without a new ADR.
- **Frontend**: single React app (`web/`), role-based routing for admin vs employee views.
- **Connectors**: Python classes within `app/domains/<domain>/connector.py`, not separate services.
- **Deployment**: Docker Compose for local development; production runs on AWS EC2 + Docker Compose (backend, worker, frontend-nginx); future target is EKS.
- **Portal**: a single React app must support `/me`, `/dashboard`, and role-based navigation from Phase 1.
- **Governance baseline**: `Leaver Blocked`, audit, permission tracing, and scope enforcement are Phase 1 platform capabilities, not future nice-to-haves.
- **Approval**: keep an approval domain/entity model in Phase 1, but treat it as a Feishu approval integration domain, not a self-built workflow engine.

## Backend Tech
- **Python 3.12** + **FastAPI 0.141+** + **SQLAlchemy 2.0 async** (aiomysql driver for MySQL/TiDB)
- **Dependency management**: uv + pyproject.toml
- **HTTP client**: httpx (async) — for Google token verify, SuccessFactors API, Feishu API, all external calls
- **JWT**: PyJWT
- **Config**: pydantic-settings
- **Excel import**: openpyxl (asset data import)
- **Lint + format**: ruff (replaces black + isort + flake8)
- **Type check**: mypy (standard mode, not strict)
- **Sync DB driver**: pymysql (for Alembic migrations)

## Frontend Tech
- **React 19** + **TypeScript 5.9** + **Vite 7** + **Ant Design 6** (desktop) + **antd-mobile 5** (Feishu H5 mobile)
- **Package manager**: pnpm
- **State management**: Zustand
- **HTTP client**: Axios (JWT interceptors)
- **Google Sign-In**: @react-oauth/google (frontend gets id_token, POST to backend verify)
- **Routing**: react-router v7
- **i18n**: react-i18next (zh-CN + en-US + ja-JP)
- **CSS**: Ant Design token system + CSS Modules as needed (no tailwind/styled-components)
- **Permission guards**: `<PermissionGuard>` for role-based UI rendering
- **Type safety**: All API responses typed; align with backend Pydantic schemas

## i18n — Hard Rule (NON-NEGOTIABLE)

**No hardcoded user-facing Chinese (or English) strings in `web/src/`.** Every string that can reach a user's eyes goes through `t('key')`.

This is enforced by an ESLint rule (`no-restricted-syntax` regex for `[\u4e00-\u9fff]`) on:
- JSX text nodes: `<Button>保存</Button>` → `<Button>{t('common.save')}</Button>`
- Antd prop strings: `title=`, `placeholder=`, `okText=`, `cancelText=`, `label=`, `description=`, `tooltip=`, `notFoundContent=`, `header=`
- Toast/notification payloads: `message.success('已保存')` → `message.success(t('asset.saved'))`
- Modal/Popconfirm text props
- `notification.open({ message, description })`

Keys live in [web/src/i18n/index.ts](web/src/i18n/index.ts) and the per-language bundles under [web/src/i18n](web/src/i18n). Namespace pattern: `<domain>.<section>.<action>` (e.g. `asset.list.filterPlaceholder`, `provisioning.admin.retryConfirm`). **Every new key requires zh-CN, en-US, and ja-JP translations.**

OK to keep raw Chinese:
- Code comments (`// 处理离职事件`)
- Console logs / thrown `Error(...)` that never reach the user
- Backend-bound enum string values (`status === 'active'` — these are English, but same principle applies to backend enums sent over the wire)
- The second argument of `t('key', '中文默认值')` — that's the fallback, not a literal

When Antd's built-in strings matter (pagination, Modal default buttons, Empty), route the `ConfigProvider` `locale` prop through the current i18next language — don't hardcode `zhCN`. See [web/src/App.tsx](web/src/App.tsx).

**If you're blocked by legacy violations while editing a file**: fix them as you go. Don't add new ones and don't ship an `eslint-disable` to "just get past CI" without a tracking TODO.

## Google SSO Flow (frontend-initiated)
1. Frontend: user clicks "Sign in with Google" → @react-oauth/google SDK → Google consent → get `id_token`
2. Frontend: POST `/api/v1/auth/google/verify` with `id_token`
3. Backend: verify `id_token` via Google OAuth endpoint → match email against `identity_persons` → create/update `identity_external_identities` (Google provider) → issue JWT
4. Backend: set `refresh_token` in httpOnly cookie, return `access_token` in response body
5. Frontend: store `access_token` in Zustand (memory only), Axios interceptor attaches it
6. On 401: Axios interceptor calls `POST /auth/refresh` (cookie carries refresh_token) → new access_token
7. On refresh failure: redirect to `/login`

## Feishu SSO Flow (frontend-initiated, OAuth 2.0 authorization code)
1. Frontend: user clicks "使用飞书登录" → POST `/api/v1/auth/feishu/authorize-url` with `{redirect_uri: window.location.origin + "/auth/feishu/callback"}`
2. Backend: validate `redirect_uri ∈ CORS_ORIGINS` (defence-in-depth on top of Feishu's redirect whitelist) → generate 256-bit `state` → store in Redis (10-min TTL, bound to redirect_uri) → return Feishu authorize URL
3. Frontend: `window.location.href = authorize_url` (full-page redirect, not popup — Feishu in-app browser blocks popups)
4. Feishu redirects browser back to `/auth/feishu/callback?code=...&state=...`
5. Frontend callback page: POST `/api/v1/auth/feishu/verify` with `{code, state, redirect_uri}`
6. Backend: atomic GETDEL state from Redis (single-use, must match stored redirect_uri) → exchange `code` for `user_access_token` via Feishu OAuth v2 → fetch user_info → match `enterprise_email` (fallback `email`, lowercase) against `identity_persons.work_email` → upsert `identity_external_identities` (provider=`feishu`, subject=`union_id`) → issue JWT (same path as Google)
7. Same cookie / refresh / 401 behaviour as Google flow

**Feishu app credentials**: reused from the existing `chat_approval` integration row (the same Feishu自建应用 used for approval + notification). No new env vars or DB schema. The OAuth web capability + redirect-URI whitelist must be enabled on the Feishu open-platform side; whitelist values: `https://helix.pingcap.net/auth/feishu/callback` (prod) and `http://localhost:3000/auth/feishu/callback` (local dev).

## Local Development
- **Runtime**: Docker Compose (infra only: MySQL 8.0 + Redis 7)
- **Backend dev**: run natively for hot reload: `cd server && uvicorn app.main:app --reload --port 8000`
- **Frontend dev**: run natively for hot reload: `cd web && pnpm dev` (port 3000)
- **API proxy**: Vite dev proxy routes `/api/v1/*` → `http://localhost:8000`
- **Start infra**: `docker compose up -d mysql redis`
- **Reset**: `docker compose down -v && docker compose up -d`
- **Env vars**: single `.env` file from `.env.example` template
- **Production DB**: TiDB Cloud (MySQL-compatible, SSL-encrypted, ap-southeast-1 via PrivateLink)

## Testing Stack & Policy
- **Backend**: pytest + pytest-asyncio, httpx AsyncClient, real MySQL (Docker), respx (external API mock), factory-boy + faker, pytest-cov (>80% critical paths)
- **Frontend**: CI runs `pnpm lint`, `pnpm typecheck`, `pnpm test` (Vitest unit), and `pnpm build`. ⚠️ **Use `pnpm typecheck`, never `pnpm exec tsc --noEmit`** — the latter is a **no-op**: the root `tsconfig.json` is solution-style (`files: []` + project references), so without `-b` it traverses nothing and exits 0 on real type errors. Verified again 2026-08-26 by appending `const x: number = "no"` to a tracked file: the bare form passed, `tsc -b` caught it. CI has been correct since #2077; this line documented the broken form for months and cost a session two undefined names that reached the branch. Vitest (config in `web/vitest.config.ts`, node env, `src/**/*.test.ts`) covers pure framework-free logic — currently the mobile deep-link mapper (`web/src/lib/mobileDeepLink.test.ts`). Add component tests deliberately (would need jsdom + @testing-library) when a UI-behaviour test is warranted.
- **DOM tests never reach the network** (guard in `web/src/test/setup.ts`, 2026-08-27; `fetch` half added the same day): the axios adapter is pinned to a refusal and `globalThis.fetch` rejects, both naming the blocked call. **Stub every endpoint the mounted TREE touches, not only the ones the file asserts on** — a child component's own on-mount read is what got missed: it escaped jsdom to `http://localhost:3000`, a running dev server answered a real `401`, and the interceptor's `logout()` wiped `viewRole` mid-test (`IntelligenceCenterPage.test.tsx` red ~1 full-suite run in 3, invisible on CI where nothing listens on 3000). See #3298.
- **A DOM test that mounts antd must wrap the tree in `WithoutAntdStyleInjection`** (`web/src/test/antdStyleInjection.tsx`, 2026-08-28) or its `*ByRole` queries cost seconds each. jsdom has no layout engine, so `getComputedStyle` answers by running the full cascade — every rule of every sheet in `document.styleSheets`, through its CSS selector engine, per element — and it drops that cache on **every** DOM mutation. antd 6 injects one `<style>` per component-token pair as components mount (~67 sheets / ~1938 rules on a mid-sized page), which puts one `getComputedStyle` call at ~15ms. Nothing calls it on purpose, but Testing Library's `*ByRole` does transitively: computing an accessible name runs `dom-accessibility-api`'s `isHidden` over the subtree, and `waitFor` re-queries after each render. A V8 CPU profile of `DataAccessRequestPage.test.tsx` put **80% of the file's CPU in `getComputedStyle` called from `isHidden`** (45% of it inside one selector-matching regex); single queries cost 1.0–2.5s and the file took 30s+, timing out at the 20s `testTimeout` on a loaded machine. The wrapper is `@ant-design/cssinjs`'s own test-only `StyleProvider mock="server"`: hashed class names are unchanged, only the injection effect is skipped — 1938 rules → 26, and the file dropped 30.18s → 3.64s with all 14 assertions untouched. **Do not "fix" a slow DOM test by raising `testTimeout` or by downgrading `*ByRole` to `*ByText`.** See #3300.
- **Frontend E2E**: not currently configured.
- **Test DB strategy**: isolated test DB per pytest session, created/destroyed via fixture
- TDD-lite: write key tests before implementation for each milestone
- Bug fixes must include a regression test
- **M0**: tests run manually (`pytest` / frontend lint+type+build)
- **M1+**: CI configured, PR merge requires passing CI

### M0 Must-Have Tests
- Google SSO verify → JWT → `/auth/me` returns correct user
- SuccessFactors sync → diff detection → confirm → users created/deactivated
- RBAC: Employee cannot call admin APIs (403)
- Manager scope: can only see subordinates' data
- Audit: write operations produce audit_logs entries

### Test Priority (all milestones)
1. Backend API integration tests (real DB, full auth + RBAC flow) — highest value
2. Backend service unit tests (core business logic)
3. Frontend component tests (permission guards, login flow)
4. Frontend E2E (M3+ only)

## Deployment
- **M0**: Dockerfiles written (multi-stage), Docker Compose for local infra
- **M1**: GitHub Actions CI (two parallel jobs: backend + frontend)
- **M2–M5**: normal dev, CI gates PRs
- **M6**: GitHub Actions CI and EC2/Docker Compose production deploy shipped; Kubernetes/EKS manifests are not authored.
- **Production DB**: TiDB Cloud (MySQL-compatible); **Redis**: managed cloud services, not self-hosted in K8s
- **Secrets**: AWS SSM Parameter Store rendered to container env on EC2 deploy; no .env in images
- **Logging**: stdout JSON → cloud log collector (no self-hosted ELK)
- **Monitoring**: Prometheus metrics endpoint → cloud monitoring (no self-hosted Grafana)

## Delivery Format Per Step
For each step provide:
1. What changed
2. Validation commands
3. Risks/TODOs
4. Next step

## Scope Guardrails
- Start with 4-layer delivery model.
- Keep 6-layer evolution compatibility in design.
- Avoid large refactors unrelated to current milestone.
- Domain modules can be extracted into separate services later (see plan §19 Splitting Guide).
- Do not introduce extra services solely for architectural purity.
- Do not shrink Helix into only an HR/asset tool; Phase 1 is a scoped slice of a larger unified platform.

## Collaboration Conventions
- **Branch strategy**: `main` (protected) ← `feat/<name>` / `fix/<name>` / `chore/<name>`
- **Commit style**: conventional commits (`feat:`, `fix:`, `chore:`, `docs:`, `test:`)
- **PR rule**: all PRs require passing CI; squash merge to main.
- **Backend path**: `server/app/domains/<domain>/` for domain modules.
- **Frontend path**: `web/src/pages/<domain>/` for domain pages.
- **Database naming**: table prefix `<domain>_` (e.g., `hr_employees`, `asset_items`, `cmdb_items`).

## Project Skills

Project-specific skills are in `.claude/skills/`. Invoke with the `Skill` tool:

| Skill | 触发词 | 说明 |
|-------|--------|------|
| `helix-dev` | "start dev", "run helix", "启动开发环境" | 启动 Docker infra + 后端 + 前端 |
| `helix-cmdb-migrate` | "migrate cmdb", "迁移 cmdb", "导入历史数据" | 运行 CMDB 历史数据迁移 |
| `helix-cmdb-sync` | "sync cloud", "cmdb sync", "同步云资源" | 触发云账号同步并监控进度 |

Ops reference: `docs/ops/cmdb-ops.md`
