4 Commits

Author SHA1 Message Date
huanld 6d95157442 chore(agent): clean up internal .agent config and workflow files
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
natlab-integrationtest / natlab-integrationtest (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / fuzz (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (benchmarks) (push) Has been cancelled
CI / Windows (1/2) (push) Has been cancelled
CI / Windows (2/2) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (macOS) (push) Has been cancelled
CI / staticcheck (Linux) (push) Has been cancelled
CI / staticcheck (Windows) (push) Has been cancelled
CI / staticcheck (Portable (1/4)) (push) Has been cancelled
CI / staticcheck (Portable (2/4)) (push) Has been cancelled
CI / staticcheck (Portable (3/4)) (push) Has been cancelled
CI / staticcheck (Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
Remove obsolete agent rules, uploads, workflows and agent-db; update
ARCHITECTURE/GEMINI notes. Refresh WiX build artifact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 14:49:39 +07:00
huanld 07486bf37a fix(deploy): point tray deploy script at signed repo dist binary
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
natlab-integrationtest / natlab-integrationtest (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (benchmarks) (push) Has been cancelled
CI / Windows (1/2) (push) Has been cancelled
CI / Windows (2/2) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / fuzz (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (macOS) (push) Has been cancelled
CI / staticcheck (Linux) (push) Has been cancelled
CI / staticcheck (Windows) (push) Has been cancelled
CI / staticcheck (Portable (1/4)) (push) Has been cancelled
CI / staticcheck (Portable (2/4)) (push) Has been cancelled
CI / staticcheck (Portable (3/4)) (push) Has been cancelled
CI / staticcheck (Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
deploy-tray-1.0.1.ps1 copied from the old staging path
(C:\Users\huanld\tailscale\dist) which held the unsigned build; point it
at the repo dist/ that holds the Azure-signed tailscale-tray.exe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 08:39:45 +07:00
huanld a91c04bf28 chore(release): sign v1.0.1 binaries and MSI (Azure Trusted Signing)
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
natlab-integrationtest / natlab-integrationtest (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (benchmarks) (push) Has been cancelled
CI / Windows (1/2) (push) Has been cancelled
CI / Windows (2/2) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / fuzz (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (macOS) (push) Has been cancelled
CI / staticcheck (Linux) (push) Has been cancelled
CI / staticcheck (Windows) (push) Has been cancelled
CI / staticcheck (Portable (1/4)) (push) Has been cancelled
CI / staticcheck (Portable (2/4)) (push) Has been cancelled
CI / staticcheck (Portable (3/4)) (push) Has been cancelled
CI / staticcheck (Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
Code-sign tailscaled.exe, tailscale.exe, tailscale-tray.exe and rebuild +
sign the MSI via Azure Trusted Signing (codesign-profile). Replaces the
unsigned 1.0.1 MSI; the Gitea release asset is updated to the signed build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 08:35:18 +07:00
huanld dba7b9ba50 fix(tray): connect/login, logout, add-server auth-key; release v1.0.1
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
natlab-integrationtest / natlab-integrationtest (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (benchmarks) (push) Has been cancelled
CI / Windows (1/2) (push) Has been cancelled
CI / Windows (2/2) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / fuzz (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (macOS) (push) Has been cancelled
CI / staticcheck (Linux) (push) Has been cancelled
CI / staticcheck (Windows) (push) Has been cancelled
CI / staticcheck (Portable (1/4)) (push) Has been cancelled
CI / staticcheck (Portable (2/4)) (push) Has been cancelled
CI / staticcheck (Portable (3/4)) (push) Has been cancelled
CI / staticcheck (Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
tailscale.com/cmd/vet / vet (push) Has been cancelled
update-flakehub / flakehub-publish (push) Has been cancelled
Tray client fixes for reconnect/add-server failures diagnosed from tray.log:

- Treat NoState like NeedsLogin so "Connect" runs the login flow instead of
  a no-op EditPrefs (fixes reconnect-after-disconnect/quit doing nothing).
- Run Logout async on bgCtx with a 30s timeout (was 10s on the per-rebuild
  ctx) so a slow Headscale logout no longer hits context-deadline-exceeded.
- Add optional pre-auth key prompt in Add Server, passed via
  ipn.Options.AuthKey, for control servers without OIDC (no browser URL).
- Drain a queued click after a modal dialog so Add Server no longer
  re-popups in a loop.
- Drop the single-domain whitelist in validateAuthURL (it silently blocked
  legit OIDC redirects, so the browser never opened); require HTTPS + host.

Packaging: bump MSI to 1.0.1, enable high cab compression. Add
deploy-tray-1.0.1.ps1 to hot-swap just the tray binary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 07:52:28 +07:00
31 changed files with 153 additions and 6267 deletions
+1 -1
View File
@@ -1 +1 @@
639120951971372439
1.0.0
+6 -8
View File
@@ -9,8 +9,8 @@
Antigravity Kit is a modular system consisting of:
- **20 Specialist Agents** - Role-based AI personas
- **38 Skills** - Domain-specific knowledge modules
- **16 Workflows** - Slash command procedures
- **36 Skills** - Domain-specific knowledge modules
- **11 Workflows** - Slash command procedures
---
@@ -21,7 +21,7 @@ Antigravity Kit is a modular system consisting of:
├── ARCHITECTURE.md # This file
├── agents/ # 20 Specialist Agents
├── skills/ # 36 Skills
├── workflows/ # 12 Slash Commands
├── workflows/ # 11 Slash Commands
├── rules/ # Global Rules
└── scripts/ # Master Validation Scripts
```
@@ -158,7 +158,6 @@ Modular knowledge domains that agents can load on-demand. based on task context.
| Skill | Description |
| ------------------------- | ------------------------- |
| `clean-code` | Coding standards (Global) |
| `coding-levels` | Adaptive communication (0-5) |
| `behavioral-modes` | Agent personas |
| `parallel-agents` | Multi-agent patterns |
| `mcp-builder` | Model Context Protocol |
@@ -169,7 +168,7 @@ Modular knowledge domains that agents can load on-demand. based on task context.
---
## 🔄 Workflows (12)
## 🔄 Workflows (11)
Slash command procedures. Invoke with `/command`.
@@ -180,7 +179,6 @@ Slash command procedures. Invoke with `/command`.
| `/debug` | Debug issues |
| `/deploy` | Deploy application |
| `/enhance` | Improve existing code |
| `/ins-develop` | INS Module development |
| `/orchestrate` | Multi-agent coordination |
| `/plan` | Task breakdown |
| `/preview` | Preview changes |
@@ -269,8 +267,8 @@ For details, see [scripts/README.md](scripts/README.md)
| Metric | Value |
| ------------------- | ----------------------------- |
| **Total Agents** | 20 |
| **Total Skills** | 38 |
| **Total Workflows** | 16 |
| **Total Skills** | 36 |
| **Total Workflows** | 11 |
| **Total Scripts** | 2 (master) + 18 (skill-level) |
| **Coverage** | ~90% web/mobile development |
-2128
View File
File diff suppressed because it is too large Load Diff
-149
View File
@@ -1,149 +0,0 @@
---
description: "Audit a backend module for YARP reverse proxy compliance. Use when: checking module readiness behind SSO gateway, verifying YARP integration, validating module config for production, module proxy audit, module deployment checklist."
tools: [read, search, agent]
user-invocable: true
---
You are a **Module YARP Compliance Auditor** for the INS platform. Your job is to thoroughly audit a module backend project to verify it meets ALL requirements for being proxied behind the INS.SSO YARP reverse proxy gateway.
## Audit Checklist
You MUST check every item below. For each item, report one of:
-**PASS** — Requirement met with evidence
- ⚠️ **WARN** — Partially met or potentially incorrect
-**FAIL** — Requirement not met or missing
### Category 1: Configuration (appsettings)
Check the **production config** file (usually `appsettings.container.release.json` or `appsettings.container.json`):
| # | Check | What to verify |
|---|-------|---------------|
| 1.1 | `ModuleBackend.ModuleId` exists | Must be a lowercase slug (e.g., `ins.pro`, `ins.wjc`) |
| 1.2 | `ModuleBackend.GatewayServerUrl` | Must be the public SSO URL (https://sso.instratech.net) |
| 1.3 | `ModuleBackend.InternalGatewayUrl` | Must use Docker container name (http://sso-instratech:8080) |
| 1.4 | `ModuleBackend.ModuleBaseUrl` | Must be `{GatewayServerUrl}/{ModuleId}` |
| 1.5 | `ModuleBackend.EnablePathBase` | Must be `true` |
| 1.6 | `ModuleBackend.EnableForwardedHeaders` | Must be `true` |
| 1.7 | `ModuleBackend.AllowedCallbackUrls` | Must include `{GatewayServerUrl}/{ModuleId}` |
| 1.8 | `ModuleBackend.AllowedOrigins` | Must include the SSO gateway URL |
| 1.9 | `GrpcClient.ServerUrl` | Must point to SSO gRPC internal endpoint (http://sso-instratech:8082) |
| 1.10 | `Redis.ConnectionString` | Must use Docker hostname (`redis:6379`), NOT `localhost` |
| 1.11 | `RabbitMQ.Host` / `RabbitMQ.HostName` | Must use Docker hostname (`rabbitmq`), NOT `localhost` |
| 1.12 | No hardcoded `localhost` in production config | Search for `localhost` — should not appear in container config |
### Category 2: Program.cs — Middleware Pipeline
Read the module's `Program.cs` and verify:
| # | Check | What to verify |
|---|-------|---------------|
| 2.1 | `UseModulePathBase()` present | Must be called BEFORE any other middleware |
| 2.2 | `UseForwardedHeaders()` present | Must be early in pipeline |
| 2.3 | HTTP/2 cleartext switches | `AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true)` must exist before `WebApplication.CreateBuilder` |
| 2.4 | Token validation middleware | Must have gRPC-based or JWT-based token validation middleware |
| 2.5 | CORS configuration | Must allow SSO gateway origin |
| 2.6 | `MapReverseProxy` NOT present | Module should NOT have its own YARP — only SSO gateway has YARP |
### Category 3: Kestrel Configuration
| # | Check | What to verify |
|---|-------|---------------|
| 3.1 | Dual-port binding | HTTP/1.1 port (e.g., 8000) + HTTP/2 port (e.g., 8001) |
| 3.2 | HTTP/2 port for gRPC | The gRPC port must use `HttpProtocols.Http2` |
| 3.3 | Port consistency | Ports in code must match Dockerfile EXPOSE and docker run -p |
### Category 4: Blazor WASM (if applicable)
| # | Check | What to verify |
|---|-------|---------------|
| 4.1 | `<base href>` in index.html | Must be dynamic or set to `/{ModuleId}/` |
| 4.2 | `_framework` path | Blazor static assets must be accessible under PathBase |
| 4.3 | Navigation/routing | Blazor router must handle PathBase-prefixed routes |
### Category 5: Docker Configuration
| # | Check | What to verify |
|---|-------|---------------|
| 5.1 | Dockerfile exists | Must have a working Dockerfile |
| 5.2 | EXPOSE ports | Must expose both REST and gRPC ports |
| 5.3 | HEALTHCHECK | Must have a health check endpoint |
| 5.4 | Config file copy | Production appsettings must be copied as `appsettings.json` |
| 5.5 | Network compatibility | Deploy script must use `--network app-network` |
### Category 6: gRPC Integration
| # | Check | What to verify |
|---|-------|---------------|
| 6.1 | gRPC client configured | Must have gRPC channel to SSO for token validation |
| 6.2 | Token validation via gRPC | Must validate JWT tokens by calling SSO gRPC service |
| 6.3 | Rule registration | Must register INSRule attributes with SSO on startup |
| 6.4 | Module registration service | Must auto-register module with SSO gateway |
### Category 7: Deploy Script
| # | Check | What to verify |
|---|-------|---------------|
| 7.1 | Deploy script exists | `docker-release.ps1` or equivalent |
| 7.2 | Correct VPS target | Must target production VPS IP |
| 7.3 | Network join | Must include `--network app-network` |
| 7.4 | Container naming | Must use consistent container name matching YARP destination |
## Approach
1. **Ask for module path** if not provided (or infer from context)
2. **Use Explore subagent** to scan the module project structure
3. **Read key files** sequentially:
- Production appsettings (`appsettings.container.release.json` or `appsettings.container.json`)
- Development appsettings (`appsettings.json`) for comparison
- `Program.cs` (middleware pipeline)
- `Dockerfile`
- Deploy script (`docker-release.ps1`)
- Blazor `index.html` (if exists)
4. **Search for patterns**:
- `UseModulePathBase` in *.cs
- `Http2UnencryptedSupport` in *.cs
- `localhost` in production config (should NOT be there)
- `ListenAnyIP` or `Kestrel` config in Program.cs
- `GrpcChannel` or `GrpcClient` usage
- `ModuleRegistration` service
- `INSRule` attribute usage
5. **Generate audit report** with pass/warn/fail for each item
## Output Format
Return a structured audit report in this EXACT format:
```
# YARP Module Audit Report: {ModuleId}
**Date:** {date}
**Module Path:** {path}
**Overall Score:** {pass_count}/{total_checks} passed, {warn_count} warnings, {fail_count} failures
## Category 1: Configuration
| # | Check | Status | Evidence |
|---|-------|--------|----------|
| 1.1 | ModuleId | ✅/⚠️/❌ | Found: "ins.pro" |
...
## Category 2: Middleware Pipeline
...
## Summary
### Critical Failures (must fix before deploy)
- ...
### Warnings (should fix)
- ...
### Recommendations
- ...
```
## Constraints
- DO NOT modify any files — this is a READ-ONLY audit
- DO NOT skip any checklist item — mark as ⚠️ WARN if you cannot determine
- DO NOT assume compliance — verify with actual file contents
- ALWAYS show evidence (file path + relevant code/config snippet)
- If a category doesn't apply (e.g., no Blazor), mark all items in that category as "N/A — {reason}"
-54
View File
@@ -271,57 +271,3 @@ When user's prompt is NOT in English:
- **Test**: `playwright_runner.py`, `test_runner.py`
---
## 🛡️ AUTO-PROTECTION (Always Active — Zero Config)
> These rules are ALWAYS enforced. No configuration needed.
### Scout Block — Forbidden Directories
**NEVER read/list/explore:** `node_modules/`, `.git/`, `dist/`, `build/`, `out/`, `__pycache__/`, `.next/`, `.nuxt/`, `.turbo/`, `vendor/`, `target/`, `coverage/`, `bin/`, `obj/`, `packages/`, `.vs/`, `.idea/`
- Use `package.json` or `*.csproj` instead of reading `node_modules` or `packages/`
- Use `git` CLI instead of reading `.git/`
- Read source instead of build outputs
- If user insists → warn about context waste first
### Privacy Protection — Sensitive Files
**ASK before reading:** `.env`, `.env.*`, `*.key`, `*.pem`, `*.crt`, `*secret*`, `*credential*`, `appsettings.*.json`, `Web.config`, `launchSettings.json`, `*.pfx`, `*.p12`
> 🔒 "This file may contain sensitive data. Should I read it? I will NOT include secret values in my responses."
When approved: NEVER echo passwords/API keys. Replace with `[REDACTED]`.
### Post-Edit Awareness
After editing **5+ files** in one session, suggest a review:
> 📝 "Should I review changes for: complexity, duplication, dead code, shared utilities?"
### Context Efficiency
- **Search before read** — find specific content first, read targeted lines second
- **Don't re-read** — remember info from earlier in conversation
- **Read ranges** — for large files, read 50-100 lines at a time
- **Batch reads** — read multiple files in parallel, not sequential turns
- **Use manifests** — read `package.json` or `*.csproj`/`*.sln` instead of exploring deps
### Naming Enforcement
- C# source: `PascalCase` (`UserService.cs`)
- JS/TS source: `kebab-case` (`user-service.ts`)
- Components: `PascalCase` (`UserProfile.tsx`, `NavMenu.razor`)
- Tests (JS): `{name}.test.{ext}` | Tests (C#): `{Name}Tests.cs`
- **KEY**: Match the project's existing convention. Never mix styles.
- Warn on vague names (`Utils.cs` → suggest `StringUtils.cs`)
### Coding Level Auto-Detection
| Signal | Level | Style |
|---|---|---|
| Asks "what is X?" | Beginner | Explain with analogies |
| Writes pseudo-code | Intermediate | Working code + WHY |
| Gives file paths, function names | Senior | Code-first, trade-offs |
| "just do it", terse | Expert | Zero explanation, pure code |
**Default: Senior.** Match user's language register and formality.
-218
View File
@@ -1,218 +0,0 @@
# Antigravity Agent Kit — Antigravity Rules
> This workspace has an AI toolkit at `.agent/`. Read `.agent/ARCHITECTURE.md` first.
---
## What is `.agent/`?
This workspace contains the Antigravity AI capability expansion toolkit. It provides:
- **Agents** (`.agent/agents/*.md`) — 20 specialist AI personas (frontend, backend, security, etc.)
- **Skills** (`.agent/skills/*/SKILL.md`) — 37 domain-specific knowledge modules
- **Workflows** (`.agent/workflows/*.md`) — 12 step-by-step procedures triggered by slash commands
**Read `.agent/ARCHITECTURE.md` first** to understand the full system map.
---
## Agent Routing Protocol (MANDATORY)
Before writing any code or making design decisions:
1. **Identify the domain** of the user's request (Frontend, Backend, Security, Database, etc.)
2. **Select the matching agent** from `.agent/agents/`
3. **Read that agent's `.md` file** to understand its rules, persona, and linked skills
4. **Load linked skills** listed in the agent file's frontmatter (`skills:` field) — read `SKILL.md` first, then only sections matching the task
5. **Apply the agent's rules** when generating your response
6. **Announce**: State which agent expertise is being applied
### Agent Selection Guide
| Domain | Agent File | Key Skills |
|--------|-----------|------------|
| Web UI/UX | `frontend-specialist.md` | frontend-design, react-best-practices |
| API/Backend | `backend-specialist.md` | api-patterns, nodejs-best-practices |
| Database | `database-architect.md` | database-design |
| Mobile | `mobile-developer.md` | mobile-design |
| Security | `security-auditor.md` | vulnerability-scanner |
| Testing | `test-engineer.md` | testing-patterns, webapp-testing |
| Debugging | `debugger.md` | systematic-debugging |
| Planning | `project-planner.md` | brainstorming, plan-writing |
| Multi-domain | `orchestrator.md` | parallel-agents |
---
## Skill Loading Protocol
Skills are modular knowledge packages inside `.agent/skills/`.
1. Read `SKILL.md` inside the skill folder (e.g., `.agent/skills/clean-code/SKILL.md`)
2. Only read the sections relevant to the current task (**selective reading**)
3. If the skill has a `scripts/` folder, those scripts can be executed for validation
### Skill Structure
```
.agent/skills/{skill-name}/
├── SKILL.md # Main instructions (REQUIRED — read this first)
├── scripts/ # Runnable validation scripts (optional)
├── references/ # Templates and docs (optional)
└── assets/ # Images, resources (optional)
```
### Global Mandatory Skill
**`clean-code`** applies to ALL code output. Always follow `.agent/skills/clean-code/SKILL.md`.
---
## Workflow Conventions
Workflow files in `.agent/workflows/*.md` are triggered by slash commands (e.g., `/debug`, `/deploy`, `/ins-develop`).
### Special Annotations
When reading workflow files, understand these Antigravity-specific markers:
| Marker | Meaning |
|--------|---------|
| `$ARGUMENTS` | Placeholder for user-provided arguments after the slash command |
| `// turbo` | The NEXT step can be auto-executed without user confirmation |
| `// turbo-all` | ALL subsequent steps in this section can be auto-executed |
### Available Workflows (12)
| Command | Purpose |
|---------|---------|
| `/brainstorm` | Socratic discovery |
| `/create` | Create new features |
| `/debug` | Systematic debugging |
| `/deploy` | Production deployment |
| `/enhance` | Improve existing code |
| `/ins-develop` | INS Module development lifecycle |
| `/orchestrate` | Multi-agent coordination |
| `/plan` | Task breakdown |
| `/preview` | Preview changes |
| `/status` | Check project status |
| `/test` | Run tests |
| `/ui-ux-pro-max` | UI design with 50+ styles |
---
## Request Classification
Before any action, classify the request type:
| Request Type | Trigger | Action |
|-------------|---------|--------|
| **Question** | "what is", "explain" | Text response only |
| **Survey** | "analyze", "overview" | Research, no file changes |
| **Simple Code** | "fix", "add" (single file) | Direct inline edit |
| **Complex Code** | "build", "implement", "refactor" | Plan first, then implement |
| **Design/UI** | "design", "page", "dashboard" | Agent routing + plan required |
| **Slash Command** | `/command` | Read matching workflow file |
---
## Rules Priority
When rules conflict, apply this priority:
1. **P0** — This rules file (global)
2. **P1** — Agent-specific rules (from `.agent/agents/*.md`)
3. **P2** — Skill-specific rules (from `.agent/skills/*/SKILL.md`)
---
## Code Standards
- ALL code follows `clean-code` skill rules — no exceptions
- Use **English** for code, comments, and variable names
- Respond in the **user's language** for explanations
- Before modifying files, check for dependencies and update all affected files together
- For complex requests: **ask questions first** before implementing (Socratic Gate)
---
## Important Paths
```
.agent/
├── ARCHITECTURE.md # System overview — READ THIS FIRST
├── agents/ # 20 specialist agent definitions
├── skills/ # 37 knowledge modules
├── workflows/ # 12 slash command procedures
├── rules/ # IDE-specific instruction files
└── scripts/ # Master validation scripts
```
---
## Validation Scripts
| Script | Purpose | When |
|--------|---------|------|
| `checklist.py` | Priority-based project audit | Development, pre-commit |
| `verify_all.py` | Comprehensive verification | Pre-deployment |
```bash
python .agent/scripts/checklist.py .
python .agent/scripts/verify_all.py . --url http://localhost:3000
```
---
## 🛡️ AUTO-PROTECTION RULES (Always Active — Zero Config)
> These rules are **ALWAYS enforced automatically**. No configuration needed.
### Scout Block — Forbidden Directories
**NEVER read/list/explore:** `node_modules/`, `.git/`, `dist/`, `build/`, `out/`, `__pycache__/`, `.next/`, `.nuxt/`, `.turbo/`, `vendor/`, `target/`, `coverage/`, `bin/`, `obj/`, `packages/`, `.vs/`, `.idea/`
- Use `package.json` or `*.csproj` instead of reading `node_modules` or `packages/`
- Use `git` CLI instead of reading `.git/`
- Read source instead of build outputs
- If user insists → warn about context waste first
### Privacy Protection — Sensitive Files
**ASK before reading:** `.env`, `.env.*`, `*.key`, `*.pem`, `*.crt`, `*secret*`, `*credential*`, `appsettings.*.json`, `Web.config`, `launchSettings.json`, `*.pfx`, `*.p12`
> 🔒 "This file may contain sensitive data. Should I read it? I will NOT include secret values in my responses."
When approved: NEVER echo passwords/API keys. Replace with `[REDACTED]`.
### Post-Edit Awareness
After editing **5+ files** in one session, suggest a review:
> 📝 "Should I review changes for: complexity, duplication, dead code, shared utilities?"
### Context Efficiency
- **Search before read** — find specific content first, read targeted lines second
- **Don't re-read** — remember info from earlier in conversation
- **Read ranges** — for large files, read 50-100 lines at a time
- **Batch reads** — read multiple files in parallel, not sequential turns
- **Use manifests** — read `package.json` or `*.csproj`/`*.sln` instead of exploring deps
### Naming Enforcement
- C# source: `PascalCase` (`UserService.cs`)
- JS/TS source: `kebab-case` (`user-service.ts`)
- Components: `PascalCase` (`UserProfile.tsx`, `NavMenu.razor`)
- Tests (JS): `{name}.test.{ext}` | Tests (C#): `{Name}Tests.cs`
- **KEY**: Match the project's existing convention. Never mix styles.
- Warn on vague names (`Utils.cs` → suggest `StringUtils.cs`)
### Coding Level Auto-Detection
| Signal | Level | Style |
|---|---|---|
| Asks "what is X?" | Beginner | Explain with analogies |
| Writes pseudo-code | Intermediate | Working code + WHY |
| Gives file paths, function names | Senior | Code-first, trade-offs |
| "just do it", terse | Expert | Zero explanation, pure code |
**Default: Senior.** Match user's language register and formality.
-170
View File
@@ -1,170 +0,0 @@
# Antigravity Agent Kit — Instructions for GitHub Copilot
> This file teaches Copilot how to use the `.agent/` toolkit installed in this workspace.
---
## What is `.agent/`?
This workspace contains an AI capability expansion toolkit at `.agent/`. It provides:
- **Agents** (`.agent/agents/*.md`) — Specialist AI personas (frontend, backend, security, etc.)
- **Skills** (`.agent/skills/*/SKILL.md`) — Domain-specific knowledge modules
- **Workflows** (`.agent/workflows/*.md`) — Step-by-step procedures triggered by slash commands
**Read `.agent/ARCHITECTURE.md` first** to understand the full system map.
---
## Agent Routing Protocol
Before writing code or making design decisions:
1. **Identify the domain** of the user's request (Frontend, Backend, Security, Database, etc.)
2. **Select the matching agent** from `.agent/agents/`
3. **Read that agent's `.md` file** to understand its rules, persona, and linked skills
4. **Load linked skills** listed in the agent file's frontmatter (`skills:` field)
5. **Apply the agent's rules** when generating your response
### Agent Selection Guide
| Domain | Agent File | Key Skills |
|--------|-----------|------------|
| Web UI/UX | `frontend-specialist.md` | frontend-design, react-best-practices |
| API/Backend | `backend-specialist.md` | api-patterns, nodejs-best-practices |
| Database | `database-architect.md` | database-design |
| Mobile | `mobile-developer.md` | mobile-design |
| Security | `security-auditor.md` | vulnerability-scanner |
| Testing | `test-engineer.md` | testing-patterns, webapp-testing |
| Debugging | `debugger.md` | systematic-debugging |
| Planning | `project-planner.md` | brainstorming, plan-writing |
| Multi-domain | `orchestrator.md` | parallel-agents |
---
## Skill Loading Protocol
Skills are modular knowledge packages. To use a skill:
1. Read `SKILL.md` inside the skill folder (e.g., `.agent/skills/clean-code/SKILL.md`)
2. Only read the sections relevant to the current task (selective reading)
3. If the skill has a `scripts/` folder, those scripts can be executed for validation
### Skill Structure
```
.agent/skills/{skill-name}/
├── SKILL.md # Main instructions (REQUIRED — read this first)
├── scripts/ # Runnable validation scripts (optional)
├── references/ # Templates and docs (optional)
└── assets/ # Images, resources (optional)
```
### Global Mandatory Skill
**`clean-code`** applies to ALL code. Always follow `.agent/skills/clean-code/SKILL.md`.
---
## Workflow Conventions
Workflow files in `.agent/workflows/*.md` are triggered by slash commands (e.g., `/debug`, `/deploy`).
### Special Annotations
When reading workflow files, understand these Antigravity-specific markers:
| Marker | Meaning |
|--------|---------|
| `$ARGUMENTS` | Placeholder for user-provided arguments after the slash command |
| `// turbo` | The NEXT step can be auto-executed without user confirmation |
| `// turbo-all` | ALL subsequent steps in this section can be auto-executed |
### Available Workflows
| Command | File | Purpose |
|---------|------|---------|
| `/brainstorm` | `brainstorm.md` | Socratic discovery |
| `/create` | `create.md` | Create new features |
| `/debug` | `debug.md` | Systematic debugging |
| `/deploy` | `deploy.md` | Production deployment |
| `/enhance` | `enhance.md` | Improve existing code |
| `/ins-develop` | `ins-develop.md` | INS Module development lifecycle |
| `/orchestrate` | `orchestrate.md` | Multi-agent coordination |
| `/plan` | `plan.md` | Task breakdown |
| `/preview` | `preview.md` | Preview changes |
| `/status` | `status.md` | Check project status |
| `/test` | `test.md` | Run tests |
| `/ui-ux-pro-max` | `ui-ux-pro-max.md` | UI design with 50+ styles |
---
## Rules Priority
When rules conflict, apply this priority:
1. **P0** — This instructions file + GEMINI.md rules
2. **P1** — Agent-specific rules (from `.agent/agents/*.md`)
3. **P2** — Skill-specific rules (from `.agent/skills/*/SKILL.md`)
---
## Code Standards
- Follow **clean-code** skill for all code
- Use **English** for code, comments, and variable names
- Respond in the **user's language** for explanations
- Before modifying files, check for dependencies and update all affected files together
---
## Important Paths
```
.agent/
├── ARCHITECTURE.md # System overview — READ THIS FIRST
├── agents/ # 20 specialist agent definitions
├── skills/ # 38 knowledge modules
├── workflows/ # 16 slash command procedures
├── rules/ # IDE-specific instruction files
└── scripts/ # Master validation scripts
```
---
## 🛡️ Auto-Protection Rules (Always Active)
> These rules are always enforced. No configuration needed.
### Forbidden Directories
**NEVER read or explore these directories** — they waste context and provide no useful information:
`node_modules/`, `.git/`, `dist/`, `build/`, `out/`, `bin/`, `obj/`, `packages/`, `.vs/`, `.idea/`, `__pycache__/`, `.next/`, `.nuxt/`, `.turbo/`, `vendor/`, `target/`, `coverage/`
Instead: use `package.json` or `*.csproj` for dependency info. Use `git` commands for version control. Read source files, not build outputs.
### Sensitive Files
**Always ask the user before reading:** `.env`, `.env.*`, `*.key`, `*.pem`, `*.crt`, `*secret*`, `*credential*`, `appsettings.*.json`, `Web.config`, `launchSettings.json`, `*.pfx`, `*.p12`
When approved: never echo passwords or API keys in responses. Replace with `[REDACTED]`.
### Context Efficiency
- Search for specific content before reading entire files
- Don't re-read files already seen in the conversation
- For large files, read focused ranges (50-100 lines) instead of all at once
- Use `package.json` or `*.csproj`/`*.sln` instead of exploring dependency directories
### Naming Conventions
- **C# source files**: `PascalCase` (`UserService.cs`)
- **JS/TS source files**: `kebab-case` (`user-service.ts`)
- **Components**: `PascalCase` (`UserProfile.tsx`, `NavMenu.razor`)
- **Tests (JS)**: `{name}.test.{ext}` | **Tests (C#)**: `{Name}Tests.cs`
- **KEY**: Always match the project's existing naming convention
### Communication Style
Adapt based on user's expertise level. Default: Senior (code-first, minimal explanation). If user asks basic questions, shift to teaching mode. If user gives terse instructions, respond with pure code.
-86
View File
@@ -1,86 +0,0 @@
# Antigravity Agent Kit — Cursor Rules
> This workspace has an AI toolkit at `.agent/`. Read `.agent/ARCHITECTURE.md` first.
## System Overview
- **Agents** (`.agent/agents/*.md`): Specialist AI personas — read the matching agent file before responding
- **Skills** (`.agent/skills/*/SKILL.md`): Domain knowledge modules — read selectively based on task
- **Workflows** (`.agent/workflows/*.md`): Step-by-step procedures for slash commands
## Agent Routing (MANDATORY)
Before writing any code:
1. Identify the domain (Frontend, Backend, Security, etc.)
2. Read the matching agent file from `.agent/agents/`
3. Load skills listed in the agent's `skills:` frontmatter field
4. Apply agent rules when generating code
Quick reference:
- Web UI → `frontend-specialist.md` (skills: frontend-design, react-best-practices)
- API/Backend → `backend-specialist.md` (skills: api-patterns, nodejs-best-practices)
- Database → `database-architect.md` (skills: database-design)
- Mobile → `mobile-developer.md` (skills: mobile-design)
- Security → `security-auditor.md` (skills: vulnerability-scanner)
- Testing → `test-engineer.md` (skills: testing-patterns, webapp-testing)
- Debugging → `debugger.md` (skills: systematic-debugging)
- Planning → `project-planner.md` (skills: brainstorming, plan-writing)
- Complex/multi-domain → `orchestrator.md` (skills: parallel-agents)
## Skill Loading
Read `SKILL.md` inside the skill folder. Only read sections relevant to current task.
Global mandatory: `.agent/skills/clean-code/SKILL.md` — applies to ALL code.
Skill structure:
```
.agent/skills/{name}/
├── SKILL.md # Main instructions (read first)
├── scripts/ # Validation scripts (optional)
├── references/ # Templates (optional)
```
## Workflow Annotations
When reading `.agent/workflows/*.md`, recognize these markers:
- `$ARGUMENTS` — placeholder for user input after the slash command
- `// turbo` — next step can auto-execute without confirmation
- `// turbo-all` — all subsequent steps in this section can auto-execute
## Rules Priority
P0 (this file / GEMINI.md) > P1 (agent .md) > P2 (skill SKILL.md)
## Code Standards
- ALL code follows `clean-code` skill rules
- English for code/comments/variables; respond in user's language
- Check file dependencies before modifying; update all affected files together
## Paths
```
.agent/
├── ARCHITECTURE.md # READ FIRST
├── agents/ # 20 agents
├── skills/ # 38 skills
├── workflows/ # 16 workflows
├── rules/ # IDE instruction files
└── scripts/ # Validation scripts
```
## 🛡️ Auto-Protection (Always Active)
**Scout Block — NEVER read:** `node_modules/`, `.git/`, `dist/`, `build/`, `out/`, `bin/`, `obj/`, `packages/`, `.vs/`, `__pycache__/`, `.next/`, `vendor/`, `coverage/`
→ Use `package.json`/`*.csproj` for deps. Use `git` CLI for version control. Read source not outputs.
**Privacy — ASK before reading:** `.env`, `*.key`, `*.pem`, `*secret*`, `appsettings.*.json`, `Web.config`, `launchSettings.json`, `*.pfx`
→ Never echo passwords/API keys. Replace with `[REDACTED]`.
**Post-Edit** — After 5+ file edits, suggest complexity/duplication review.
**Context Efficiency** — Grep before read. Don't re-read. Read ranges (50-100 lines). Batch parallel reads. Use `package.json`/`*.csproj`/`*.sln` instead of exploring deps.
**Naming** — C#: `PascalCase`. JS/TS: `kebab-case`. Components: `PascalCase`. Match existing project convention.
**Coding Level** — Default: Senior. Auto-detect from user signals. Beginner=explain, Expert=pure code zero explanation.
-1
View File
@@ -1 +0,0 @@
Hello world from test2
-86
View File
@@ -1,86 +0,0 @@
# Antigravity Agent Kit — Windsurf Rules
> This workspace has an AI toolkit at `.agent/`. Read `.agent/ARCHITECTURE.md` first.
## System Overview
- **Agents** (`.agent/agents/*.md`): Specialist AI personas — read the matching agent file before responding
- **Skills** (`.agent/skills/*/SKILL.md`): Domain knowledge modules — read selectively based on task
- **Workflows** (`.agent/workflows/*.md`): Step-by-step procedures for slash commands
## Agent Routing (MANDATORY)
Before writing any code:
1. Identify the domain (Frontend, Backend, Security, etc.)
2. Read the matching agent file from `.agent/agents/`
3. Load skills listed in the agent's `skills:` frontmatter field
4. Apply agent rules when generating code
Quick reference:
- Web UI → `frontend-specialist.md` (skills: frontend-design, react-best-practices)
- API/Backend → `backend-specialist.md` (skills: api-patterns, nodejs-best-practices)
- Database → `database-architect.md` (skills: database-design)
- Mobile → `mobile-developer.md` (skills: mobile-design)
- Security → `security-auditor.md` (skills: vulnerability-scanner)
- Testing → `test-engineer.md` (skills: testing-patterns, webapp-testing)
- Debugging → `debugger.md` (skills: systematic-debugging)
- Planning → `project-planner.md` (skills: brainstorming, plan-writing)
- Complex/multi-domain → `orchestrator.md` (skills: parallel-agents)
## Skill Loading
Read `SKILL.md` inside the skill folder. Only read sections relevant to current task.
Global mandatory: `.agent/skills/clean-code/SKILL.md` — applies to ALL code.
Skill structure:
```
.agent/skills/{name}/
├── SKILL.md # Main instructions (read first)
├── scripts/ # Validation scripts (optional)
├── references/ # Templates (optional)
```
## Workflow Annotations
When reading `.agent/workflows/*.md`, recognize these markers:
- `$ARGUMENTS` — placeholder for user input after the slash command
- `// turbo` — next step can auto-execute without confirmation
- `// turbo-all` — all subsequent steps in this section can auto-execute
## Rules Priority
P0 (this file / GEMINI.md) > P1 (agent .md) > P2 (skill SKILL.md)
## Code Standards
- ALL code follows `clean-code` skill rules
- English for code/comments/variables; respond in user's language
- Check file dependencies before modifying; update all affected files together
## Paths
```
.agent/
├── ARCHITECTURE.md # READ FIRST
├── agents/ # 20 agents
├── skills/ # 38 skills
├── workflows/ # 16 workflows
├── rules/ # IDE instruction files
└── scripts/ # Validation scripts
```
## 🛡️ Auto-Protection (Always Active)
**Scout Block — NEVER read:** `node_modules/`, `.git/`, `dist/`, `build/`, `out/`, `bin/`, `obj/`, `packages/`, `.vs/`, `__pycache__/`, `.next/`, `vendor/`, `coverage/`
→ Use `package.json`/`*.csproj` for deps. Use `git` CLI for version control. Read source not outputs.
**Privacy — ASK before reading:** `.env`, `*.key`, `*.pem`, `*secret*`, `appsettings.*.json`, `Web.config`, `launchSettings.json`, `*.pfx`
→ Never echo passwords/API keys. Replace with `[REDACTED]`.
**Post-Edit** — After 5+ file edits, suggest complexity/duplication review.
**Context Efficiency** — Search before reading full files. Don't re-read files already seen. Read focused ranges (50-100 lines). Use `package.json`/`*.csproj`/`*.sln` for dep info.
**Naming** — C#: `PascalCase`. JS/TS: `kebab-case`. Components: `PascalCase`. Match existing project convention.
**Coding Level** — Default: Senior. Auto-detect from user signals. Beginner=explain, Expert=pure code zero explanation.
-93
View File
@@ -1,93 +0,0 @@
---
name: coding-levels
description: Adaptive communication style system. Auto-detects user's experience level and adjusts AI output accordingly. From beginner-friendly teaching to expert-mode zero-explanation code.
keywords: [communication, style, level, beginner, expert, senior, output]
---
# Coding Levels — Adaptive AI Communication
> Automatically adjust your communication style to match the user's expertise level. The goal is to be maximally helpful without being condescending or verbose.
---
## Quick Reference
| Level | Name | When Detected | Core Principle |
|---|---|---|---|
| 0 | **ELI5** | User is completely new to programming | Explain Like I'm 5 — analogies, metaphors |
| 1 | **Junior** | User knows basics, asks "how do I..." | Guided learning — working code with explanations |
| 2 | **Mid** | User knows patterns, asks about trade-offs | Practical — code + brief rationale |
| 3 | **Senior** | User gives file paths, talks architecture | Code-first — minimal explanation, focus on decisions |
| 4 | **Lead** | User reviews approaches, mentions scale | Strategic — alternatives, long-term implications |
| 5 | **Expert** | Terse instructions, "just do it" | Zero overhead — pure code, challenge if wrong |
**Default: Level 3 (Senior)** — unless clear signals indicate otherwise.
---
## Detection Signals
### Level 0 — ELI5 (Beginner)
**Signals**: "what is a function?", "I don't understand", "can you explain from scratch?"
**Behavior**:
- Use real-world analogies ("a function is like a recipe...")
- Step-by-step walkthrough with numbered instructions
- Add inline comments to every non-obvious line
- Show expected output for each step
- Offer to explain any concept further
### Level 1 — Junior
**Signals**: "how do I create a...", writes incomplete code, asks about syntax
**Behavior**:
- Provide complete, working code
- Explain the "why" behind each major decision
- Link to official docs when relevant
- Warn about common pitfalls
- Suggest best practices proactively
### Level 2 — Mid-Level
**Signals**: Writes working code, asks about patterns, mentions specific libraries
**Behavior**:
- Code + brief rationale for design choices
- Mention alternatives ("you could also use X, but Y is better here because...")
- Skip basic explanations (user knows what a promise is)
- Focus on edge cases and error handling
### Level 3 — Senior (DEFAULT)
**Signals**: Provides file paths, discusses architecture, names specific functions
**Behavior**:
- Code-first, explanation only when non-obvious
- Focus on trade-offs, not tutorials
- Respect their existing patterns — follow project conventions
- Only explain when the approach differs from what they might expect
- Suggest improvements without being preachy
### Level 4 — Lead
**Signals**: Discusses scaling, team impact, mentions CI/CD, reviews approaches
**Behavior**:
- Present multiple options with pros/cons table
- Consider team-wide implications
- Discuss maintainability and long-term costs
- Skip implementation details unless asked
- Focus on architecture decisions
### Level 5 — Expert (God Mode)
**Signals**: Terse instructions, "just do it", "implement X", no questions
**Behavior**:
- **ZERO explanation** — every word must earn its place
- Pure code output, no comments unless truly non-obvious
- Challenge their approach ONLY if there's a critical flaw
- Never repeat back what they said
- Never pad with "Sure!" or "Great question!"
- If unsure about intent, ask ONE precise question, not three
---
## Rules
1. **Don't announce the level** — never say "I detected you're at Senior level"
2. **Adapt dynamically** — if the user shifts style mid-conversation, follow them
3. **Match formality** — casual user = casual AI, formal user = formal AI
4. **Match language** — if user switches from English to Vietnamese, follow
5. **Never condescend** — even at Level 0, respect intelligence
6. **Never over-explain** — at Level 3+, unnecessary explanation is noise
-1
View File
@@ -1 +0,0 @@
Hello world from test1
-123
View File
@@ -1,123 +0,0 @@
---
description: Audit all features on a page/module like a picky user. Find and fix form issues, API silent failures, missing fields, hardcoded values.
---
# /audit - Feature Audit & Fix
Quy trình kiểm tra tính năng toàn diện cho một page/module, đóng vai user khó tính.
---
## Phase 1: IDENTIFY — Xác định scope
1. **Xác định page/module cần audit** từ user request
2. **Liệt kê tất cả files liên quan:**
- Frontend page (`.razor`)
- Frontend service (`HrmApiService.cs` hoặc tương đương)
- Backend controller
- Backend service
- DTOs / Entities / Enums
3. **Đọc toàn bộ code** của các files trên
---
## Phase 2: AUDIT — Kiểm tra từng chức năng
Với MỖI form/popup/grid trên page, kiểm tra theo checklist:
### 2a. Form Fields — Trường nhập liệu
- [ ] So sánh form fields với DTO/Entity → **Có trường nào trong DTO mà form không hiển thị?**
- [ ] Kiểm tra có giá trị **hardcoded** trong handler thay vì cho user nhập? (e.g., `DayType.Normal`, `IsNightShift = false`, `ShiftCodeId = 1`)
- [ ] Mọi trường bắt buộc có dấu `*` (required indicator)?
- [ ] Các trường time dùng `DxTimeEdit` thay vì `DxTextBox`?
- [ ] Các trường date dùng `DxDateEdit` thay vì `DxTextBox`?
- [ ] Các trường enum/lookup dùng `DxComboBox` với data source thay vì `DxTextBox`?
- [ ] **FK fields (xxxId) PHẢI dùng `DxComboBox` hiện tên/mã từ DB, KHÔNG dùng `DxSpinEdit` nhập số ID thủ công**
- Tìm: `DxSpinEdit.*Id` → phải đổi sang `DxComboBox` với data load từ API
- Label phải hiện tên entity ("Phòng ban", "Ca làm việc"), KHÔNG hiện "Phòng ban ID"
- ComboBox cần có `NullText`, `FilteringMode`, `ClearButtonDisplayMode`
- [ ] Checkbox cho boolean fields?
### 2b. Validation — Kiểm tra ràng buộc
- [ ] Required fields có validation trước khi submit?
- [ ] Date range: ToDate >= FromDate?
- [ ] Time range: ToTime > FromTime (trừ ca đêm)?
- [ ] Số lượng/giá trị > 0 khi cần?
- [ ] Có hiện Toast warning với message cụ thể khi validation fail?
### 2c. API Error Handling — Xử lý lỗi API
- [ ] Mọi POST/PUT/DELETE API call có kiểm tra `resp.IsSuccessStatusCode`?
- [ ] Khi API fail, có **throw exception** (KHÔNG return default value im lặng)?
- [ ] Error message có đọc `resp.Content` để hiển thị chi tiết?
- [ ] Frontend handler có `try/catch` bao bọc API call?
- [ ] Catch block có hiện `Toast.Error()` với `ex.Message`?
### 2d. Popup / Form Reset
- [ ] `ShowAddXxx()` reset TẤT CẢ field về default (không chỉ một vài)?
- [ ] Date fields reset về `DateTime.Today` (không để giá trị cũ)?
- [ ] Nullable fields reset về `null`?
- [ ] String fields reset về `""`?
### 2e. Grid Display — Hiển thị dữ liệu
- [ ] Grid columns khớp với DTO properties cần hiển thị?
- [ ] Enum columns hiển thị text thay vì số (e.g., "Phép năm" thay vì "0")?
- [ ] Date/Time columns format đúng?
- [ ] Có column thừa hoặc thiếu?
### 2f. Data Integrity — Tính toàn vẹn dữ liệu
- [ ] Default values trong entity có hợp lý? (e.g., DateOnly default = 0001-01-01 → cần set mặc định)
- [ ] Foreign key relationships có valid? (e.g., EmployeeId phải tồn tại)
- [ ] Seed data có đủ cho tất cả dropdowns?
---
## Phase 3: REPORT — Báo cáo audit
Tạo danh sách issues theo severity:
```markdown
### 🔴 Critical — Silent failures, data loss
### 🟡 Medium — Missing fields, hardcoded values
### 🟢 Low — UI polish, format
```
---
## Phase 4: FIX — Sửa batch
// turbo-all
1. **Sửa tất cả issues trong batch** — ưu tiên Critical → Medium → Low
2. **Build:**
```powershell
dotnet build INS.HRM.slnx
```
3. **Test:**
```powershell
dotnet test src/INS.HRM.Backend/INS.HRM.Backend.csproj
```
4. **Start server:**
```powershell
dotnet run --project src/INS.HRM.Backend
```
---
## Phase 5: VERIFY — Xác nhận
5. Tóm tắt kết quả cho user:
- Số issues tìm được
- Số issues đã sửa
- Build status
- Test status
---
## Examples
```
/audit trang chấm công
/audit module nhân viên
/audit tất cả các form trên trang tổ chức
/audit page recruitment
```
@@ -1,693 +0,0 @@
# Audit Service Platform Variants — Implementation Guide
> Triển khai `IAuditService` abstraction cho 3 nền tảng: **Blazor WASM**, **Blazor Server**, **API-only**
---
## Kiến trúc tổng quan
```mermaid
graph TD
subgraph "Shared Contract (INS.Shared)"
I["IAuditService"]
end
subgraph "Blazor WASM"
W["WasmAuditService"]
W -->|"HTTP POST"| MC["Module Controller"]
MC -->|"gRPC"| GS["LoggingGrpcService"]
GS -->|"Publish"| RMQ
end
subgraph "Blazor Server"
S["ServerAuditService"]
S -->|"Direct inject"| PUB["IAuditLogPublisher"]
PUB -->|"Publish"| RMQ
end
subgraph "API Only"
A["ApiAuditService"]
A -->|"Direct inject"| PUB
end
I --> W
I --> S
I --> A
RMQ["RabbitMQ"] --> CON["AuditLogConsumerService"]
CON --> DB["SQL Server"]
```
---
## File 1: `IAuditService.cs` — Shared Contract
> **Location**: `lib/INS.Shared/Services/IAuditService.cs`
```csharp
namespace INS.Shared.Services
{
/// <summary>
/// Platform-agnostic audit logging interface.
/// Implementations: WasmAuditService, ServerAuditService, ApiAuditService
/// </summary>
public interface IAuditService
{
/// <summary>
/// Log INSERT operation
/// </summary>
Task LogInsertAsync(string tableName, string rowId, object? newData,
string? schemaName = null, string? functionName = null);
/// <summary>
/// Log UPDATE operation
/// </summary>
Task LogUpdateAsync(string tableName, string rowId,
object? oldData, object? newData,
string? schemaName = null, string? primaryKeyColumns = null,
string? functionName = null);
/// <summary>
/// Log DELETE operation
/// </summary>
Task LogDeleteAsync(string tableName, string rowId, object? oldData,
string? schemaName = null, string? functionName = null);
/// <summary>
/// Log custom operation (APPROVE, REJECT, EXPORT, etc.)
/// </summary>
Task LogCustomAsync(string tableName, string operation,
string? rowId = null, object? oldData = null, object? newData = null,
string? schemaName = null, string? functionName = null);
}
}
```
---
## File 2: `WasmAuditService.cs` — Blazor WASM Variant
> **Location**: `lib/INS.DXComponents/Logging/WasmAuditService.cs`
> **Flow**: `HttpClient POST → Module Controller → gRPC → RabbitMQ`
```csharp
using System.Net.Http.Json;
using System.Text.Json;
using INS.Shared.Services;
using INS.Module.Shared.Models;
namespace INS.DXComponents.Logging;
/// <summary>
/// Blazor WASM implementation: gửi audit log qua HTTP tới Module Backend.
/// Module Backend forward qua gRPC tới INS.Server → RabbitMQ.
///
/// Dùng khi: Client-side rendering, không có direct access tới RabbitMQ.
/// DI Lifetime: Scoped (per-circuit)
/// </summary>
public class WasmAuditService : IAuditService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IAuditUserProvider _userProvider;
public WasmAuditService(
IHttpClientFactory httpClientFactory,
IAuditUserProvider userProvider)
{
_httpClientFactory = httpClientFactory;
_userProvider = userProvider;
}
public Task LogInsertAsync(string tableName, string rowId, object? newData,
string? schemaName = null, string? functionName = null)
=> SendAsync(tableName, "INSERT", rowId, null, newData, schemaName, functionName);
public Task LogUpdateAsync(string tableName, string rowId,
object? oldData, object? newData,
string? schemaName = null, string? primaryKeyColumns = null,
string? functionName = null)
=> SendAsync(tableName, "UPDATE", rowId, oldData, newData, schemaName, functionName);
public Task LogDeleteAsync(string tableName, string rowId, object? oldData,
string? schemaName = null, string? functionName = null)
=> SendAsync(tableName, "DELETE", rowId, oldData, null, schemaName, functionName);
public Task LogCustomAsync(string tableName, string operation,
string? rowId = null, object? oldData = null, object? newData = null,
string? schemaName = null, string? functionName = null)
=> SendAsync(tableName, operation, rowId ?? "", oldData, newData, schemaName, functionName);
private async Task SendAsync(string tableName, string operation, string rowId,
object? oldData, object? newData, string? schemaName, string? functionName)
{
try
{
var user = await _userProvider.GetCurrentUserAsync();
var dto = new RabbitMQAuditLogDto
{
TableName = tableName,
RowId = rowId,
Operation = operation,
OldData = Serialize(oldData),
NewData = Serialize(newData),
ChangedBy = user.Username ?? user.UserId ?? "System",
Timestamp = DateTime.UtcNow
};
using var client = _httpClientFactory.CreateClient("AuthenticatedClient");
await client.PostAsJsonAsync("/api/logging/audit", dto);
}
catch
{
// Swallow - audit failure không break UI
}
}
private static string? Serialize(object? data)
=> data is null ? null
: data is string s ? s
: JsonSerializer.Serialize(data);
}
```
---
## File 3: `ServerAuditService.cs` — Blazor Server Variant
> **Location**: `lib/INS.DXComponents/Logging/ServerAuditService.cs`
> **Flow**: `Direct inject IAuditLogPublisher → RabbitMQ` (KHÔNG qua HTTP)
```csharp
using System.Text.Json;
using INS.Shared.Services;
namespace INS.DXComponents.Logging;
/// <summary>
/// Blazor Server implementation: inject trực tiếp IAuditLogPublisher.
/// KHÔNG gọi HTTP tới chính server (tránh circular call).
///
/// Dùng khi: Server-side rendering, có direct access tới RabbitMQ.
/// DI Lifetime: Scoped (per-circuit)
/// </summary>
public class ServerAuditService : IAuditService
{
private readonly IAuditLogPublisher _publisher;
private readonly IAuditUserProvider _userProvider;
private readonly ICircuitContextAccessor _circuitContext;
public ServerAuditService(
IAuditLogPublisher publisher,
IAuditUserProvider userProvider,
ICircuitContextAccessor circuitContext)
{
_publisher = publisher;
_userProvider = userProvider;
_circuitContext = circuitContext;
}
public Task LogInsertAsync(string tableName, string rowId, object? newData,
string? schemaName = null, string? functionName = null)
=> PublishAsync(schemaName ?? "dbo", tableName, rowId, "INSERT",
null, newData, null, functionName);
public Task LogUpdateAsync(string tableName, string rowId,
object? oldData, object? newData,
string? schemaName = null, string? primaryKeyColumns = null,
string? functionName = null)
=> PublishAsync(schemaName ?? "dbo", tableName, rowId, "UPDATE",
oldData, newData, primaryKeyColumns, functionName);
public Task LogDeleteAsync(string tableName, string rowId, object? oldData,
string? schemaName = null, string? functionName = null)
=> PublishAsync(schemaName ?? "dbo", tableName, rowId, "DELETE",
oldData, null, null, functionName);
public Task LogCustomAsync(string tableName, string operation,
string? rowId = null, object? oldData = null, object? newData = null,
string? schemaName = null, string? functionName = null)
=> PublishAsync(schemaName ?? "dbo", tableName, rowId ?? "", operation,
oldData, newData, null, functionName);
private async Task PublishAsync(string schemaName, string tableName,
string rowId, string operation,
object? oldData, object? newData,
string? primaryKeyColumns, string? functionName)
{
try
{
await _publisher.PublishAsync(
schemaName: schemaName,
tableName: tableName,
rowId: rowId,
operation: operation,
oldData: Serialize(oldData),
newData: Serialize(newData),
primaryKeyColumns: primaryKeyColumns,
functionName: functionName ?? "BlazorServer");
}
catch
{
// Swallow - audit failure không break UI
}
}
private static string? Serialize(object? data)
=> data is null ? null
: data is string s ? s
: JsonSerializer.Serialize(data);
}
```
---
## File 4: `ApiAuditService.cs` — API-only Variant
> **Location**: `src/INS.Backend/Services/ApiAuditService.cs`
> **Flow**: `Direct inject IAuditLogPublisher → RabbitMQ`
```csharp
using System.Text.Json;
using INS.Shared.Services;
namespace INS.Backend.Services;
/// <summary>
/// API-only implementation: inject IAuditLogPublisher + IRequestContextAccessor.
/// Auto-enrich từ HTTP context (đã có CorrelationIdMiddleware).
///
/// Dùng khi: Pure API backend (không có Blazor component).
/// DI Lifetime: Scoped (per-request)
/// </summary>
public class ApiAuditService : IAuditService
{
private readonly IAuditLogPublisher _publisher;
public ApiAuditService(IAuditLogPublisher publisher)
{
_publisher = publisher;
}
public Task LogInsertAsync(string tableName, string rowId, object? newData,
string? schemaName = null, string? functionName = null)
=> _publisher.PublishAsync(schemaName ?? "dbo", tableName, rowId,
"INSERT", null, Serialize(newData), null, functionName);
public Task LogUpdateAsync(string tableName, string rowId,
object? oldData, object? newData,
string? schemaName = null, string? primaryKeyColumns = null,
string? functionName = null)
=> _publisher.PublishAsync(schemaName ?? "dbo", tableName, rowId,
"UPDATE", Serialize(oldData), Serialize(newData), primaryKeyColumns, functionName);
public Task LogDeleteAsync(string tableName, string rowId, object? oldData,
string? schemaName = null, string? functionName = null)
=> _publisher.PublishAsync(schemaName ?? "dbo", tableName, rowId,
"DELETE", Serialize(oldData), null, null, functionName);
public Task LogCustomAsync(string tableName, string operation,
string? rowId = null, object? oldData = null, object? newData = null,
string? schemaName = null, string? functionName = null)
=> _publisher.PublishAsync(schemaName ?? "dbo", tableName, rowId ?? "",
operation, Serialize(oldData), Serialize(newData), null, functionName);
private static string? Serialize(object? data)
=> data is null ? null
: data is string s ? s
: JsonSerializer.Serialize(data);
}
```
---
## File 5: `IAuditUserProvider.cs` — User Context Abstraction
> **Location**: `lib/INS.Shared/Services/IAuditUserProvider.cs`
```csharp
namespace INS.Shared.Services
{
/// <summary>
/// Abstraction để lấy thông tin user hiện tại, hoạt động trên cả 3 platform.
/// Blazor WASM/Server: từ AuthenticationStateProvider
/// API: từ HttpContext.User
/// </summary>
public interface IAuditUserProvider
{
Task<AuditUserInfo> GetCurrentUserAsync();
}
public record AuditUserInfo(
string? UserId,
string? Username,
string? IpAddress = null,
string? SessionId = null);
}
```
### 5a. Blazor Implementation (cả WASM và Server)
> **Location**: `lib/INS.DXComponents/Logging/BlazorAuditUserProvider.cs`
```csharp
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
using INS.Shared.Services;
namespace INS.DXComponents.Logging;
/// <summary>
/// Lấy user info từ AuthenticationStateProvider (hoạt động cả WASM + Server)
/// </summary>
public class BlazorAuditUserProvider : IAuditUserProvider
{
private readonly AuthenticationStateProvider _authState;
public BlazorAuditUserProvider(AuthenticationStateProvider authState)
{
_authState = authState;
}
public async Task<AuditUserInfo> GetCurrentUserAsync()
{
var state = await _authState.GetAuthenticationStateAsync();
var user = state.User;
if (user.Identity?.IsAuthenticated != true)
return new AuditUserInfo(null, "Anonymous");
return new AuditUserInfo(
UserId: user.FindFirst(ClaimTypes.NameIdentifier)?.Value,
Username: user.FindFirst(ClaimTypes.Name)?.Value
?? user.FindFirst("preferred_username")?.Value
);
}
}
```
### 5b. API Implementation
> **Location**: `src/INS.Backend/Services/ApiAuditUserProvider.cs`
```csharp
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using INS.Shared.Services;
namespace INS.Backend.Services;
/// <summary>
/// Lấy user info từ HttpContext (API-only, có HTTP pipeline)
/// </summary>
public class ApiAuditUserProvider : IAuditUserProvider
{
private readonly IHttpContextAccessor _httpContext;
public ApiAuditUserProvider(IHttpContextAccessor httpContext)
{
_httpContext = httpContext;
}
public Task<AuditUserInfo> GetCurrentUserAsync()
{
var user = _httpContext.HttpContext?.User;
if (user?.Identity?.IsAuthenticated != true)
return Task.FromResult(new AuditUserInfo(null, "System"));
var ip = _httpContext.HttpContext?.Connection?.RemoteIpAddress?.ToString();
return Task.FromResult(new AuditUserInfo(
UserId: user.FindFirst(ClaimTypes.NameIdentifier)?.Value,
Username: user.FindFirst(ClaimTypes.Name)?.Value,
IpAddress: ip
));
}
}
```
---
## File 6: `ICircuitContextAccessor.cs` — Blazor Server Context
> **Location**: `lib/INS.DXComponents/Logging/ICircuitContextAccessor.cs`
```csharp
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.AspNetCore.Http;
namespace INS.DXComponents.Logging;
/// <summary>
/// Blazor Server thay thế cho IRequestContextAccessor.
/// Capture HttpContext tại thời điểm kết nối SignalR (initial connection).
/// SAU ĐÓ SignalR không còn HttpContext → phải lưu lại.
/// </summary>
public interface ICircuitContextAccessor
{
string? CorrelationId { get; }
string? IpAddress { get; }
string? UserAgent { get; }
string? SessionId { get; }
string CircuitId { get; }
}
/// <summary>
/// Capture context từ HttpContext lúc circuit mở.
/// Registered: Scoped (per-circuit)
/// </summary>
public class CircuitContextAccessor : ICircuitContextAccessor
{
public string? CorrelationId { get; set; }
public string? IpAddress { get; set; }
public string? UserAgent { get; set; }
public string? SessionId { get; set; }
public string CircuitId { get; set; } = "";
}
/// <summary>
/// CircuitHandler populate CircuitContextAccessor khi circuit mở.
/// Capture IP, UserAgent, SessionId từ initial HTTP handshake.
/// </summary>
public class AuditCircuitHandler : CircuitHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly CircuitContextAccessor _circuitContext;
public AuditCircuitHandler(
IHttpContextAccessor httpContextAccessor,
CircuitContextAccessor circuitContext)
{
_httpContextAccessor = httpContextAccessor;
_circuitContext = circuitContext;
}
public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken ct)
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext != null)
{
_circuitContext.CircuitId = circuit.Id;
_circuitContext.IpAddress = httpContext.Connection.RemoteIpAddress?.ToString();
_circuitContext.UserAgent = httpContext.Request.Headers.UserAgent.ToString();
_circuitContext.SessionId = httpContext.Session?.Id;
_circuitContext.CorrelationId = httpContext.Request.Headers["X-Correlation-ID"]
.FirstOrDefault() ?? Guid.NewGuid().ToString("N");
}
return Task.CompletedTask;
}
}
```
---
## File 7: `AuditServiceExtensions.cs` — DI Registration
> **Location**: `lib/INS.DXComponents/Logging/AuditServiceExtensions.cs`
```csharp
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection;
using INS.Shared.Services;
namespace INS.DXComponents.Logging;
public static class AuditServiceExtensions
{
/// <summary>
/// Blazor WASM: audit log qua HTTP → Module Controller → gRPC → RabbitMQ
/// Yêu cầu: IHttpClientFactory đã cấu hình "AuthenticatedClient"
/// </summary>
public static IServiceCollection AddAuditService_BlazorWasm(this IServiceCollection services)
{
services.AddScoped<IAuditUserProvider, BlazorAuditUserProvider>();
services.AddScoped<IAuditService, WasmAuditService>();
return services;
}
/// <summary>
/// Blazor Server: audit log direct inject → IAuditLogPublisher → RabbitMQ
/// Yêu cầu: IAuditLogPublisher, IRabbitMQService đã đăng ký
/// </summary>
public static IServiceCollection AddAuditService_BlazorServer(this IServiceCollection services)
{
services.AddHttpContextAccessor();
// Context capture từ SignalR initial connection
services.AddScoped<CircuitContextAccessor>();
services.AddScoped<ICircuitContextAccessor>(sp => sp.GetRequiredService<CircuitContextAccessor>());
services.AddScoped<CircuitHandler, AuditCircuitHandler>();
// Audit services
services.AddScoped<IAuditUserProvider, BlazorAuditUserProvider>();
services.AddScoped<IAuditService, ServerAuditService>();
return services;
}
/// <summary>
/// API-only: audit log direct inject → IAuditLogPublisher → RabbitMQ
/// Yêu cầu: IAuditLogPublisher, IRequestContextAccessor đã đăng ký
/// </summary>
public static IServiceCollection AddAuditService_Api(this IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddScoped<IAuditUserProvider, ApiAuditUserProvider>();
services.AddScoped<IAuditService, ApiAuditService>();
return services;
}
}
```
---
## File 8: `INS_AuditLogger.razor` — Updated Component (Multi-Platform)
> **Location**: `lib/INS.DXComponents/Logging/INS_AuditLogger.razor`
> **Thay đổi**: Sử dụng `IAuditService` thay vì `LogService` trực tiếp
```razor
@using INS.Shared.Services
@inject IAuditService AuditService
@code {
/// <summary>
/// Component ẩn để tự động log audit khi có thay đổi dữ liệu.
/// Hoạt động trên CẢ 3 platform (WASM, Server, API).
/// Sử dụng: <INS_AuditLogger @ref="auditLogger" />
/// </summary>
public Task LogInsertAsync(string tableName, string? rowId, object? newValue)
=> AuditService.LogInsertAsync(tableName, rowId ?? "", newValue);
public Task LogUpdateAsync(string tableName, string? rowId,
object? oldValue, object? newValue)
=> AuditService.LogUpdateAsync(tableName, rowId ?? "", oldValue, newValue);
public Task LogDeleteAsync(string tableName, string? rowId, object? oldValue)
=> AuditService.LogDeleteAsync(tableName, rowId ?? "", oldValue);
public Task LogCustomAsync(string tableName, string operation,
string? rowId = null, object? oldValue = null, object? newValue = null)
=> AuditService.LogCustomAsync(tableName, operation, rowId, oldValue, newValue);
}
```
---
## Cấu hình `Program.cs` theo nền tảng
### Blazor WASM
```csharp
// Program.cs (Client project)
builder.Services.AddAuditService_BlazorWasm();
```
### Blazor Server
```csharp
// Program.cs (Server project)
// Prerequisites
builder.Services.AddScoped<IRequestContextAccessor, RequestContextAccessor>();
builder.Services.AddSingleton<IRollbackSqlGenerator, RollbackSqlGenerator>();
builder.Services.AddScoped<IAuditLogPublisher, AuditLogPublisher>();
// Audit service
builder.Services.AddAuditService_BlazorServer();
```
### API-only
```csharp
// Program.cs (API project)
// Prerequisites
builder.Services.AddScoped<IRequestContextAccessor, RequestContextAccessor>();
builder.Services.AddSingleton<IRollbackSqlGenerator, RollbackSqlGenerator>();
builder.Services.AddScoped<IAuditLogPublisher, AuditLogPublisher>();
// Middleware pipeline
app.UseCorrelationId();
app.UseActivityLogging();
// Audit service
builder.Services.AddAuditService_Api();
```
---
## Usage Pattern — Giống nhau trên cả 3 platform
### Blazor Page
```razor
<INS_AuditLogger @ref="auditLogger" />
@code {
private INS_AuditLogger auditLogger = default!;
async Task SaveUser(UserDto user)
{
var oldData = await LoadUser(user.Id);
await UpdateUserInDb(user);
await auditLogger.LogUpdateAsync("Users", user.Id.ToString(), oldData, user);
}
}
```
### API Controller (inject trực tiếp)
```csharp
[ApiController]
public class UsersController : ControllerBase
{
private readonly IAuditService _audit;
[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, UserDto dto)
{
var old = await _repo.GetById(id);
await _repo.Update(dto);
await _audit.LogUpdateAsync("Users", id.ToString(), old, dto,
functionName: nameof(Update));
return Ok();
}
}
```
---
## So sánh 3 variants
| Aspect | Blazor WASM | Blazor Server | API-only |
|--------|-------------|---------------|----------|
| **Class** | `WasmAuditService` | `ServerAuditService` | `ApiAuditService` |
| **Transport** | HTTP → gRPC → RMQ | Direct → RMQ | Direct → RMQ |
| **Network hop** | 2 (HTTP + gRPC) | 0 | 0 |
| **Latency** | ~10-50ms | ~1-5ms | ~1-5ms |
| **User context** | `BlazorAuditUserProvider` | `BlazorAuditUserProvider` | `ApiAuditUserProvider` |
| **Request context** | N/A (client-side) | `ICircuitContextAccessor` | `IRequestContextAccessor` |
| **CorrelationId** | From server response header | From initial SignalR handshake | From `CorrelationIdMiddleware` |
| **Requires RabbitMQ** | ❌ (module backend has it) | ✅ Direct | ✅ Direct |
| **Dependencies** | `IHttpClientFactory` | `IAuditLogPublisher` | `IAuditLogPublisher` |
| **Registration** | `AddAuditService_BlazorWasm()` | `AddAuditService_BlazorServer()` | `AddAuditService_Api()` |
@@ -1,323 +0,0 @@
# 🔍 Multi-Agent Review: Audit Log Flow
> **Mục đích**: Phản biện luồng Audit Log từ 4 góc nhìn chuyên gia, đảm bảo triển khai được cho cả **API-only** và **Blazor Server** model.
---
## 🤖 Agent 1: `@backend-specialist` — Architecture & Scalability
### ✅ Điểm mạnh
| Aspect | Assessment |
|--------|-----------|
| **Layered Architecture** | Controller → Service → Repository pattern tuân thủ tốt |
| **Async by default** | Tất cả publisher/consumer đều `async Task` |
| **Dependency Injection** | Lifetimes đúng: Scoped (per-request), Singleton (stateless), HostedService (background) |
| **Error Isolation** | Audit log failure swallow exceptions, không break business flow |
| **Message Broker Decoupling** | RabbitMQ tách biệt hoàn toàn publisher/consumer |
### 🔴 CRITICAL: Gaps khi triển khai cho Blazor Server
> [!CAUTION]
> **`IRequestContextAccessor` KHÔNG hoạt động trong Blazor Server** vì không có HTTP request pipeline!
| Problem | Blazor WASM (hiện tại) | Blazor Server | API-only |
|---------|----------------------|---------------|----------|
| `IRequestContextAccessor` | ✅ HTTP POST → Middleware populate | ❌ **SignalR Hub, không có HttpContext** | ✅ HTTP Request |
| `CorrelationIdMiddleware` | ✅ HTTP headers | ❌ **Không có HTTP headers** | ✅ HTTP headers |
| `LogService` POST to API | ✅ HttpClient call | ⚠️ **Gọi API từ server → chính nó?** Circular! | N/A |
| `INS_AuditLogger` lấy auth | ✅ `AuthenticationStateProvider` | ✅ Works (nhưng flow khác) | N/A |
**Giải pháp đề xuất cho Blazor Server**:
```csharp
// 1. Tạo ICircuitContextAccessor thay vì IRequestContextAccessor
public interface ICircuitContextAccessor
{
string? UserId { get; set; }
string? Username { get; set; }
string? IpAddress { get; set; }
string? SessionId { get; set; }
string? CorrelationId { get; set; }
}
// 2. Populate từ CircuitHandler thay vì middleware
public class AuditCircuitHandler : CircuitHandler
{
public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken ct)
{
// Lấy HttpContext từ initial connection
// HttpContext chỉ available tại thời điểm kết nối SignalR
}
}
// 3. Inject trực tiếp IAuditLogPublisher trong Blazor Server
// Không cần roundtrip qua HTTP/gRPC
@inject IAuditLogPublisher AuditPublisher
```
### 🟡 WARNING: Missing Pieces cho API-only Model
1. **Không có Logging Controller trong tài liệu**: Module Backend Controller nhận POST từ `LogService` nhưng KHÔNG được document. Cần document `/api/logging/audit` endpoint ở module backend.
2. **Thiếu Retry Policy cho Publisher**: `AuditLogPublisher.PublishAsync()` catch exception nhưng KHÔNG retry. Nếu RabbitMQ tạm unavailable → message bị mất.
```csharp
// Đề xuất: Thêm local buffer hoặc retry policy
public class ResilientAuditLogPublisher : IAuditLogPublisher
{
// Option A: Polly retry
// Option B: In-memory queue + background flush
// Option C: Outbox pattern (ghi vào DB trước, background job publish)
}
```
3. **Consumer chỉ xử lý `AuditLogMessage`**: Nhưng `LoggingGrpcService` publish `RabbitMQAuditLogDto` (khác model). Consumer cần handle cả 2 hoặc cần adapter thống nhất model trước khi publish.
---
## 🤖 Agent 2: `@security-auditor` — Security & OWASP
### 🔴 CRITICAL Findings
| # | Severity | Finding | OWASP | Remediation |
|---|----------|---------|-------|-------------|
| S1 | **CRITICAL** | `RollbackSqlGenerator` build SQL bằng string concat | A05 Injection | Rollback SQL chỉ dùng cho **reference**, KHÔNG BAO GIỜ auto-execute. Thêm warning comment. |
| S2 | **HIGH** | `RequestBody` được log raw | A09 Logging | Nhạy cảm data (passwords, tokens) có thể bị log. Cần sanitize/redact sensitive fields trước khi publish. |
| S3 | **HIGH** | Không có authorization trên Consumer | A01 Access Control | HostedService chạy với full system permissions. Cần validate message source. |
| S4 | **MEDIUM** | `OldData`/`NewData` lưu raw JSON | A04 Crypto | PII data (email, phone, address) lưu plaintext. Cần xem xét encryption at rest hoặc redaction policy. |
| S5 | **MEDIUM** | Không có rate limiting cho audit publish | A02 Misconfiguration | Malicious module có thể flood RabbitMQ queue. Cần throttle hoặc quota per module. |
### Chi tiết Finding S1: SQL Injection Risk
```csharp
// HIỆN TẠI: RollbackSqlGenerator.cs
private string FormatValue(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => $"N'{EscapeSqlString(element.GetString() ?? "")}'",
// EscapeSqlString chỉ replace ' → ''
// KHÔNG đủ cho tất cả SQL injection vectors!
};
}
```
> [!WARNING]
> `EscapeSqlString` chỉ escape single quotes. Các vectors khác như `; DROP TABLE` trong numeric fields, hoặc Unicode tricks vẫn có thể exploit nếu rollback SQL được auto-execute.
**Remediation**:
- Rollback SQL chỉ dùng tham khảo (display-only) → Thêm `-- WARNING: Manual review required before execution`
- Nếu cần auto-execute → chuyển sang parameterized queries hoặc stored procedure
### Chi tiết Finding S2: Sensitive Data Logging
```csharp
// AuditLogPublisher.cs line 123
RequestBody = _requestContext.RequestBody, // ← RAW body!
```
**Đề xuất**: Thêm redaction middleware:
```csharp
public static class SensitiveDataRedactor
{
private static readonly string[] SensitiveFields =
{ "password", "token", "secret", "creditCard", "ssn" };
public static string Redact(string? json) { /* redact matching fields */ }
}
```
---
## 🤖 Agent 3: `@debugger` — Failure Modes & Observability
### Failure Mode Analysis (5 Whys)
**Scenario: Audit log KHÔNG được ghi vào DB**
```
WHY message không vào DB?
→ Consumer NACK (return false) hoặc message bị mất
WHY consumer NACK?
→ DbContext.SaveChangesAsync() throw exception (DB connection fail, constraint violation)
WHY DbContext fail?
→ Connection string sai, hoặc schema [log] chưa tạo, hoặc DB full
WHY không biết bị lỗi?
→ LogError chỉ log local, KHÔNG có alerting mechanism
WHY không có alerting?
→ CHƯA implement Dead Letter Queue (DLQ) hoặc health check cho consumer
```
### 🟡 Missing Observability
| Gap | Impact | Recommendation |
|-----|--------|----------------|
| **Không có DLQ** | NACK message bị requeue vô hạn → CPU spike | Thêm DLQ: `ins.server.auditlog.dlq` sau N retries |
| **Không có metrics** | Không biết queue depth, processing rate, failure rate | Export Prometheus metrics từ consumer |
| **Consumer một chiều** | Không biết consumer chạy hay không từ UI | Đã có `/api/rabbitmq/consumer-status` nhưng cần health check dashboard |
| **Message ordering** | Không guarantee order → UPDATE có thể đến trước INSERT | Chấp nhận được vì audit log timestamp-based, nhưng cần document |
| **Duplicate messages** | RabbitMQ at-least-once delivery → có thể duplicate INSERT | Cần idempotency check (correlation_id + timestamp + table + rowId) |
### Troubleshooting Gaps trong tài liệu
Tài liệu thiếu các scenarios debugging phổ biến:
```
1. "Consumer connected nhưng không process message"
→ Kiểm tra binding key mismatch (logs.audit.server vs logs.audit.#)
→ Kiểm tra exchange type (topic vs direct)
2. "Message bị mất sau restart"
→ Queue có durable=true không?
→ Message có persistent=true không?
→ Kiểm tra IRabbitMQService default settings
3. "Blazor Server không ghi audit log"
→ IRequestContextAccessor null (không có HTTP context)
→ LogService gọi HTTP tới chính nó (circular dependency)
```
---
## 🤖 Agent 4: `@frontend-specialist` — Blazor Component & UX
### Blazor Server vs WASM: Component Behavior
| Aspect | Blazor WASM | Blazor Server | Action Required |
|--------|-------------|---------------|-----------------|
| `LogService` HTTP call | Client → Server (OK) | Server → Server (❌ Circular) | **Inject `IAuditLogPublisher` trực tiếp** |
| `INS_AuditLogger` rendering | Component trên client | Component trên server | Hoạt động, nhưng UserId flow khác |
| `AuthenticationStateProvider` | Frontend auth state | Server auth state | ✅ Cả hai đều hoạt động |
| Error swallowing | User không thấy lỗi | User không thấy lỗi | ✅ Consistent |
| Serialization overhead | JSON serialize trên client | JSON serialize trên server | ✅ Cả hai đều OK |
### 🔴 CRITICAL: `LogService` Circular Call trong Blazor Server
```
Blazor Server:
INS_AuditLogger → LogService.AuditAsync()
→ HttpClient.PostAsync("/api/logging/audit")
→ Gọi tới CHÍNH SERVER ĐANG CHẠY
→ Self-referencing HTTP call → có thể deadlock hoặc connection exhaustion!
```
**Giải pháp**:
```csharp
// Tạo IServerAuditLogger cho Blazor Server
public interface IServerAuditLogger
{
Task LogAsync(string tableName, string operation, string? rowId,
string? oldData, string? newData);
}
// Implementation inject trực tiếp IAuditLogPublisher (không qua HTTP)
public class ServerAuditLogger : IServerAuditLogger
{
private readonly IAuditLogPublisher _publisher;
private readonly AuthenticationStateProvider _authState;
public async Task LogAsync(string tableName, string operation,
string? rowId, string? oldData, string? newData)
{
var user = await GetCurrentUser();
await _publisher.PublishAsync("dbo", tableName, rowId ?? "",
operation, oldData, newData, functionName: "BlazorServer");
}
}
```
### Component Registration Pattern
```csharp
// Blazor WASM → dùng LogService (HTTP)
services.AddScoped<LogService>();
// Blazor Server → dùng ServerAuditLogger (direct inject)
services.AddScoped<IServerAuditLogger, ServerAuditLogger>();
// Shared → INS_AuditLogger cần abstraction
// Option: ILogServiceAbstraction với 2 implementations
```
---
## 📋 Tổng hợp: Bảng Action Items
| Priority | Finding | Agent | Model | Action |
|----------|---------|-------|-------|--------|
| 🔴 P0 | `IRequestContextAccessor` không hoạt động Blazor Server | Backend | Blazor Server | Tạo `ICircuitContextAccessor` + `CircuitHandler` |
| 🔴 P0 | `LogService` circular call trong Blazor Server | Frontend | Blazor Server | Tạo `IServerAuditLogger` inject trực tiếp `IAuditLogPublisher` |
| 🔴 P0 | SQL Injection risk trong rollback SQL | Security | Cả hai | Mark rollback SQL là display-only, thêm warning |
| 🟡 P1 | Sensitive data logging (passwords, tokens) | Security | Cả hai | Thêm redaction trước khi publish |
| 🟡 P1 | Không có DLQ cho failed messages | Debugger | Cả hai | Configure DLQ + max retry count |
| 🟡 P1 | Message model mismatch (DTO vs full model) | Backend | Cả hai | Thống nhất hoặc adapter pattern |
| 🟡 P1 | Thiếu module logging controller doc | Backend | API | Document `/api/logging/audit` endpoint |
| 🟢 P2 | Không có duplicate detection | Debugger | Cả hai | Idempotency check trên consumer |
| 🟢 P2 | Thiếu metrics/observability | Debugger | Cả hai | Prometheus metrics cho queue depth |
| 🟢 P2 | Không có rate limiting cho publish | Security | API | Throttle per-module publish rate |
---
## 🏗️ Đề xuất: Abstraction Layer cho Multi-Model Support
Để audit log hoạt động trên cả API, Blazor WASM, và Blazor Server:
```mermaid
graph TD
subgraph "Blazor WASM"
A["INS_AuditLogger"] --> B["LogService<br/>(HTTP Client)"]
B --> C["Module Controller<br/>POST /api/logging/audit"]
C --> D["gRPC → LoggingGrpcService"]
end
subgraph "Blazor Server"
E["INS_AuditLogger"] --> F["IServerAuditLogger<br/>(Direct Inject)"]
F --> G["IAuditLogPublisher<br/>(Direct)"]
end
subgraph "API Only"
H["Controller Code"] --> G
end
D --> I["IRabbitMQService.SendAsync()"]
G --> I
I --> J["RabbitMQ<br/>ins.logs.exchange"]
J --> K["AuditLogConsumerService"]
K --> L["SQL Server<br/>[log].[audit_log]"]
```
**Key abstraction**: `IAuditService` interface cho cả 3 model
```csharp
public interface IAuditService
{
Task LogAsync(string tableName, string operation, string? rowId,
object? oldData, object? newData, string? functionName = null);
}
// Blazor WASM implementation → HTTP → gRPC → RabbitMQ
public class WasmAuditService : IAuditService { }
// Blazor Server implementation → Direct → RabbitMQ
public class ServerAuditService : IAuditService { }
// API implementation → Direct → RabbitMQ
public class ApiAuditService : IAuditService { }
```
Registration per hosting model:
```csharp
// Program.cs
if (isBlazorServer)
services.AddScoped<IAuditService, ServerAuditService>();
else if (isBlazorWasm)
services.AddScoped<IAuditService, WasmAuditService>();
else // API
services.AddScoped<IAuditService, ApiAuditService>();
```
@@ -1,942 +0,0 @@
# Audit Log Flow - Tài liệu chi tiết luồng hoạt động
> **Mục đích**: Tài liệu này mô tả đầy đủ chi tiết luồng ghi Audit Log trong hệ thống INS, đủ để tái tạo lại chức năng tương tự.
---
## 1. Tổng quan kiến trúc
Hệ thống Audit Log sử dụng kiến trúc **event-driven** với **RabbitMQ** làm message broker, tách biệt hoàn toàn việc ghi log khỏi business logic.
```mermaid
graph TD
subgraph "TIER 1: Frontend (Blazor WASM)"
A["INS_AuditLogger.razor<br/>(DXComponents)"]
B["LogService.cs<br/>(HTTP Client)"]
end
subgraph "TIER 2: Module Backend"
C["Module Controller<br/>(REST API /api/logging/audit)"]
D["gRPC Client<br/>(INS.LoggingController)"]
end
subgraph "TIER 3: INS.WASM.AUTH Server"
E["CorrelationIdMiddleware"]
F["ActivityLoggingMiddleware"]
G["LoggingGrpcService"]
H["AuditLogPublisher"]
I["IRabbitMQService.SendAsync()"]
end
subgraph "Message Broker"
J["RabbitMQ<br/>Exchange: ins.logs.exchange<br/>(topic)"]
K["Queue: ins.server.auditlog.queue<br/>Binding: logs.audit.#"]
end
subgraph "Consumer Layer"
L["AuditLogConsumerService<br/>(BackgroundService)"]
M["BaseRabbitMqConsumerService<br/>(lazy init + retry)"]
end
subgraph "Database"
N["SQL Server<br/>[Log].[AuditLogs]"]
end
A --> B
B -->|"POST /api/logging/audit"| C
C -->|"gRPC"| D
D -->|"gRPC SendAuditLog()"| G
G --> I
H --> I
F --> I
I -->|"Publish"| J
J -->|"Route: logs.audit.*"| K
K -->|"Consume"| L
L --> M
L -->|"EF Core INSERT"| N
E -->|"Inject CorrelationId"| G
E -->|"Inject CorrelationId"| H
```
### 3 Đường vào (Entry Points) cho Audit Log
| # | Entry Point | Routing Key | Khi nào dùng |
|---|-------------|-------------|--------------|
| 1 | **gRPC từ Module** (`LoggingGrpcService`) | `logs.audit.server` | Module Frontend gọi qua `LogService` → Module Controller → gRPC |
| 2 | **AuditLogPublisher** (direct call) | `logs.audit.data` | Backend code gọi trực tiếp khi có data change |
| 3 | **REST API** (`TestRabbitMQController`) | `logs.audit.server` | Test/debug endpoint |
---
## 2. Chi tiết từng thành phần
### 2.1. Frontend Blazor: `INS_AuditLogger.razor`
> **Location**: `lib/INS.DXComponents/Logging/INS_AuditLogger.razor`
**Chức năng**: Component ẩn (không render UI), inject vào Blazor page để ghi audit log cho thao tác CRUD.
**Cách sử dụng trong page**:
```razor
<INS_AuditLogger @ref="auditLogger" />
@code {
private INS_AuditLogger auditLogger = default!;
// Khi INSERT
await auditLogger.LogInsertAsync("TableName", "rowId", newObject);
// Khi UPDATE
await auditLogger.LogUpdateAsync("TableName", "rowId", oldObject, newObject);
// Khi DELETE
await auditLogger.LogDeleteAsync("TableName", "rowId", oldObject);
// Custom operation
await auditLogger.LogCustomAsync("TableName", "APPROVE", "rowId", oldObj, newObj);
}
```
**Nội bộ hoạt động**:
1. `OnInitializedAsync()` → Lấy `UserId``Username` từ `AuthenticationStateProvider`
2. Serialize old/new data thành JSON bằng `System.Text.Json.JsonSerializer`
3. Gọi `LogService.AuditAsync()` với các tham số
**Dependencies**: `LogService`, `AuthenticationStateProvider`
**DI Registration**:
```csharp
services.AddScoped<INS_AuditLogger>();
```
---
### 2.2. LogService (HTTP Client - 3-tier Bridge)
> **Location**: `lib/INS.DXComponents/Logging/LogService.cs`
**Chức năng**: Bridge giữa Frontend và Module Backend, gửi log qua HTTP POST.
**Flow**: Frontend → `LogService` → HTTP POST → Module Controller → gRPC → INS.Server
```csharp
public class LogService
{
private readonly IHttpClientFactory _httpClientFactory;
// Sử dụng "AuthenticatedClient" - tự động gắn Access Token
private HttpClient CreateClient() => _httpClientFactory.CreateClient("AuthenticatedClient");
public async Task<bool> AuditAsync(
string tableName,
string operation,
string? rowId = null,
string? oldValue = null,
string? newValue = null,
string? userId = null,
string? username = null)
{
var dto = new RabbitMQAuditLogDto
{
TableName = tableName,
RowId = rowId ?? string.Empty,
Operation = operation,
OldData = oldValue,
NewData = newValue,
ChangedBy = username ?? userId ?? "System",
Timestamp = DateTime.UtcNow
};
using var client = CreateClient();
var response = await client.PostAsJsonAsync("/api/logging/audit", dto);
return response.IsSuccessStatusCode;
}
}
```
**Đặc điểm quan trọng**:
- Sử dụng `IHttpClientFactory` với named client `"AuthenticatedClient"` (tự gắn JWT token)
- Swallow exceptions (return `false`) → Log failure KHÔNG break UI flow
- POST tới `/api/logging/audit` trên Module Backend
**DI Registration**:
```csharp
// Extension method
services.AddINSLogging(); // → AddScoped<LogService>()
```
---
### 2.3. gRPC Proto Definition
> **Location**: `INS.WASM.AUTH/src/INS.Backend/Protos/logging.proto`
```protobuf
syntax = "proto3";
option csharp_namespace = "INS.LoggingController";
import "google/protobuf/timestamp.proto";
package logging;
service LoggingService {
rpc SendAuditLog (AuditLogRequest) returns (LogResponse);
rpc SendAppLog (AppLogRequest) returns (LogResponse);
rpc SendActivityLog (ActivityLogRequest) returns (LogResponse);
}
message AuditLogRequest {
string table_name = 1;
string row_id = 2;
string operation = 3; // INSERT, UPDATE, DELETE
string old_data = 4; // JSON
string new_data = 5; // JSON
string changed_by = 6;
google.protobuf.Timestamp timestamp = 7;
string user_id = 8;
repeated string roles = 9;
}
message LogResponse {
bool success = 1;
string message = 2;
}
```
**NuGet package**: `INS.LoggingController` (v1.0.4) - chứa generated gRPC client/server code
---
### 2.4. LoggingGrpcService (gRPC → RabbitMQ Bridge)
> **Location**: `INS.WASM.AUTH/src/INS.Backend/Services/Grpc/LoggingGrpcService.cs`
**Chức năng**: Nhận gRPC call từ modules, chuyển đổi sang `RabbitMQAuditLogDto`, publish lên RabbitMQ.
```csharp
public class LoggingGrpcService : LoggingService.LoggingServiceBase
{
private readonly IRabbitMQService _rabbitMQService;
public override async Task<LogResponse> SendAuditLog(
AuditLogRequest request, ServerCallContext context)
{
// Map Protobuf → DTO
var dto = new RabbitMQAuditLogDto
{
TableName = request.TableName,
RowId = request.RowId,
Operation = request.Operation,
OldData = request.OldData,
NewData = request.NewData,
ChangedBy = request.ChangedBy,
Timestamp = request.Timestamp.ToDateTime()
};
// Publish tới RabbitMQ
await _rabbitMQService.SendAsync(
exchangeName: "ins.logs.exchange",
routingKey: "logs.audit.server",
message: dto
);
return new LogResponse { Success = true, Message = "Audit log sent successfully" };
}
}
```
**Routing Key**: `logs.audit.server` (fixed)
**Exchange**: `ins.logs.exchange` (topic exchange)
---
### 2.5. AuditLogPublisher (Direct Publisher)
> **Location**: `INS.WASM.AUTH/src/INS.Backend/Services/AuditLogPublisher.cs`
**Chức năng**: Publisher trực tiếp cho backend code, tự động enrich message với request context và generate rollback SQL.
**Interface**:
```csharp
public interface IAuditLogPublisher
{
// Simplified API - auto-enrich từ request context
Task PublishAsync(
string schemaName,
string tableName,
string rowId,
string operation, // INSERT, UPDATE, DELETE
string? oldData, // JSON
string? newData, // JSON
string? primaryKeyColumns = null, // JSON array: ["Id"] hoặc ["Code", "Version"]
string? functionName = null);
// Full control API
Task PublishAsync(AuditLogMessage message);
}
```
**Luồng xử lý chi tiết**:
```
PublishAsync(params)
├── 1. Generate Rollback SQL
│ └── IRollbackSqlGenerator.GenerateRollbackSql()
├── 2. Detect Changed Columns (nếu UPDATE)
│ └── IRollbackSqlGenerator.GetChangedColumns(oldData, newData)
├── 3. Build AuditLogMessage (enrich từ IRequestContextAccessor)
│ ├── WHO: UserId, Username, IpAddress, SessionId, MachineName
│ ├── WHAT: Schema, Table, RowId, Operation, OldData, NewData, ChangedColumns
│ ├── WHEN: Timestamp (UtcNow)
│ ├── HOW TO ROLLBACK: RollbackSql
│ └── WHERE FROM: ModuleId, CorrelationId, ParentCorrelationId, RequestUrl, HttpMethod
├── 4. Kiểm tra Enabled
│ └── RabbitMQ:Enabled && RabbitMQ:AuditLog:Enabled
└── 5. Publish lên RabbitMQ
└── IRabbitMQService.SendAsync(exchangeName, routingKey, message)
```
**Configuration** (appsettings.json):
```json
{
"RabbitMQ": {
"Enabled": true,
"AuditLog": {
"Enabled": true,
"ExchangeName": "ins.logs.exchange",
"PublishRoutingKey": "logs.audit.data"
}
},
"ModuleRegistration": {
"ModuleId": "INS.Server"
}
}
```
**Dependencies**:
| Service | Lifetime | Chức năng |
|---------|----------|-----------|
| `IRabbitMQService?` | Singleton | Publish message (optional - nullable) |
| `IRequestContextAccessor` | Scoped | Lấy context từ HTTP request hiện tại |
| `IRollbackSqlGenerator` | Singleton | Generate rollback SQL + detect changed columns |
| `IConfiguration` | Singleton | Đọc config |
**DI Registration**:
```csharp
builder.Services.AddScoped<IRequestContextAccessor, RequestContextAccessor>();
builder.Services.AddSingleton<IRollbackSqlGenerator, RollbackSqlGenerator>();
builder.Services.AddScoped<IAuditLogPublisher, AuditLogPublisher>();
```
**Error handling**: Swallow exceptions - audit log failure KHÔNG break business flow:
```csharp
catch (Exception ex)
{
_logger.LogError(ex, "Failed to publish AuditLog for {Table}/{Operation}", ...);
// Don't throw - audit log failure shouldn't break the main flow
}
```
---
### 2.6. CorrelationIdMiddleware (Distributed Tracing)
> **Location**: `INS.WASM.AUTH/src/INS.Backend/Middleware/CorrelationIdMiddleware.cs`
**Chức năng**: Inject/generate CorrelationId cho mỗi HTTP request, cho phép trace toàn bộ luồng từ request → audit log → database.
**HTTP Headers sử dụng**:
| Header | Mục đích | Auto-generate? |
|--------|---------|----------------|
| `X-Correlation-ID` | Unique ID cho request | ✅ Guid.NewGuid() nếu không có |
| `X-Parent-Correlation-ID` | ID của request cha (chained calls) | ❌ |
| `X-Trace-Span-ID` | Span ID cho distributed tracing | ✅ 16-char substring |
| `X-Module-ID` | Module nào gọi request | ❌ |
| `X-Sequence-Number` | Thứ tự trong chain | ❌ |
**Luồng xử lý**:
1. Đọc/generate `CorrelationId` từ request header
2. Populate `IRequestContextAccessor` (scoped service)
3. Store vào `HttpContext.Items`
4. Gắn `CorrelationId` vào response header
5. Gọi `_next(context)` → pipeline tiếp tục
**Middleware registration order**:
```csharp
app.UseCorrelationId(); // ← PHẢI trước các middleware khác
app.UseActivityLogging(); // Activity log middleware
// ... other middleware
```
---
### 2.7. IRequestContextAccessor (Scoped Context)
> **Location**: `INS.WASM.AUTH/src/INS.Backend/Middleware/IRequestContextAccessor.cs`
**Chức năng**: Scoped service chứa thông tin request hiện tại, được populate bởi `CorrelationIdMiddleware``ActivityLoggingMiddleware`.
```csharp
public interface IRequestContextAccessor
{
// Tracing
string? CorrelationId { get; set; }
string? ParentCorrelationId { get; set; }
string? TraceSpanId { get; set; }
string? ModuleId { get; set; }
int SequenceNumber { get; set; }
// Request
string? RequestUrl { get; set; }
string? HttpMethod { get; set; }
string? RequestBody { get; set; }
string? ResponseBody { get; set; }
string? IpAddress { get; set; }
string? UserAgent { get; set; }
// User
string? UserId { get; set; }
string? Username { get; set; }
string? SessionId { get; set; }
// Timing
DateTime RequestStartTime { get; set; }
long GetDurationMs();
// System
string? GetSystemState();
}
```
**Lifetime**: `Scoped` → mỗi HTTP request có một instance riêng, đảm bảo data isolation.
---
### 2.8. RollbackSqlGenerator
> **Location**: `INS.WASM.AUTH/src/INS.Backend/Services/RollbackSqlGenerator.cs`
**Chức năng**: Tự động generate SQL để rollback thay đổi dựa trên operation type.
**Logic**:
| Operation | Rollback SQL | Input cần |
|-----------|-------------|-----------|
| `INSERT` | `DELETE FROM [schema].[table] WHERE [pk] = [value]` | PK columns + newData |
| `UPDATE` | `UPDATE [schema].[table] SET [cols] = [old_values] WHERE [pk] = [value]` | PK columns + oldData |
| `DELETE` | `INSERT INTO [schema].[table] ([cols]) VALUES ([old_values])` | oldData |
**GetChangedColumns()**: So sánh oldData và newData (JSON), trả về danh sách columns đã thay đổi.
**WHERE clause strategy** (ưu tiên):
1. Parse `rowId` as JSON object → `{"Id": 123, "Code": "ABC"}``[Id] = 123 AND [Code] = N'ABC'`
2. Dùng `primaryKeyColumns` + data → extract PK values từ data
3. Fallback: `[Id] = rowId` (int/Guid/string detection)
**Value formatting**:
- `null``NULL`
- `true/false``1/0`
- number → literal
- string → `N'escaped_value'` (SQL injection protection via `'``''`)
---
### 2.9. Shared DTO: `RabbitMQAuditLogDto`
> **Location**: `lib/INS.Shared/Models/RabbitMQLogDtos.cs`
```csharp
public class RabbitMQAuditLogDto
{
public string TableName { get; set; } = string.Empty;
public string RowId { get; set; } = string.Empty;
public string Operation { get; set; } = string.Empty; // INSERT, UPDATE, DELETE
public string? OldData { get; set; } // JSON
public string? NewData { get; set; } // JSON
public string? ChangedBy { get; set; }
public string? DeviceName { get; set; }
public DateTime? Timestamp { get; set; }
public string? CorrelationId { get; set; }
}
```
> [!IMPORTANT]
> `RabbitMQAuditLogDto` (lightweight, dùng cho gRPC path) khác với `AuditLogMessage` (full, dùng cho AuditLogPublisher). Consumer phải handle cả 2 format.
---
### 2.10. AuditLogMessage (Full Message Model)
> **Location**: `INS.Backend/Models/RabbitMQLogModels.cs` (cả INS.Server và INS.WASM.AUTH)
```csharp
public class AuditLogMessage
{
// WHO
public string? UserId { get; set; }
public string? ChangedBy { get; set; }
public string? IpAddress { get; set; }
public string? SessionId { get; set; }
public string? DeviceName { get; set; }
// WHAT
public string SchemaName { get; set; }
public string TableName { get; set; }
public string RowId { get; set; }
public string? PrimaryKeyColumns { get; set; }
public string Operation { get; set; } // INSERT, UPDATE, DELETE
public string? OldData { get; set; } // JSON
public string? NewData { get; set; } // JSON
public string? ChangedColumns { get; set; }
// WHEN
public DateTime? Timestamp { get; set; }
// HOW TO ROLLBACK
public string? RollbackSql { get; set; }
// TRACING
public string? ModuleId { get; set; }
public string? CorrelationId { get; set; }
public string? ParentCorrelationId { get; set; }
public string? RequestUrl { get; set; }
public string? HttpMethod { get; set; }
public string? RequestBody { get; set; }
public string? FunctionName { get; set; }
}
```
---
### 2.11. RabbitMQ Configuration
**Exchange**: `ins.logs.exchange` (type: `topic`)
**Routing Keys**:
| Publisher | Routing Key | Mô tả |
|-----------|-------------|--------|
| `LoggingGrpcService.SendAuditLog()` | `logs.audit.server` | Từ module gRPC |
| `AuditLogPublisher.PublishAsync()` | `logs.audit.data` | Từ backend direct call |
| `ActivityLoggingMiddleware` | `logs.activity` | Auto HTTP tracking |
**Queue Binding**: `ins.server.auditlog.queue` bind với pattern `logs.audit.#` → match cả `logs.audit.server``logs.audit.data`
**Core Library**: `INS.RabbitMQ` (v1.2.1, RabbitMQ.Client 7.2.0)
- Interface: `IRabbitMQService`
- Methods: `SendAsync(exchange, routingKey, message)`, `CreateConsumer<T>()`
---
### 2.12. BaseRabbitMqConsumerService (Consumer Infrastructure)
> **Location**: `INS.WASM.AUTH/src/INS.Backend/Services/Consumers/BaseRabbitMqConsumerService.cs`
**Chức năng**: Abstract base class cho tất cả consumer, kế thừa `BackgroundService`.
**Đặc điểm kiến trúc**:
- **Lazy initialization**: Không connect RabbitMQ tại startup, đợi 5s rồi mới connect
- **Auto-retry**: Nếu connect fail, retry mỗi `RetryIntervalSeconds` (default: 30s)
- **Fluent API**: Sử dụng `IRabbitMQService.CreateConsumer<T>()` fluent builder
```csharp
public abstract class BaseRabbitMqConsumerService<TMessage> : BackgroundService
where TMessage : class
{
protected readonly string QueueName;
protected readonly string ExchangeName;
protected readonly string RoutingKey;
protected readonly ushort PrefetchCount; // default: 10
protected readonly bool Enabled;
protected readonly int RetryIntervalSeconds; // default: 30
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (!Enabled) return;
await Task.Delay(5_000); // Đợi app khởi động
while (!stoppingToken.IsCancellationRequested)
{
if (!_isConnected)
{
await TryConnectAsync(stoppingToken);
}
if (!_isConnected)
{
await Task.Delay(RetryIntervalSeconds * 1000);
}
else
{
await Task.Delay(60_000); // Health check mỗi 1 phút
}
}
}
private async Task TryConnectAsync(CancellationToken ct)
{
// Fluent API registration
await RabbitMQService.CreateConsumer<TMessage>()
.WithQueue(QueueName)
.WithExchange(ExchangeName, RoutingKey)
.WithPrefetchCount(PrefetchCount)
.WithHandler(async message => await ProcessMessageAsync(message))
.RegisterAsync();
}
// Subclass implement: return true = ACK, false = NACK (requeue)
protected abstract Task<bool> ProcessMessageAsync(TMessage message);
}
```
**Config từ appsettings** (section name truyền qua constructor):
```json
{
"RabbitMQ": {
"Enabled": true,
"RetryIntervalSeconds": 30,
"AuditLog": {
"Enabled": true,
"QueueName": "ins.server.auditlog.queue",
"ExchangeName": "ins.logs.exchange",
"RoutingKey": "logs.audit.#",
"PrefetchCount": 10
}
}
}
```
---
### 2.13. AuditLogConsumerService (Write to DB)
> **Location**: `INS.WASM.AUTH/src/INS.Backend/Services/Consumers/AuditLogConsumerService.cs`
**Chức năng**: Consume `AuditLogMessage` từ RabbitMQ và INSERT vào `[Log].[audit_log]` table.
```csharp
public class AuditLogConsumerService : BaseRabbitMqConsumerService<AuditLogMessage>
{
public AuditLogConsumerService(
IServiceProvider serviceProvider,
IConfiguration configuration,
ILogger<AuditLogConsumerService> logger)
: base(serviceProvider, configuration, logger, "AuditLog") // config section
{ }
protected override async Task<bool> ProcessMessageAsync(AuditLogMessage message)
{
// 1. Validate required fields
if (string.IsNullOrEmpty(message.TableName) ||
string.IsNullOrEmpty(message.RowId) ||
string.IsNullOrEmpty(message.Operation))
{
return true; // ACK invalid message (skip, không requeue)
}
// 2. Tạo scope mới cho DbContext (scoped service)
using var scope = ServiceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AuthDbContext>();
// 3. Map message → Entity
var auditLog = new AuditLog
{
// WHO
UserId = message.UserId,
ChangedBy = message.ChangedBy,
DeviceName = message.DeviceName,
IpAddress = message.IpAddress,
SessionId = message.SessionId,
// WHAT
SchemaName = message.SchemaName,
TableName = message.TableName,
RowId = message.RowId,
PrimaryKeyColumns = message.PrimaryKeyColumns,
Operation = message.Operation,
OldData = message.OldData,
NewData = message.NewData,
ChangedColumns = message.ChangedColumns,
// WHEN
ChangedAt = message.Timestamp ?? DateTime.UtcNow,
// HOW TO ROLLBACK
RollbackSql = message.RollbackSql,
RollbackStatus = "NotRolledBack",
// WHERE FROM
ModuleId = message.ModuleId,
CorrelationId = message.CorrelationId,
ParentCorrelationId = message.ParentCorrelationId,
RequestUrl = message.RequestUrl,
HttpMethod = message.HttpMethod,
RequestBody = message.RequestBody,
FunctionName = message.FunctionName
};
// 4. Insert vào database
dbContext.AuditLogs.Add(auditLog);
await dbContext.SaveChangesAsync();
return true; // ACK
}
}
```
**ACK/NACK strategy**:
- `return true` (ACK) → Message processed, remove from queue
- `return true` cho invalid message → Skip, không requeue vĩnh viễn
- `return false` (NACK) → Exception xảy ra, requeue message
**DI**: Consumer là `BackgroundService`, registered as `HostedService`:
```csharp
builder.Services.AddHostedService<AuditLogConsumerService>();
```
---
### 2.14. Database Entity: `AuditLog`
> **Location**: `INS.WASM.AUTH/src/INS.Backend/Data/LogModels/AuditLog.cs`
**Table**: `[log].[audit_log]`
```csharp
[Table("audit_log", Schema = "log")]
public class AuditLog
{
[Key]
[Column("audit_id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long AuditId { get; set; }
// WHO
[Column("user_id"), MaxLength(255)]
public string? UserId { get; set; }
[Column("changed_by"), MaxLength(255)]
public string? ChangedBy { get; set; }
[Column("device_name"), MaxLength(255)]
public string? DeviceName { get; set; }
[Column("ip_address"), MaxLength(45)]
public string? IpAddress { get; set; }
[Column("session_id"), MaxLength(255)]
public string? SessionId { get; set; }
// WHAT
[Column("schema_name"), MaxLength(128)]
public string? SchemaName { get; set; }
[Required, Column("table_name"), MaxLength(255)]
public string TableName { get; set; }
[Required, Column("row_id")]
public string RowId { get; set; }
[Column("primary_key_columns"), MaxLength(500)]
public string? PrimaryKeyColumns { get; set; }
[Required, Column("operation"), MaxLength(10)]
public string Operation { get; set; }
[Column("old_data")]
public string? OldData { get; set; }
[Column("new_data")]
public string? NewData { get; set; }
[Column("changed_columns")]
public string? ChangedColumns { get; set; }
// WHEN
[Required, Column("changed_at")]
public DateTime ChangedAt { get; set; }
// HOW TO ROLLBACK
[Column("rollback_sql")]
public string? RollbackSql { get; set; }
[Column("rollback_status"), MaxLength(20)]
public string? RollbackStatus { get; set; } // NotRolledBack, SUCCESS, FAILED
[Column("rollback_at")]
public DateTime? RollbackAt { get; set; }
[Column("rollback_by"), MaxLength(255)]
public string? RollbackBy { get; set; }
// WHERE FROM
[Column("module_id"), MaxLength(100)]
public string? ModuleId { get; set; }
[Column("correlation_id"), MaxLength(255)]
public string? CorrelationId { get; set; }
[Column("parent_correlation_id"), MaxLength(255)]
public string? ParentCorrelationId { get; set; }
[Column("request_url"), MaxLength(2048)]
public string? RequestUrl { get; set; }
[Column("http_method"), MaxLength(10)]
public string? HttpMethod { get; set; }
[Column("request_body")]
public string? RequestBody { get; set; }
[Column("function_name"), MaxLength(255)]
public string? FunctionName { get; set; }
}
```
**Database Indexes** (từ migration SQL):
```sql
CREATE INDEX [idx_audit_log_changed_at] ON [Log].[AuditLogs] ([changed_at]);
CREATE INDEX [idx_audit_log_changed_by] ON [Log].[AuditLogs] ([changed_by]);
CREATE INDEX [idx_audit_log_correlation_id] ON [Log].[AuditLogs] ([correlation_id]);
CREATE INDEX [idx_audit_log_table_name] ON [Log].[AuditLogs] ([table_name]);
CREATE INDEX [idx_audit_log_table_time] ON [Log].[AuditLogs] ([table_name], [changed_at]);
```
---
## 3. Full Flow: Từ UI Click đến Database
### Flow 1: Frontend Blazor gọi audit (3-tier path)
```
1. User click "Save" trên Blazor page
2. Page code gọi: await auditLogger.LogUpdateAsync("Users", "123", oldUser, newUser)
3. INS_AuditLogger:
├── Serialize oldUser/newUser → JSON
└── Gọi LogService.AuditAsync(tableName, operation, rowId, oldJson, newJson, userId, username)
4. LogService:
├── Tạo RabbitMQAuditLogDto
└── POST /api/logging/audit → Module Backend Controller
5. Module Backend Controller:
├── Nhận DTO
└── Gọi gRPC SendAuditLog() tới INS.WASM.AUTH
6. LoggingGrpcService (INS.WASM.AUTH):
├── Map AuditLogRequest → RabbitMQAuditLogDto
└── IRabbitMQService.SendAsync("ins.logs.exchange", "logs.audit.server", dto)
7. RabbitMQ:
├── Nhận message tại exchange "ins.logs.exchange"
└── Route theo key "logs.audit.server" → match "logs.audit.#" → queue "ins.server.auditlog.queue"
8. AuditLogConsumerService (BackgroundService):
├── Consume message từ queue
├── Validate required fields
├── Map AuditLogMessage → AuditLog entity
└── dbContext.AuditLogs.Add() + SaveChangesAsync()
9. SQL Server: INSERT INTO [log].[audit_log] (...)
```
### Flow 2: Backend direct publish (enriched path)
```
1. Backend API handler thực hiện data change
2. Code gọi: await _auditLogPublisher.PublishAsync("dbo", "Users", "123", "UPDATE", oldJson, newJson, "[\"Id\"]", "UpdateUser")
3. AuditLogPublisher:
├── Generate rollback SQL: UPDATE [dbo].[Users] SET [...] WHERE [Id] = 123
├── Detect changed columns: ["Name", "Email"]
├── Enrich từ IRequestContextAccessor:
│ ├── UserId, Username, IpAddress, SessionId
│ ├── CorrelationId (từ CorrelationIdMiddleware)
│ ├── RequestUrl, HttpMethod
│ └── ModuleId
└── IRabbitMQService.SendAsync("ins.logs.exchange", "logs.audit.data", message)
4. RabbitMQ → Queue → Consumer → Database (giống Flow 1 step 7-9)
```
---
## 4. Module Extension Pattern (CDE Example)
CDE module có hệ thống audit log **riêng biệt** (domain-specific), KHÁC với system-level audit log ở trên:
### CDEAuditController
> **Location**: `INS.CDE/src/INS.CDE.Backend/Controllers/CDE/CDEAuditController.cs`
- **Table**: CDE có bảng `CDEActivityLogs` riêng (không dùng chung `[Log].[audit_log]`)
- **Entity fields**: `ProjectId`, `DocumentId`, `EntityType`, `EntityId`, `EntityCode`, `Action`, `Details`, `IPAddress`, `UserAgent`
- **Endpoints**:
- `GET /api/cde/audit/logs` - Lấy logs với filters (project, document, user, action, date range)
- `GET /api/cde/audit/documents/{id}/logs` - Logs theo document
- `GET /api/cde/audit/stats` - Statistics cho dashboard
### Module Config
```csharp
// ModuleOptions.cs
public bool EnableAuditLogging { get; set; } = true;
```
> [!NOTE]
> Modules có thể: (1) Sử dụng system-level audit log qua `LogService`/`INS_AuditLogger` → ghi vào `[Log].[audit_log]`, HOẶC (2) Có bảng audit log riêng (như CDE) cho domain-specific tracking.
---
## 5. DI Registration Summary
### INS.WASM.AUTH/Program.cs
```csharp
// Infrastructure
builder.Services.AddScoped<IRequestContextAccessor, RequestContextAccessor>();
builder.Services.AddSingleton<IRollbackSqlGenerator, RollbackSqlGenerator>();
// Publisher
builder.Services.AddScoped<IAuditLogPublisher, AuditLogPublisher>();
// Consumers (BackgroundServices)
builder.Services.AddHostedService<AuditLogConsumerService>();
builder.Services.AddHostedService<AppLogConsumerService>();
builder.Services.AddHostedService<ActivityLogConsumerService>();
```
### Middleware Pipeline Order
```csharp
app.UseCorrelationId(); // 1. Generate/inject CorrelationId
app.UseActivityLogging(); // 2. Auto-capture HTTP → Activity Log
// ... Authentication, Authorization
// ... Controllers, gRPC endpoints
```
### Frontend (Blazor WASM)
```csharp
services.AddINSLogging(); // → AddScoped<LogService>()
services.AddScoped<INS_AuditLogger>();
```
---
## 6. Checklist để tái tạo chức năng
- [ ] **Shared Models**: Tạo `RabbitMQAuditLogDto``AuditLogMessage`
- [ ] **Proto file**: Định nghĩa `logging.proto` với `AuditLogRequest` message
- [ ] **gRPC Service**: Implement `LoggingGrpcService` nhận request, map → DTO, publish RabbitMQ
- [ ] **RabbitMQ Core**: Setup `IRabbitMQService` với `SendAsync()``CreateConsumer<T>()` fluent API
- [ ] **CorrelationIdMiddleware**: Inject/generate `X-Correlation-ID` mỗi request
- [ ] **IRequestContextAccessor**: Scoped service chứa request context (user, IP, correlation, URL)
- [ ] **RollbackSqlGenerator**: Generate rollback SQL dựa trên operation type
- [ ] **AuditLogPublisher**: Publisher enriches message từ context, generate rollback, publish RabbitMQ
- [ ] **BaseRabbitMqConsumerService**: Abstract BackgroundService với lazy init + retry logic
- [ ] **AuditLogConsumerService**: Consume message → validate → map → EF Core INSERT
- [ ] **AuditLog Entity**: EF Core entity map tới `[log].[audit_log]` table
- [ ] **Database Schema**: Create table với indexes trên `changed_at`, `changed_by`, `correlation_id`, `table_name`
- [ ] **LogService**: Frontend HTTP client bridge (POST → Module Controller → gRPC)
- [ ] **INS_AuditLogger**: Blazor component wrapper (serialize old/new → LogService)
- [ ] **DI Registration**: Register tất cả services với correct lifetimes
- [ ] **Configuration**: appsettings.json cho RabbitMQ exchange/queue/routing keys
@@ -1,69 +0,0 @@
# Disk Cleanup Workflow Knowledge
## Overview
This document serves as the AI knowledge base for the `Disk_Cleanup_Workflow.bat` script. Its purpose is to allow any AI assistant in the future to understand the developer-centric disk cleanup logic, explain it to the user, or fully reconstruct the batch script if it is ever lost or needs to be ported.
## Context & Problem
Developer environments (particularly those running .NET, Node.js, and Docker) accumulate massive amounts of cached files. Over time, this leads to the `C:\` drive becoming critically full (e.g., `< 100MB` remaining), causing system unresponsiveness. Standard Windows Disk Cleanup does not empty backend developer caches.
## Target Areas for Cleanup
This workflow specifically targets:
1. **NuGet Cache:** Purges `.NET` local packages, which can take tens of GBs.
2. **NPM Cache:** Force cleans `Node.js` caches.
3. **User Temp Directory:** Empties `%TEMP%`.
4. **Windows Temp Directory:** Empties `C:\Windows\Temp`.
5. **Browser Caches:** Empties Google Chrome and Microsoft Edge user data caches to free up space from extensive web usage.
6. **Recycle Bin:** Empties the Windows Recycle Bin using a silent PowerShell command.
7. **Docker Images/Volumes:** Removes **ALL** unused containers, networks, dangling images, and unreferenced images (`-a` flag ensures full cleanup of old images).
## Batch Script Source Code
```bat
@echo off
chcp 65001 >nul
title Disk Cleanup Workflow (Developer Edition)
echo ======================================================================
echo WORKFLOW DỌN DẸP Ổ ĐĨA TỰ ĐỘNG - DÀNH CHO LẬP TRÌNH VIÊN
echo ======================================================================
echo.
echo [1/7] Dang xoa NuGet Cache (.NET)...
dotnet nuget locals all --clear
echo.
echo [2/7] Dang xoa NPM Cache (Node.js)...
call npm cache clean --force
echo.
echo [3/7] Dang don dep thu muc Temp cua User...
rmdir /s /q "%TEMP%" >nul 2>&1
mkdir "%TEMP%" >nul 2>&1
echo.
echo [4/7] Dang don dep thu muc Temp cua Windows (Can Run As Administrator de xoa het)...
rmdir /s /q "C:\Windows\Temp" >nul 2>&1
mkdir "C:\Windows\Temp" >nul 2>&1
echo.
echo [5/7] Dang don dep Cache trinh duyet (Chrome / Edge)...
rmdir /s /q "%LOCALAPPDATA%\Google\Chrome\User Data\Default\Cache" >nul 2>&1
rmdir /s /q "%LOCALAPPDATA%\Google\Chrome\User Data\Default\Code Cache" >nul 2>&1
rmdir /s /q "%LOCALAPPDATA%\Microsoft\Edge\User Data\Default\Cache" >nul 2>&1
rmdir /s /q "%LOCALAPPDATA%\Microsoft\Edge\User Data\Default\Code Cache" >nul 2>&1
echo.
echo [6/7] Dang lam trong Thung rac (Recycle Bin)...
PowerShell.exe -NoProfile -Command "Clear-RecycleBin -Force -ErrorAction SilentlyContinue"
echo.
echo [7/7] Dang don dep lich su Docker (Xoa TOAN BO image, container, volume cu)...
echo (Bo qua tuy chon nay neu ban khong dung Docker)
docker system prune -a -f >nul 2>&1
echo.
echo ======================================================================
echo HOÀN TẤT DỌN DẸP!
echo ======================================================================
echo Phân vùng ổ C của bạn đã được giải phóng dung lượng.
echo Bấm phím bất kỳ để thoát...
pause >nul
```
-123
View File
@@ -1,123 +0,0 @@
---
description: Audit all features on a page/module like a picky user. Find and fix form issues, API silent failures, missing fields, hardcoded values.
---
# /audit - Feature Audit & Fix
Quy trình kiểm tra tính năng toàn diện cho một page/module, đóng vai user khó tính.
---
## Phase 1: IDENTIFY — Xác định scope
1. **Xác định page/module cần audit** từ user request
2. **Liệt kê tất cả files liên quan:**
- Frontend page (`.razor`)
- Frontend service (`HrmApiService.cs` hoặc tương đương)
- Backend controller
- Backend service
- DTOs / Entities / Enums
3. **Đọc toàn bộ code** của các files trên
---
## Phase 2: AUDIT — Kiểm tra từng chức năng
Với MỖI form/popup/grid trên page, kiểm tra theo checklist:
### 2a. Form Fields — Trường nhập liệu
- [ ] So sánh form fields với DTO/Entity → **Có trường nào trong DTO mà form không hiển thị?**
- [ ] Kiểm tra có giá trị **hardcoded** trong handler thay vì cho user nhập? (e.g., `DayType.Normal`, `IsNightShift = false`, `ShiftCodeId = 1`)
- [ ] Mọi trường bắt buộc có dấu `*` (required indicator)?
- [ ] Các trường time dùng `DxTimeEdit` thay vì `DxTextBox`?
- [ ] Các trường date dùng `DxDateEdit` thay vì `DxTextBox`?
- [ ] Các trường enum/lookup dùng `DxComboBox` với data source thay vì `DxTextBox`?
- [ ] **FK fields (xxxId) PHẢI dùng `DxComboBox` hiện tên/mã từ DB, KHÔNG dùng `DxSpinEdit` nhập số ID thủ công**
- Tìm: `DxSpinEdit.*Id` → phải đổi sang `DxComboBox` với data load từ API
- Label phải hiện tên entity ("Phòng ban", "Ca làm việc"), KHÔNG hiện "Phòng ban ID"
- ComboBox cần có `NullText`, `FilteringMode`, `ClearButtonDisplayMode`
- [ ] Checkbox cho boolean fields?
### 2b. Validation — Kiểm tra ràng buộc
- [ ] Required fields có validation trước khi submit?
- [ ] Date range: ToDate >= FromDate?
- [ ] Time range: ToTime > FromTime (trừ ca đêm)?
- [ ] Số lượng/giá trị > 0 khi cần?
- [ ] Có hiện Toast warning với message cụ thể khi validation fail?
### 2c. API Error Handling — Xử lý lỗi API
- [ ] Mọi POST/PUT/DELETE API call có kiểm tra `resp.IsSuccessStatusCode`?
- [ ] Khi API fail, có **throw exception** (KHÔNG return default value im lặng)?
- [ ] Error message có đọc `resp.Content` để hiển thị chi tiết?
- [ ] Frontend handler có `try/catch` bao bọc API call?
- [ ] Catch block có hiện `Toast.Error()` với `ex.Message`?
### 2d. Popup / Form Reset
- [ ] `ShowAddXxx()` reset TẤT CẢ field về default (không chỉ một vài)?
- [ ] Date fields reset về `DateTime.Today` (không để giá trị cũ)?
- [ ] Nullable fields reset về `null`?
- [ ] String fields reset về `""`?
### 2e. Grid Display — Hiển thị dữ liệu
- [ ] Grid columns khớp với DTO properties cần hiển thị?
- [ ] Enum columns hiển thị text thay vì số (e.g., "Phép năm" thay vì "0")?
- [ ] Date/Time columns format đúng?
- [ ] Có column thừa hoặc thiếu?
### 2f. Data Integrity — Tính toàn vẹn dữ liệu
- [ ] Default values trong entity có hợp lý? (e.g., DateOnly default = 0001-01-01 → cần set mặc định)
- [ ] Foreign key relationships có valid? (e.g., EmployeeId phải tồn tại)
- [ ] Seed data có đủ cho tất cả dropdowns?
---
## Phase 3: REPORT — Báo cáo audit
Tạo danh sách issues theo severity:
```markdown
### 🔴 Critical — Silent failures, data loss
### 🟡 Medium — Missing fields, hardcoded values
### 🟢 Low — UI polish, format
```
---
## Phase 4: FIX — Sửa batch
// turbo-all
1. **Sửa tất cả issues trong batch** — ưu tiên Critical → Medium → Low
2. **Build:**
```powershell
dotnet build INS.HRM.slnx
```
3. **Test:**
```powershell
dotnet test src/INS.HRM.Backend/INS.HRM.Backend.csproj
```
4. **Start server:**
```powershell
dotnet run --project src/INS.HRM.Backend
```
---
## Phase 5: VERIFY — Xác nhận
5. Tóm tắt kết quả cho user:
- Số issues tìm được
- Số issues đã sửa
- Build status
- Test status
---
## Examples
```
/audit trang chấm công
/audit module nhân viên
/audit tất cả các form trên trang tổ chức
/audit page recruitment
```
-249
View File
@@ -1,249 +0,0 @@
---
description: Backup all workspaces by compressing to ZIP and uploading to Google Drive (G:\My Drive\Code_Backups)
---
# /backup — Workspace Backup Workflow
Nén toàn bộ workspace thành file ZIP có timestamp, sau đó tự động copy lên Google Drive.
## Tính năng nổi bật (v3 — Dynamic Workspaces)
-**Nhận workspace động** — truyền đường dẫn qua tham số, không hardcode
-**Tự động phát hiện** ổ đĩa Google Drive (không hardcode ký tự ổ đĩa)
-**Loại trừ rác** (`bin/`, `obj/`, `.vs/`, `node_modules/`, `.git/`) → file ZIP nhỏ gọn
-**Ghi log** đầy đủ tại `C:\Code_Backups\backup_log.txt`
- ✅ Fallback: nếu máy không có Google Drive, file ZIP vẫn được giữ lại ở local
## Cách hoạt động
Script `backup_workspaces.ps1` nhận tham số `-WorkspacePaths` là mảng các đường dẫn.
Workflow này yêu cầu AI **tự động đọc danh sách workspace đang mở trong VS Code** từ user metadata,
sau đó truyền vào script — không cần chỉnh sửa gì khi copy sang workspace khác.
## Bước thực hiện
// turbo-all
### Bước 1 — Backup tất cả workspace hiện tại
> **[AI INSTRUCTION]**: Đọc danh sách workspace đang mở từ `user_information.workspaces` (các URI được liệt kê ở đầu conversation).
> Lấy phần URI (trước `->`), loại bỏ duplicate, build thành mảng PowerShell `$workspaces` rồi chạy script bên dưới.
> Đường dẫn script: `{workspace_root}\.agent\scripts\backup_workspaces.ps1` — thay `{workspace_root}` bằng thư mục workspace **hiện tại đang active**.
> Không hardcode bất kỳ path nào — luôn lấy từ metadata.
```powershell
# AI sẽ tự điền $workspaces và $script dựa trên workspace hiện tại
$script = "{active_workspace}\.agent\scripts\backup_workspaces.ps1"
$workspaces = @(
# <-- AI tự điền danh sách workspace từ VS Code metadata -->
)
& $script -WorkspacePaths $workspaces -StagingDir "C:\Code_Backups"
```
### Bước 2 — Xem kết quả log
```powershell
Get-Content "C:\Code_Backups\backup_log.txt" | Select-Object -Last 30
```
### Bước 3 — Kiểm tra file trên Google Drive
```powershell
$gd = (Get-PSDrive -PSProvider FileSystem | Where-Object { Test-Path (Join-Path $_.Root "My Drive") }).Root
Get-ChildItem (Join-Path $gd "My Drive\Code_Backups") -Recurse -File | Sort-Object LastWriteTime -Descending | Select-Object -First 10 | ForEach-Object { "$($_.Name) | $([Math]::Round($_.Length/1MB,0)) MB" }
```
---
## Nội dung Script (backup_workspaces.ps1)
> Script nhận tham số `-WorkspacePaths` — danh sách đường dẫn workspace cần backup.
> Không cần sửa file script khi đổi workspace. Chỉ cần truyền đường dẫn mới vào tham số.
```powershell
# ============================================================
# backup_workspaces.ps1 - v3.0 (Dynamic Workspaces)
#
# Tham so:
# -WorkspacePaths : Mang cac duong dan workspace can backup
# -StagingDir : (Optional) Thu muc local luu ZIP, mac dinh C:\Code_Backups
# -DriveFolder : (Optional) Ten thu muc tren Google Drive, mac dinh Code_Backups
#
# Vi du:
# .\backup_workspaces.ps1 -WorkspacePaths @("C:\MyProject","D:\OtherProject")
#
# ============================================================
param(
[string[]]$WorkspacePaths = @(),
[string]$StagingDir = "C:\Code_Backups",
[string]$DriveFolder = "Code_Backups"
)
$ErrorActionPreference = "Continue"
Add-Type -AssemblyName System.IO.Compression.FileSystem
# Loai tru cac thu muc nay khi nen
$ExcludeDirs = @("bin","obj",".vs","node_modules",".git","packages",".gitnexus","TestResults",".nuget","wwwroot\uploads")
$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$StartTime = Get-Date
# ---- TU DONG PHAT HIEN GOOGLE DRIVE -------------------------
function Find-GoogleDrive {
$regPaths = @("HKCU:\Software\Google\DriveFS","HKLM:\SOFTWARE\Google\DriveFS")
foreach ($reg in $regPaths) {
if (Test-Path $reg) {
$mp = (Get-ItemProperty $reg -ErrorAction SilentlyContinue).MountPoint
if ($mp -and (Test-Path $mp)) { return $mp.TrimEnd('\') }
}
}
if (Test-Path "HKCU:\Software\Google\Drive") {
$p = (Get-ItemProperty "HKCU:\Software\Google\Drive" -ErrorAction SilentlyContinue).Path
if ($p -and (Test-Path $p)) { return $p.TrimEnd('\') }
}
foreach ($drive in (Get-PSDrive -PSProvider FileSystem -ErrorAction SilentlyContinue)) {
try {
$candidate = Join-Path $drive.Root "My Drive"
if (Test-Path $candidate) { return $drive.Root.TrimEnd('\') }
} catch {}
}
return $null
}
function Get-FilesExcluding {
param([string]$Root, [string[]]$ExcludeDirNames)
Get-ChildItem -Path $Root -Recurse -File -ErrorAction SilentlyContinue | Where-Object {
$rel = $_.FullName.Substring($Root.Length).TrimStart('\')
$parts = $rel -split '\\'
$skip = $false
for ($i = 0; $i -lt ($parts.Count - 1); $i++) {
if ($ExcludeDirNames -contains $parts[$i]) { $skip = $true; break }
}
-not $skip
}
}
# ---- KHOI TAO -----------------------------------------------
if (-not (Test-Path $StagingDir)) {
New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null
}
$LogFile = Join-Path $StagingDir "backup_log.txt"
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [$Level] $Message"
Write-Host $line
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
# Neu khong truyen tham so nao, bao loi
if ($WorkspacePaths.Count -eq 0) {
Write-Host "[ERROR] Khong co workspace nao duoc truyen vao. Su dung: -WorkspacePaths @('path1','path2')" -ForegroundColor Red
exit 1
}
# Build workspace config tu tham so
$WorkspaceConfig = $WorkspacePaths | ForEach-Object {
$wsPath = $_.TrimEnd('\')
$wsName = Split-Path $wsPath -Leaf
@{ Name = $wsName; Path = $wsPath }
}
Write-Log "========================================================"
Write-Log "BAT DAU BACKUP - Machine: $env:COMPUTERNAME | User: $env:USERNAME"
Write-Log "Timestamp: $Timestamp"
Write-Log "Workspaces: $($WorkspaceConfig.Count)"
Write-Log "========================================================"
# ---- XAC DINH GOOGLE DRIVE ----------------------------------
$GoogleDriveRoot = Find-GoogleDrive
if (-not $GoogleDriveRoot) {
Write-Log "KHONG TIM THAY GOOGLE DRIVE - Chi luu local" "WARN"
$GoogleDriveDest = $null
} else {
$myDrive = Join-Path $GoogleDriveRoot "My Drive"
$driveBase = if (Test-Path $myDrive) { $myDrive } else { $GoogleDriveRoot }
$GoogleDriveDest = Join-Path $driveBase $DriveFolder
if (-not (Test-Path $GoogleDriveDest)) {
New-Item -ItemType Directory -Path $GoogleDriveDest -Force | Out-Null
}
Write-Log "Google Drive dich: $GoogleDriveDest"
}
# ---- NEN TUNG WORKSPACE -------------------------------------
$Results = @()
foreach ($ws in $WorkspaceConfig) {
$wsName = $ws.Name
$wsPath = $ws.Path
if ([string]::IsNullOrWhiteSpace($wsPath) -or !(Test-Path $wsPath)) {
Write-Log "[$wsName] Bo qua - khong tim thay: '$wsPath'" "WARN"
$Results += [PSCustomObject]@{ Workspace=$wsName; Status="SKIP"; Size="" }
continue
}
$zipName = "${wsName}_Backup_${Timestamp}.zip"
$zipPath = Join-Path $StagingDir $zipName
Write-Log "[$wsName] Dang quet files..."
try {
if (Test-Path $zipPath) { Remove-Item $zipPath -Force }
$files = Get-FilesExcluding -Root $wsPath -ExcludeDirNames $ExcludeDirs
Write-Log "[$wsName] Nen $($files.Count) files -> $zipName"
$zip = [System.IO.Compression.ZipFile]::Open($zipPath, 'Create')
foreach ($file in $files) {
$entry = $file.FullName.Substring($wsPath.Length).TrimStart('\')
try {
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
$zip, $file.FullName, $entry,
[System.IO.Compression.CompressionLevel]::Optimal
) | Out-Null
} catch {
Write-Log "[$wsName] Bo qua: $($file.Name)" "WARN"
}
}
$zip.Dispose()
$sizeMB = [Math]::Round((Get-Item $zipPath).Length / 1MB, 1)
Write-Log "[$wsName] Nen OK - $sizeMB MB"
if ($GoogleDriveDest) {
$wsFolder = Join-Path $GoogleDriveDest $wsName
if (-not (Test-Path $wsFolder)) {
New-Item -ItemType Directory -Path $wsFolder -Force | Out-Null
}
$gdPath = Join-Path $wsFolder $zipName
Write-Log "[$wsName] Copy sang Google Drive -> $gdPath"
Copy-Item -Path $zipPath -Destination $gdPath -Force
Write-Log "[$wsName] Google Drive OK -> $wsFolder"
}
$Results += [PSCustomObject]@{ Workspace=$wsName; Status="OK"; Size="$sizeMB MB" }
} catch {
Write-Log "[$wsName] LOI: $_" "ERROR"
$Results += [PSCustomObject]@{ Workspace=$wsName; Status="ERROR"; Size="" }
}
}
# ---- TOM TAT ------------------------------------------------
$ElapsedMin = [Math]::Round(((Get-Date) - $StartTime).TotalMinutes, 1)
Write-Log "========================================================"
Write-Log "KET QUA (Tong thoi gian: $ElapsedMin phut)"
Write-Log "========================================================"
foreach ($r in $Results) {
$icon = if ($r.Status -eq "OK") { "[OK]" } elseif ($r.Status -eq "SKIP") { "[SKIP]" } else { "[ERROR]" }
Write-Log "$icon $($r.Workspace.PadRight(25)) -> $($r.Status) $($r.Size)"
}
if ($GoogleDriveDest) { Write-Log "Google Drive: $GoogleDriveDest" }
Write-Log "Local staging: $StagingDir"
Write-Log "Log: $LogFile"
Write-Log "========================================================"
Write-Host ""
Write-Host "Backup completed!"
```
@@ -1,118 +0,0 @@
# Blazor WASM Deployment Compression Optimization
## Bối Cảnh
Khi deploy ứng dụng Blazor WebAssembly lên Docker/VPS, payload ban đầu rất nặng (~20MB uncompressed, ~15MB compressed). Ứng dụng phải tải hơn 80 file `.wasm``.dll` trước khi khởi động được. Nếu không tối ưu đúng cách, thời gian load trang có thể lên tới **3+ phút**.
## Các Tối Ưu Đã Áp Dụng
### 1. Conditional Response Compression (Tránh Double-Compression)
**Vấn đề:** Blazor WASM publish tự động tạo ra các file pre-compressed `.br` (Brotli) trong thư mục `_framework/`. Khi thêm middleware `ResponseCompression` vào pipeline, nó **nén thêm lần nữa** lên file đã nén → trình duyệt giải mã thất bại → `TypeError: Failed to fetch`.
**Giải pháp:** Dùng `app.UseWhen()` để áp dụng compression cho MỌI đường dẫn **NGOẠI TRỪ** `/_framework`:
```csharp
// Program.cs — Services
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
{
"image/svg+xml", "application/javascript", "text/css"
});
});
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
// Program.cs — Middleware (CONDITIONAL)
app.UseWhen(
context => !context.Request.Path.StartsWithSegments("/_framework"),
appBuilder => { appBuilder.UseResponseCompression(); }
);
```
> **Quy tắc vàng:** KHÔNG BAO GIỜ nén `/_framework/*` bằng middleware. Blazor đã tự xử lý qua `UseBlazorFrameworkFiles()`.
### 2. Aggressive Static File Caching (365 ngày)
**Vấn đề:** Mỗi lần truy cập, trình duyệt tải lại toàn bộ 80+ file static (CSS, JS, WASM) vì không có Cache header.
**Giải pháp:** Set `Cache-Control: public,max-age=31536000` (1 năm) cho tất cả static files:
```csharp
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=31536000");
}
});
```
> Lần đầu load chậm, nhưng **từ lần 2 trở đi load gần như tức thì** vì mọi thứ đã cached.
### 3. Assembly Lazy Loading (Giảm 4MB payload)
**Vấn đề:** DevExpress PDF và Printing DLLs chiếm tổng ~4MB compressed nhưng chỉ dùng khi xuất file/in ấn.
**Giải pháp:** Thêm `BlazorWebAssemblyLazyLoad` vào `.csproj`:
```xml
<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="DevExpress.Pdf.v24.2.Core.wasm" />
<BlazorWebAssemblyLazyLoad Include="DevExpress.Printing.v24.2.Core.wasm" />
</ItemGroup>
```
> **Kết quả:** Giảm initial payload từ ~15MB → ~11MB compressed.
### 4. HTTP/1.1 vs HTTP/2 (Nút thắt cổ chai lớn nhất)
**Vấn đề CRITICAL:** Khi truy cập qua `http://IP:port` (HTTP thuần), trình duyệt bắt buộc dùng HTTP/1.1 với giới hạn **tối đa 6 kết nối đồng thời** cho mỗi domain. Với 80+ file WASM, trình duyệt phải xếp hàng tải từng batch 6 file → cực kỳ chậm (2-3 phút).
**Giải pháp:** Truy cập qua HTTPS domain (ví dụ: `https://sso.instratech.net/hrm/`). HTTPS tự động kích hoạt **HTTP/2 Multiplexing** — tải **tất cả 80 file cùng lúc** trên 1 kết nối duy nhất.
| Phương thức | Protocol | Song song tối đa | Thời gian load |
|---|---|---|---|
| `http://IP:8010` | HTTP/1.1 | 6 files | ~2.5 phút |
| `https://domain/hrm/` | HTTP/2 | Unlimited | ~10-15 giây |
> **Bài học:** LUÔN deploy Blazor WASM đằng sau reverse proxy có HTTPS (Nginx, Cloudflare, etc.) để tận dụng HTTP/2.
## Thứ Tự Middleware Đúng Trong Pipeline
```
UseResponseCompression() ← CHỈ cho non-framework paths
UseBlazorFrameworkFiles() ← Serve pre-compressed _framework/*.br files
UseStaticFiles() ← Serve CSS/JS/images với cache headers
```
## Kiểm Tra Nhanh
```bash
# Verify Brotli compression đang hoạt động cho _framework files:
curl -I -H "Accept-Encoding: br" http://localhost:8010/_framework/blazor.webassembly.js
# Phải thấy: Content-Encoding: br
# Verify non-framework files cũng được nén:
curl -I -H "Accept-Encoding: br" http://localhost:8010/_content/DevExpress.Blazor/modules/dx-blazor-all.js
# Phải thấy: Content-Encoding: br (từ middleware)
# Verify cache headers:
curl -I http://localhost:8010/app.css
# Phải thấy: Cache-Control: public,max-age=31536000
```
## Tổng Hợp Kết Quả
| Tối ưu | Tiết kiệm |
|---|---|
| Conditional Brotli Compression | ~60-70% kích thước CSS/JS |
| Lazy Loading (PDF + Printing) | ~4MB compressed |
| Cache 365 ngày | Lần 2+ load tức thì |
| HTTP/2 Multiplexing | Từ 2.5 phút → 10-15s |
-37
View File
@@ -1,37 +0,0 @@
---
description: Decrypt Forge App ID & Secret from database using connection string in appsettings*.json of current workspace
---
# Decrypt Forge Credentials
Giải mã Forge App ID, Forge Secret, FTP credentials từ database.
Script tự động tìm `appsettings*.json` trong workspace hiện tại để lấy connection string.
## How It Works
1. Tìm `appsettings*.json` trong workspace (ưu tiên `appsettings.Development.json` > `appsettings.json`)
2. Đọc `ConnectionStrings.DefaultConnection`
3. Query `GLBSystems` + `FOGForgeApps`
4. Decrypt bằng TripleDES/ECB (key: `instratechspm2019`)
5. Lưu ra `Desktop\forge_credentials_decrypted.txt`
## Steps
### 1. Xác định workspace path
Lấy đường dẫn workspace hiện tại mà user đang mở (project root chứa appsettings*.json).
### 2. Chạy script
// turbo
```powershell
powershell -ExecutionPolicy Bypass -File "C:\Users\huanld\.gemini\antigravity\scripts\decrypt_forge.ps1" -SearchPath "{workspace_path}"
```
Thay `{workspace_path}` bằng đường dẫn workspace thực tế.
### 3. Đọc kết quả
File output: `C:\Users\huanld\Desktop\forge_credentials_decrypted.txt`
Đọc file và trình bày kết quả cho user.
-52
View File
@@ -1,52 +0,0 @@
---
description: Pipeline and Template for deploying IFC 3D Viewer Converter into a new ATAD application
---
# Workflow: Deploy IFC Converter Template to New Project
This workflow establishes the procedures to transfer and configure the standalone .NET Worker IFC Converter Service and Backend API endpoints to any new module.
---
1. Khởi tạo và Extract Code Template
Bung nén file **`IFC_Template.zip`** (được đính kèm ngay cùng thư mục chứa file hướng dẫn này - linh hoạt di chuyển qua mọi máy / server mà không phụ thuộc đường dẫn tuyệt đối). Chiết xuất bộ code mẫu này sẽ ra 2 cấu phần chính:
- Thư mục **`IFC.ConverterService`** bao gồm Background Service và bộ lõi Xbim (DLLs).
- Thư mục **`API_Sample`** chứa tất cả Controller, Service và Razor Pages UI cho việc Upload File.
2. Web API / DataBase
- Chỉnh sửa Context DB của Web ứng dụng mới. Merge các thuộc tính của `IFCConvertJobEntity` vào. Tạo Migration Add bảng `IFCConvertJob` vào Database.
- Copy thư mục `API_Sample/Contracts`, `API_Sample/Controllers``API_Sample/Services` vào Source Code Web của bạn.
- Cấu hình Dependency Injection ở Program.cs:
```csharp
services.AddScoped<IIfcModelService, IfcModelService>();
services.AddSingleton<IfcConverterPipeClient>();
```
- Cấu hình Named Pipe ở `appsettings.json` của Web:
```json
"IfcConverter": {
"PipeName": "ProjectPipeName_Unique",
"ConnectTimeoutMs": 10000
}
```
3. Gắn Giao Điện (Frontend Blazor/Razor)
- Copy `UploadIFC.razor` vào thư mục Pages.
- Cân chỉnh lại CSS hoặc biến ngôn ngữ dịch hóa (i18n) cho phù hợp với UI của Module Web bạn đang build.
4. Cài đặt Cấu hình IFC Worker
- Đổi tên thư mục `IFC.ConverterService` thành tên phù hợp nếu muốn (VD: `MyApp.IFCWorker`).
- Mở Server, vào file cấu hình của Worker, đặt `PipeName` đúng bằng chữ `"ProjectPipeName_Unique"`.
- Đảm bảo File System Path lưu file giữa Web (upload) và Worker (đọc ifc để convert) là trỏ đến cùng 1 Ổ đĩa / Folder thư mục vật lý.
5. Cài đặt Windows Services (Triển khai Server Cuối)
- Sử dụng lệnh SC Create để đóng gói chạy Background:
```powershell
# Lưu ý: Thay thế đường dẫn D:\Apps\MyApp_IFCWorker bằng thư mục vật lý thực tế bạn vừa bung nén ra.
sc.exe create "MyApp_IFCConverter" binpath= "D:\Apps\MyApp_IFCWorker\IFC.ConverterService.exe" start= auto
sc.exe start "MyApp_IFCConverter"
```
6. Nâng cấp Core Conversion Workflow (Future Updates)
Khi triển khai lại, cần bổ sung hoặc rà soát các tính năng mở rộng sau cho hệ thống:
- **Fallback Convert**: Xây dựng cơ chế fallback (chuyển phòng bị). Nếu thuật toán convert IFC chính (Xbim) gặp lỗi hoặc bị crash do file quá phức tạp, hệ thống nên tự động gọi một bộ parser/converter thứ cấp (công cụ khác hoặc API thứ ba) để đảm bảo mô hình vẫn được render.
- **Check DB Tránh Trùng Lặp**: Trước khi đẩy job convert vào Pipeline/Queue, bắt buộc thêm bước check Database xem mô hình này (theo ModelID hoặc FileHash) đã được convert thành công trước đó hay chưa, tránh tình trạng convert đi convert lại gây tốn tài nguyên Server.
- **Đánh dấu IsModelReady**: Sau khi event thông báo pipe convert xong (hoặc Worker update Job), cần cập nhật field `IsModelReady = true` (hoặc các trạng thái render tương đương) ở bảng quản lý Model/Document của DB để báo hiệu cho Web Client có thể truy cập/hiển thị `eview` hoặc preview nội dung 3D.
-185
View File
@@ -1,185 +0,0 @@
---
description: Deploy any INS module to VPS with pre-flight connectivity checks, local build, and post-deploy verification
---
# Docker VPS Release Workflow
> **RULE**: Never build on VPS. Always publish locally and create Docker image with pre-built DLLs only.
// turbo-all
## Prerequisites
- Docker Desktop running locally
- SSH access to VPS: `root@36.50.176.30`
- SSH key: `~/.ssh/id_rsa` or `~/.ssh/id_ed25519`
## Phase 1: Pre-flight — Check VPS Infrastructure
### 1.1. SSH into VPS and check Docker network
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "docker network ls --format 'table {{.Name}}\t{{.Driver}}\t{{.Scope}}'"
```
### 1.2. List all containers on `app-network` with health status
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}\t{{.Networks}}' | head -30"
```
### 1.3. Verify SSO container is running and healthy
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "docker inspect sso-instratech --format '{{.State.Status}} | Health: {{.State.Health.Status}} | Network: {{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' 2>/dev/null || echo 'SSO container NOT FOUND'"
```
### 1.4. Verify Redis is running
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "docker exec ins-redis redis-cli ping 2>/dev/null || echo 'Redis NOT AVAILABLE'"
```
### 1.5. Verify inter-container DNS resolution (from a running container)
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "docker exec sso-instratech sh -c 'getent hosts ins-redis && getent hosts ins-rabbitmq' 2>/dev/null || echo 'DNS resolution check failed'"
```
### 1.6. Check gRPC health on SSO (port 8082)
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "curl -sf http://localhost:8082/health || echo 'gRPC health endpoint not responding'"
```
> **STOP**: If SSO or Redis are NOT running/healthy, fix them FIRST before deploying any module.
---
## Phase 2: Validate Configuration
### 2.1. Identify the correct appsettings file for the target module
Check the module's Dockerfile to see which CONFIG_FILE is used:
```powershell
Select-String -Path "Dockerfile" -Pattern "CONFIG_FILE"
```
- `appsettings.container.release.json` → Production VPS (sso.instratech.net)
- `appsettings.container.debug.json` → Local Docker Desktop
- `appsettings.container.json` → Generic container mode
### 2.2. Verify appsettings has correct service hostnames
Review the release config and verify:
```powershell
Get-Content "src\*Backend\appsettings.container.release.json" | Select-String -Pattern "ServerUrl|ConnectionString|Host|redis|rabbitmq|sso-instratech"
```
**Expected hostnames inside Docker network:**
| Service | Hostname | Port |
|---------|----------|------|
| SSO (HTTP) | `sso-instratech` | 8080 |
| SSO (gRPC) | `sso-instratech` | 8082 |
| Redis | `ins-redis` or `redis` | 6379 |
| RabbitMQ | `ins-rabbitmq` or `rabbitmq` | 5672 |
| SQL Server | External IP (e.g., `203.167.14.22`) | 1433 |
> **CRITICAL**: Connection strings MUST use Docker network hostnames, NOT `localhost`.
### 2.3. Verify startup order
Container startup dependencies:
```
1. Redis (ins-redis) ← No dependencies
2. RabbitMQ (ins-rabbitmq) ← No dependencies
3. SSO (sso-instratech) ← Depends on: Redis, RabbitMQ, SQL Server
4. INS.PRO (ins-pro) ← Depends on: SSO, Redis, SQL Server
5. INS.CDE (ins-cde) ← Depends on: SSO, Redis, SQL Server
6. INS.WJC (ins-wjc) ← Depends on: SSO, Redis, SQL Server
```
> All modules depend on SSO for gRPC token validation. SSO MUST be healthy before starting modules.
---
## Phase 3: Build Locally (NEVER on VPS)
### 3.1. Clean previous publish output
```powershell
Remove-Item -Path "publish" -Recurse -ErrorAction SilentlyContinue
```
### 3.2. Publish with Release configuration (no debug symbols)
```powershell
dotnet publish src\<MODULE>.Backend\<MODULE>.Backend.csproj -c Release -o publish/backend /p:DebugType=none /p:DebugSymbols=false
```
### 3.3. Build Docker image with production config
```powershell
docker build -t <image-name>:latest --build-arg CONFIG_FILE=appsettings.container.release.json -f Dockerfile .
```
### 3.4. Save image as .tar for transfer
```powershell
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'; docker save -o "<image-name>-$ts.tar" <image-name>:latest
```
---
## Phase 4: Deploy to VPS
### 4.1. Upload .tar to VPS
```powershell
scp -o StrictHostKeyChecking=no "<image-name>-*.tar" "root@36.50.176.30:/tmp/"
```
### 4.2. Load image and deploy on VPS
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "docker load -i /tmp/<image-name>-*.tar && docker stop <container-name> 2>/dev/null; docker rm <container-name> 2>/dev/null; docker run -d --name <container-name> --network app-network --restart unless-stopped -p <host-port>:<container-port> -e ASPNETCORE_ENVIRONMENT=Production -e DOTNET_RUNNING_IN_CONTAINER=true --health-cmd 'curl -f http://localhost:<container-port>/health/ready || exit 1' --health-interval 30s --health-timeout 10s --health-retries 3 --health-start-period 60s <image-name>:latest && rm -f /tmp/<image-name>-*.tar"
```
### 4.3. Clean up local .tar
```powershell
Remove-Item -Path "<image-name>-*.tar" -Force -ErrorAction SilentlyContinue
```
---
## Phase 5: Post-Deploy Verification
### 5.1. Check container health status
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "docker ps --filter name=<container-name> --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
```
### 5.2. Check startup connectivity report in logs
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "docker logs <container-name> 2>&1 | grep -A 20 'Connectivity Report'"
```
Must see: All ✅ in the report. If any ❌, the container will crash and restart.
### 5.3. Deep health check via HTTP
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "curl -s http://localhost:<host-port>/health/ready | python3 -m json.tool 2>/dev/null || curl -s http://localhost:<host-port>/health/ready"
```
Expected: `"status": "Healthy"` with all checks passing.
### 5.4. Verify container-to-container connectivity
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "docker exec <container-name> sh -c 'getent hosts sso-instratech && getent hosts ins-redis' 2>/dev/null || echo 'DNS resolution FAILED'"
```
### 5.5. Test gRPC connectivity from new container to SSO
```powershell
ssh -o StrictHostKeyChecking=no root@36.50.176.30 "docker exec <container-name> sh -c 'curl -sf http://sso-instratech:8082/health' 2>/dev/null || echo 'gRPC SSO unreachable from container'"
```
> **DEPLOYMENT IS COMPLETE** only when ALL Phase 5 checks pass.
---
## Module Reference
| Module | Image | Container | Port | Config |
|--------|-------|-----------|------|--------|
| SSO | `sso-instratech` | `sso-instratech` | 8080, 8082 | `appsettings.container.release.json` |
| PRO | `ins-pro` | `ins-pro` | 8000 | `appsettings.container.release.json` |
| CDE | `ins-cde` | `ins-cde` | 8090 | `appsettings.container.release.json` |
| WJC | `ins-wjc` | `ins-wjc` | 8070 | `appsettings.container.release.json` |
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| Container keeps restarting | Critical dependency unreachable | Check Phase 5.2 logs for ❌ |
| `health/ready` returns 503 | gRPC/Redis/SQL down | Fix dependency first, container will self-heal |
| DNS resolution fails | Container not on `app-network` | `docker network connect app-network <name>` |
| 30s request timeouts | gRPC SSO unreachable | Check SSO container + network alias |
-39
View File
@@ -1,39 +0,0 @@
---
description: Initialize Workspace Memory Protocol for Antigravity
---
# /initial - Khởi tạo Workspace Memory (Global Version)
$ARGUMENTS
---
## 🎯 Mục đích
Thiết lập "Bộ nhớ vô hạn" (Workspace Memory) cho dự án hiện tại bằng cách kết nối với **MCP Knowledge Server**. Lệnh này sẽ tự động hóa việc khởi tạo bối cảnh dự án, tạo Plan và Task trên Database (MongoDB) thay vì chỉ lưu file text cục bộ. Nó sẽ giúp mọi session AI sau này tự động nhớ được tiến độ công việc đang làm.
---
## 🛠️ Trình tự thực thi (MANDATORY)
Khi User gõ lệnh `/initial` ở bất kỳ thư mục dự án nào, **BẠN (AI) BẮT BUỘC PHẢI LÀM ĐỦ 4 BƯỚC SAU:**
### Bước 1: Gọi công cụ `GetWorkspaceContext`
- **Action:** Gọi tool `GetWorkspaceContext` với `workspaceId` là đường dẫn gốc CỦA DỰ ÁN HIỆN TẠI (Absolute path).
- **Mục đích:** Để AI "thức tỉnh" và biết dự án này đã từng có ai viết Plan hay tạo Task chưa. (Dù lỗi hay rỗng cũng phải gọi để register workspace ID).
### Bước 2: Khởi tạo Implementation Plan (`SetWorkspacePlan`)
- **Action:** Nếu Bước 1 trả về rỗng (No plan set), hãy nhìn vào yêu cầu của User (nếu có ở `$ARGUMENTS`) hoặc hỏi User dự án này dùng để làm gì.
- Sau đó GỌI `SetWorkspacePlan` để ghi một bản Tóm tắt Kiến trúc & Mục tiêu dự án vào MCP Server.
### Bước 3: Phân rã công việc dở dang (`UpsertTask`)
- **Action:** Dựa trên Implementation Plan ở Bước 2, GỌI tool `UpsertTask` tạo ngay 2-3 Tasks đầu tiên với status `Todo`.
- **Mục đích:** Để AI sau này khi vào `/plan` hay `/orchestrate` thì biết mở list task ra xem.
### Bước 4: Chốt hạ Output cho User
- **Action:** Gửi `<notify_user>` thông báo: "Workspace Memory đã được kích hoạt thành công cho dự án này trên MCP Server. Mọi Agent từ nay sẽ luôn tự động check-in tiến độ trước khi làm việc!"
---
## 🛑 Forbidden Rules
- TUYỆT ĐỐI KHÔNG trả lời suông "Tôi đã hiểu". Bắt buộc phải thực hiện các Tool Calls tới MCP Server ở Bước 1, 2 và 3.
- KHÔNG LƯU `GEMINI.md` cục bộ tại mỗi project nữa vì Rule này đã được apply ở mức Global (`C:\Huanld\Code\.agent\rules\GEMINI.md`).
-225
View File
@@ -1,225 +0,0 @@
---
description: INS Module feature development workflow. Full lifecycle from planning to verification. Standard for all INS modules (HRM, PRO, CDE, WJC, etc.)
---
# /ins-develop - INS Module Feature Development
$ARGUMENTS
---
## Architecture Context
All INS modules follow the same architecture:
```
┌──────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Browser │────▶│ INS.{MOD}.Backend│────▶│ sso-instratech │
│ (Blazor │ │ localhost:{PORT} │gRPC │ 10.0.0.1:8082 │
│ WASM) │ │ │ │ (Auth/SSO) │
└──────────────┘ └────────┬─────────┘ └─────────────────┘
┌─────────┴──────────┐
│ │
┌────▼─────┐ ┌──────▼──────┐
│ INS_{MOD}│ │ INS_SYS │
│ Module DB│ │ Auth DB │
└──────────┘ └─────────────┘
```
| Component | Description |
|-----------|-------------|
| **Frontend** | `src/INS.{MOD}.Frontend/` — Blazor WASM, AdminLTE sidebar |
| **Backend** | `src/INS.{MOD}.Backend/` — ASP.NET 8, Kestrel |
| **Auth DLL** | `INS.ModuleControllers.dll` — gRPC auth, SSO, navigation |
| **Module DB** | Business data (employees, projects, documents, etc.) |
| **Auth DB** | `INS_SYS` on `dev.instratech.net` — users, permissions, navigation |
| **gRPC** | `sso-instratech` container at `10.0.0.1:8082` |
---
## Phase 0: KNOWLEDGE DISCOVERY (🔴 BẮT BUỘC — Luôn chạy đầu tiên)
> **KHÔNG ĐƯỢC BỎ QUA.** Trước khi làm bất cứ gì, phải tìm kiếm knowledge base.
1. **Search knowledge base** — Tìm quy tắc dự án, patterns, conventions
```
mcp_knowledge_search_knowledge(query="{tên tính năng hoặc domain liên quan}")
mcp_knowledge_search_knowledge(query="INS component")
mcp_knowledge_search_knowledge(query="coding conventions")
```
2. **Read relevant KI artifacts** — Đọc chi tiết các KI tìm được (architecture, component usage rules, past implementations)
3. **Check module-specific knowledge** — Tìm theo module
```
mcp_knowledge_list_modules(solution="ins-pro") // hoặc module đang phát triển
mcp_knowledge_get_module_context(solution="...", moduleName="...")
```
4. **Check global standards** — Đọc coding standards chung
```
mcp_knowledge_get_global_standards()
```
5. **Result:** Ghi nhận tất cả rules/constraints phải tuân thủ trước khi code.
---
## Phase 1: RESEARCH (Bắt buộc)
// turbo-all
6. **Identify module** — Determine ModuleCode (e.g., `INS.HRM`, `INS.PRO`, `INS.CDE`)
7. **Check DB schema** — Connect to module DB and inspect tables
```powershell
# Query schema
$connStr = "<module connection string from appsettings.json>"
# SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
# SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '...'
```
8. **Check existing code** — Search for related `.razor`, `.cs` files, controllers
9. **Check permissions** — Query `[Auth].[ApplicationRules]` for existing FuncCodes of this module
10. **Check navigation** — Verify sidebar entries in module DB (e.g., `BranchInfo` table)
---
## Phase 2: PLAN
7. **Create implementation plan** — Break into:
- [ ] DB changes (tables, seed data, migrations)
- [ ] Backend (controllers, services, DTOs)
- [ ] Frontend (pages, components, sidebar entries)
- [ ] Permissions (new FuncCodes in `INS_SYS`)
- [ ] Navigation (sidebar menu items)
8. **Present plan to user** — Wait for approval before coding.
---
## Phase 3: IMPLEMENT
### 3a. Database First
9. **Create/modify tables** — EF Core migrations or raw SQL
10. **Seed reference data** — Insert into module DB
11. **Add permission rules** — Insert into `INS_SYS`
```sql
-- AuthDb connection: Server=dev.instratech.net;Database=INS_SYS;User Id=spm34d.ins;Password=SPM2025@#;...
-- 1. Add rule definition
INSERT INTO [Auth].[ApplicationRules] (ModuleCode, Category, FuncCode, Description, IsActive, CreatedAt, UpdatedAt)
VALUES ('INS.{MOD}', '{Category}', '{MOD}_{CAT}_{ACTION}', N'{Description}', 1, GETDATE(), GETDATE());
-- 2. Assign to root user
INSERT INTO [Auth].[UserApplicationRules] (UserId, RuleId, IsAllowed, AssignedBy, AssignedAt)
SELECT 'root', ar.Id, 1, 'system', GETDATE()
FROM [Auth].[ApplicationRules] ar
WHERE ar.FuncCode = '{MOD}_{CAT}_{ACTION}'
AND NOT EXISTS (SELECT 1 FROM [Auth].[UserApplicationRules] uar WHERE uar.UserId = 'root' AND uar.RuleId = ar.Id);
```
### 3b. Backend
12. **Create DTOs** in `Models/`
13. **Create Controller** — follow existing controller patterns in the module
14. **Register services** in `Program.cs` if needed
15. **Middleware check** — ensure new endpoints work with gRPC validation pipeline
### 3c. Frontend
16. **Create page** in `Pages/` — follow existing page patterns
17. **Add sidebar entry** — Insert navigation record (e.g., `BranchInfo`)
18. **Use INS components** — `INS_DataGrid`, `INS_Popup`, `INS_RuleInit` (check KB!)
19. **Wire API calls** — `HttpClient` with auth token from `IModuleAuthenticationService`
---
## Phase 4: BUILD & TEST
// turbo-all
20. **Stop server**
```powershell
Stop-Process -Name "INS.{MOD}.Backend" -Force -ErrorAction SilentlyContinue
```
21. **Build**
```powershell
dotnet build src/INS.{MOD}.Backend/INS.{MOD}.Backend.csproj
```
22. **Fix build errors** — Check `error CS` output, fix, rebuild
23. **Start server**
```powershell
dotnet run --project src/INS.{MOD}.Backend/INS.{MOD}.Backend.csproj
```
24. **Verify startup logs** — Must see:
- `Now listening on: http://localhost:{PORT}` ✅
- No gRPC connection errors ✅
- No DB errors ✅
---
## Phase 5: VERIFY
25. **API test** via PowerShell
```powershell
$login = Invoke-RestMethod -Method Post -Uri "http://localhost:{PORT}/api/auth/login" `
-Body (@{email="root@local.instratech";password="admin123"} | ConvertTo-Json) -ContentType "application/json"
$h = @{ "Authorization" = "Bearer $($login.token)" }
Invoke-RestMethod -Uri "http://localhost:{PORT}/{mod}/api/{endpoint}" -Headers $h
```
26. **Browser test** — Login, navigate, verify:
- All buttons visible (permissions loaded correctly)
- Data displays, CRUD works
- Popup/modal margins follow INS component guidelines
27. **Permission verify** — `api/applicationrule/user` returns new FuncCodes
---
## Phase 6: REVIEW
28. **Check logs** for warnings/errors
29. **Remove debug code**
30. **Report** to user with change summary
---
## Troubleshooting
| Symptom | Root Cause | Fix |
|---------|-----------|-----|
| gRPC connection refused | `sso-instratech` stopped | `ssh root@36.50.176.30 "docker start sso-instratech"` |
| 401 on data API | Token not validated by gRPC | Check `GrpcTokenValidationMiddleware` logs |
| 401 on `/api/applicationrule` | Path not bypassed | `UseWhen` must exclude `/api/applicationrule` |
| Buttons missing | FuncCode not in DB | Insert into `[Auth].[ApplicationRules]` + assign to user |
| DB already exists | `EnsureCreated()` | Wrap in try-catch |
| Build locked file | Previous process | `Stop-Process -Name "INS.{MOD}.Backend" -Force` |
| SSO register-session fail | gRPC server unreachable | Check `appsettings.json > GrpcClient.ServerUrl` |
| Popup margins wrong | Component misuse | Check knowledge base for INS component rules |
---
## Server Infrastructure
| Container | Host Port | Internal | Purpose |
|-----------|-----------|----------|---------|
| `sso-instratech` | **8082** | 8082 | SSO/Auth gRPC (ALL modules use this) |
| `ins-sys` | 8083/7001 | 8082/8080 | INS.SYS Backend |
| `epm-sqlserver` | 1434 | 1433 | SQL Server |
| `epm-gateway` | 5000 | 5000 | API Gateway |
SSH: `ssh root@36.50.176.30`
---
## Examples
```
/ins-develop thêm module quản lý hợp đồng
/ins-develop thêm chức năng export PDF cho báo cáo
/ins-develop thêm trang dashboard thống kê
/ins-develop thêm CRUD cho bảng mới
```
-52
View File
@@ -1,52 +0,0 @@
---
description: Workflow for preparing IFC models, standardizing filenames, and syncing with APIs & FTP across ATAD software modules
---
# Workflow: Prepare IFC Model Deployment
This workflow standardizes the IFC integration process across ATAD's software modules. It downloads raw IFC files from Autodesk Forge, unifies their syntax/naming conventions, uploads them via the new 3-step API, updates the database URNs, syncs with FTP, and handles dynamic routing.
---
### Mức 0: Xác định Tiền Tố (Prefix)
Khi Agent bắt đầu chạy workflow này để ứng dụng cho một project khác, **phải chủ động hỏi người dùng** về tiền tố sẽ được áp dụng cho tên file IFC đối với dự án hiện tại (Ví dụ: `atad-`, `ins-`, `swe-`, v.v...).
🚫 **TUYỆT ĐỐI KHÔNG** fix cứng chữ `atad-` trong code mẫu. Luôn khai báo động và dùng tiền tố người dùng cung cấp vào tên file đầu ra.
### 1. Khởi tạo Razor Component
- Khởi tạo file Component View (thường sẽ là `PrepairIFC.razor` hoặc `PrepareIFC.razor`) với route tương ứng cơ chế của project (VD: `@page "/prepair-ifc/{unitid}"`).
- Bắt buộc gắn thuộc tính cấu hình `@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]` để cho phép việc trigger/webhook trích xuất tự động không cần xác thực User Auth.
- Xây dựng UI Loading thể hiện các bước Process (Đang Authenticate, Đang Load Forge, Uploading API...).
- Inject các cấu phần Service: Base DbContext, `IHttpClientFactory`, Service Forge (`IForgeUploadServices`), Service FTP (`IFTPUploadServices`).
### 2. Tiêu Chuẩn Hóa Tên File IFC (Naming Format)
Sau khi load dữ liệu DB chứa thông số của Model, tiến hành sinh chuỗi tên File IFC. Tên File tải từ Forge về có thể sai lệch hoặc không tuân thủ mẫu mới nhất.
Luôn thực thi cấu trúc: `<Prefix><ProjectCode>_<AreaCode>_<UnitCode>_<Revision>.ifc`
- `$Prefix`: Truyền biến từ Mức 0.
- **Quy tắc Revision**: Khi lấy code version từ DB (VD lấy `IfcModelVersion`), kiểm tra định dạng chữ `R`. Nếu format rỗng hoặc chỉ có số `1`, hệ thống bắt buộc tự convert thành chuẩn 2 char `R01`:
```csharp
// Logic mẫu cho AI Agent áp dụng:
string rev = dbUnit.IfcModelVersion ?? "00";
if (!rev.StartsWith("R", StringComparison.OrdinalIgnoreCase)) {
rev = int.TryParse(rev, out int rInt) ? $"R{rInt:D2}" : $"R{rev}";
}
string desiredFilename = $"{prefix}{dbProject.Code}_{dbArea.Code}_{dbUnit.Code}_{rev}.ifc";
```
### 3. Tích hợp Autodesk Forge
- Gọi hàm Authenticate của Cấu phần Hệ Thống (Ví dụ `ForgeUploadServices.Forge_AuthenticateV2`) để lấy access token.
- Khởi tạo `ObjectsApi` gọi `GetObjectsAsync(bucketKey)` để trích xuất ObjectKey cho thư mục Bucket của Model.
- Bỏ qua API SDK nếu Download Stream gặp hạn chế (Memory Stream Leak), hãy trực tiếp sử dụng lệnh `HttpClient.GetAsync()` download File thông qua Url tĩnh của Forge (`https://developer.api.autodesk.com/oss/v2/buckets/...`) cùng header *Bearer* để ghi dữ liệu về một Temp Caching Directiory trong Thư mục `Uploads`.
### 4. Luồng API Upload IFC V2
- Truy xuất thông số từ bảng Cấu hình của dự án (Thường là `GLBSystems` với các cột: `ModelApiDomain`, `ModelUserId`, `ModelProjectId`, `ModelFolderId`).
- Đẩy File Temp ở Bước 3 qua Multipart Data vào Endpoints API `/api/files/upload`. Đọc JSON trả về lấy `itemId`. (Lưu ý: Status `Conflict` vẫn cần bóc tách đọc ItemID bình thường).
- Gọi tiếp sang API Endpoint Lấy `Urn` (`/api/Files/urn?itemId=...`). Lấy ra giá trị `objectUrn` của File 3D.
- Kết nối DbContext, Gọi hàm Raw SQL Update `Urn` và cờ View(`IsModelReadyView = 1`) gán qua cho Table chứa cấu trúc lưu trữ 3D tương ứng.
### 5. FTP Server Synchronization (Hệ thống File Server)
- Build định dạng đường dẫn lưu trên FTP: `${ProjectCode}/${AreaCode}/${UnitCode}/All/{desiredFilename}`.
- Gọi `UploadFile` thao tác đẩy Cache File lên FTP để lưu trữ.
- Nếu trả về FtpStatus Success, cập nhật trực tiếp biến Remote Path đó vào cột lưu trữ vật lý của DB.
### 6. Cleanup & Bypass Routing
- Xóa Temp Caching File bằng lệnh `File.Delete()` an toàn để giải phóng cấu trúc vùng nhớ và không gian ổ đĩa.
- Gọi NavigationManager.NavigateTo bay thẳng qua URL hiển thị mô hình (`/eview/{unit_id}`). Có thể chèn hàm trễ `Task.Delay(2000)` để hiển thị Message báo kết quả Upload xong trước khi đổi trang.
+2 -2
View File
@@ -1,12 +1,12 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package Name="Tailscale Custom"
Manufacturer="SoftsBusiness"
Version="1.0.0.0"
Version="1.0.1.0"
UpgradeCode="{510A8C57-BA8F-4B9F-84E3-8E5C4E091054}"
Scope="perMachine">
<!-- Nhúng luôn dữ liệu vào MSI thay vì tách riêng ra file cab1.cab -->
<MediaTemplate EmbedCab="yes" />
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
<!-- Icon hiển thị trong Control Panel (Add/Remove Programs) -->
<Icon Id="TrayIcon.exe" SourceFile="dist\tailscale-tray.exe" />
Binary file not shown.
Binary file not shown.
+84 -40
View File
@@ -185,6 +185,16 @@ func onClick(ctx context.Context, item *systray.MenuItem, fn func()) {
return
case <-item.ClickedCh:
fn()
// A modal dialog opened inside fn() runs its own Win32 message
// loop, during which the original click can be re-posted/queued.
// Drain one pending click so we don't immediately re-run fn()
// and spam the same popup over and over.
select {
case <-item.ClickedCh:
case <-ctx.Done():
return
default:
}
}
}
}()
@@ -206,7 +216,11 @@ func (a *app) rebuild() {
state := a.backendState()
isRunning := state == "Running"
isNeedsLogin := state == "NeedsLogin"
// NoState means the backend has no usable login/profile yet, so it must go
// through the login flow (not a plain WantRunning toggle). Treat it the same
// as NeedsLogin so the "Connect" button triggers doLogin() instead of a
// no-op EditPrefs.
isNeedsLogin := state == "NeedsLogin" || state == "NoState"
// ── Header ──────────────────────────────────────────
a.setTrayIcon(state)
@@ -361,14 +375,26 @@ func (a *app) rebuild() {
logoutItem := systray.AddMenuItem("Logout", "Logout and deregister from server")
onClick(ctx, logoutItem, func() {
log.Println("action: Logout")
opCtx, opCancel := context.WithTimeout(a.bgCtx, 10*time.Second)
defer opCancel()
if err := a.lc.Logout(opCtx); err != nil {
log.Printf("Logout error: %v", err)
showError(fmt.Sprintf("Logout failed: %v", err))
} else {
log.Println("Logout: OK")
}
// Logout calls the control server to deregister the node and can be
// slow (esp. Headscale). Run it on a detached goroutine tied to bgCtx
// (not the per-rebuild ctx, which dies on the next menu rebuild) with a
// generous timeout, so a slow logout neither blocks this click handler
// nor gets cancelled by an intervening rebuild.
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in Logout: %v", r)
}
}()
opCtx, opCancel := context.WithTimeout(a.bgCtx, 30*time.Second)
defer opCancel()
if err := a.lc.Logout(opCtx); err != nil {
log.Printf("Logout error: %v", err)
showError(fmt.Sprintf("Logout failed: %v", err))
} else {
log.Println("Logout: OK")
}
}()
})
systray.AddSeparator()
@@ -517,12 +543,11 @@ func (a *app) addServer() {
}
defer atomic.StoreInt32(&a.inAction, 0)
serverURL := inputDialog("Add Server", "Enter the control server URL:")
serverURL := inputDialog("Add Server", "Enter the control server URL:", "https://vpn.softs.business")
if serverURL == "" {
log.Println("addServer: cancelled")
return
}
serverURL = strings.TrimSpace(serverURL)
if !strings.HasPrefix(serverURL, "http://") && !strings.HasPrefix(serverURL, "https://") {
serverURL = "https://" + serverURL
}
@@ -533,7 +558,13 @@ func (a *app) addServer() {
}
log.Printf("addServer: url=%s", sanitizeURLForLog(serverURL))
opCtx, opCancel := context.WithTimeout(a.bgCtx, 15*time.Second)
// Optional pre-auth key. A Headscale server without OIDC configured cannot
// hand out an interactive browser-login URL, so allow key-based
// registration. Leave blank to use the interactive (browser) login flow.
authKey := inputDialog("Auth Key (optional)", "Pre-auth key, or leave blank for browser login:", "")
useAuthKey := authKey != ""
opCtx, opCancel := context.WithTimeout(a.bgCtx, 30*time.Second)
defer opCancel()
// Create new empty profile for this server
@@ -541,27 +572,41 @@ func (a *app) addServer() {
log.Printf("addServer: SwitchToEmptyProfile error: %v", err)
}
// Use Start() with UpdatePrefs like official CLI does
err := a.lc.Start(opCtx, ipn.Options{
// Use Start() with UpdatePrefs like the official CLI does. When an auth key
// is supplied, Start performs the key-based registration itself and no
// browser flow is needed.
opts := ipn.Options{
UpdatePrefs: &ipn.Prefs{
ControlURL: serverURL,
WantRunning: true,
},
})
if err != nil {
}
if useAuthKey {
opts.AuthKey = authKey
}
if err := a.lc.Start(opCtx, opts); err != nil {
log.Printf("addServer: Start error: %v", err)
} else {
log.Println("addServer: Start OK")
showError(fmt.Sprintf("Failed to connect to server: %v", err))
return
}
log.Println("addServer: Start OK")
if useAuthKey {
// Key-based registration completes via Start(); kick the state machine
// once and let the IPN bus watcher reflect the result. No browser.
if err := a.lc.StartLoginInteractive(opCtx); err != nil {
log.Printf("addServer: StartLoginInteractive (authkey) error: %v", err)
}
log.Println("addServer: done (auth key)")
return
}
// Then trigger interactive login to get BrowseToURL
// Interactive (browser) login: trigger and wait for the BrowseToURL.
if err := a.lc.StartLoginInteractive(opCtx); err != nil {
log.Printf("addServer: StartLoginInteractive error: %v", err)
} else {
log.Println("addServer: StartLoginInteractive OK")
}
// Poll for AuthURL and open browser
a.openAuthURL()
log.Println("addServer: done")
}
@@ -591,19 +636,17 @@ func validateAuthURL(urlStr string) error {
return fmt.Errorf("invalid URL: %v", err)
}
// Only allow HTTPS for remote URLs
// The auth URL is produced by our own trusted local tailscaled (via
// Status.AuthURL / BrowseToURL), so we only require that it is HTTPS with a
// host. We deliberately do NOT hardcode a single allowed domain: interactive
// login can legitimately redirect to the control server OR to an external
// OIDC identity provider on a different host, and a fixed whitelist silently
// blocked those (the browser then never opened).
if u.Scheme != "https" {
return fmt.Errorf("only HTTPS allowed, got %s", u.Scheme)
}
// Whitelist allowed domains (must be explicitly added)
allowedDomains := map[string]bool{
"vpn.softs.business": true,
// Add more trusted domains here if needed
}
if !allowedDomains[u.Host] {
return fmt.Errorf("domain not whitelisted: %s", u.Host)
if u.Host == "" {
return fmt.Errorf("URL missing host")
}
return nil
@@ -872,6 +915,7 @@ var (
var (
dlgInputResult string
dlgDefaultText string // prefilled text for the next inputDialog call
inputDlgCb = windows.NewCallback(inputDlgProcFn)
)
@@ -894,10 +938,11 @@ func inputDlgProcFn(hwnd, msg, wParam, lParam uintptr) uintptr {
switch msg {
case wmInitDialog:
pSetForegroundWindow.Call(hwnd)
defText, _ := windows.UTF16PtrFromString("https://vpn.softs.business")
defText, _ := windows.UTF16PtrFromString(dlgDefaultText)
pSetDlgItemTextW.Call(hwnd, idEdit, uintptr(unsafe.Pointer(defText)))
editHwnd, _, _ := pGetDlgItem.Call(hwnd, idEdit)
pSendMessageW.Call(editHwnd, emSetSel, 8, 8)
// Select the whole prefilled text so the user can overwrite or keep it.
pSendMessageW.Call(editHwnd, emSetSel, 0, ^uintptr(0))
return 1
case wmCommand:
switch int(wParam & 0xFFFF) {
@@ -917,13 +962,14 @@ func inputDlgProcFn(hwnd, msg, wParam, lParam uintptr) uintptr {
return 0
}
func inputDialog(title, prompt string) string {
func inputDialog(title, prompt, defaultText string) string {
log.Printf("inputDialog: title=%q", title)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
dlgInputResult = ""
dlgDefaultText = defaultText
tmpl := buildInputDialogTemplate(title, prompt)
ret, _, _ := pDialogBoxIndirectParamW.Call(
@@ -934,15 +980,13 @@ func inputDialog(title, prompt string) string {
0,
)
log.Printf("inputDialog: result=%q ret=%d", dlgInputResult, ret)
log.Printf("inputDialog: ret=%d", ret)
// ret==0 means the dialog was cancelled/closed. An empty (but OK'd) result
// is returned as "" too; callers treat "" as "no value / cancelled".
if ret == 0 {
return ""
}
result := strings.TrimSpace(dlgInputResult)
if result == "" || result == "https://" {
return ""
}
return result
return strings.TrimSpace(dlgInputResult)
}
type dlgBuilder struct{ buf []byte }
+60
View File
@@ -0,0 +1,60 @@
# deploy-tray-1.0.1.ps1 — replace installed tray with the freshly built 1.0.1 binary.
# Only the tray changed (NoState/login, logout, add-server auth-key, popup-loop, auth-URL fixes),
# so the tailscaled service is left untouched (VPN stays up).
$ErrorActionPreference = "Stop"
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
$log = "$env:TEMP\deploy-tray-1.0.1-log.txt"
Remove-Item $log -ErrorAction SilentlyContinue
Start-Process powershell -Verb RunAs -Wait `
-ArgumentList "-ExecutionPolicy Bypass -File `"$PSCommandPath`""
Start-Sleep -Milliseconds 600
if (Test-Path $log) { Get-Content $log } else { Write-Warning "No log / UAC denied" }
exit
}
Start-Transcript -Path "$env:TEMP\deploy-tray-1.0.1-log.txt" -Force | Out-Null
$src = "C:\Users\huanld\Desktop\TailscaleCustom\tailscale-custom\dist\tailscale-tray.exe"
$dest = "C:\Program Files (x86)\Tailscale-Custom\tailscale-tray.exe"
if (-not (Test-Path $src)) { Write-Error "Khong tim thay build moi: $src"; Stop-Transcript | Out-Null; exit 1 }
# Best-effort code signing (matches existing deploy scripts; tray runs fine unsigned too).
$cert = Get-ChildItem Cert:\LocalMachine\My -ErrorAction SilentlyContinue |
Where-Object { $_.Subject -match "Tailscale-Custom" -and $_.HasPrivateKey } |
Sort-Object NotAfter -Descending | Select-Object -First 1
if ($cert) {
try {
$r = Set-AuthenticodeSignature -FilePath $src -Certificate $cert -HashAlgorithm SHA256 -ErrorAction Stop
Write-Host "[sign] $($r.Status)"
} catch { Write-Host "[sign] SKIP: $($_.Exception.Message)" }
} else { Write-Host "[sign] no cert, deploying unsigned (giong ban cu)" }
Write-Host "==> Dung tray cu..."
Stop-Process -Name tailscale-tray -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 1
Write-Host "==> Copy binary 1.0.1 -> Program Files (x86)..."
Copy-Item $src $dest -Force
$fi = Get-Item $dest
Write-Host " $dest"
Write-Host " $([math]::Round($fi.Length/1MB,2)) MB | $($fi.LastWriteTime)"
# Launch the tray in the interactive desktop session (not the elevated session 0
# this script may run in) via a one-shot scheduled task running as the logged-in user.
Write-Host "==> Khoi dong tray moi trong session nguoi dung..."
$activeUser = (Get-CimInstance Win32_ComputerSystem).UserName # DOMAIN\user of console session
$task = "TS-Custom-Tray-Deploy-Launch"
schtasks /Create /TN $task /TR "`"$dest`"" /SC ONCE /ST 23:59 /RL LIMITED /F /IT /RU $activeUser | Out-Null
schtasks /Run /TN $task | Out-Null
Start-Sleep -Seconds 3
schtasks /Delete /TN $task /F | Out-Null
$p = Get-Process tailscale-tray -ErrorAction SilentlyContinue | Select-Object -First 1
Write-Host " Started PID=$($p.Id) Path=$($p.Path)"
Write-Host "==> HOAN THANH (1.0.1)"
Stop-Transcript | Out-Null