From 6d951574421fb6787766bb78bab235f3f1b15d42 Mon Sep 17 00:00:00 2001 From: huanld Date: Sun, 31 May 2026 14:49:39 +0700 Subject: [PATCH] chore(agent): clean up internal .agent config and workflow files 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) --- .agent/.antigravity-version | 2 +- .agent/ARCHITECTURE.md | 14 +- .agent/agent-db.json | 2128 ----------------- .agent/agents/module-yarp-audit.agent.md | 149 -- .agent/rules/GEMINI.md | 54 - .agent/rules/antigravityrules | 218 -- .agent/rules/copilot-instructions.md | 170 -- .agent/rules/cursorrules | 86 - .agent/rules/test2.txt | 1 - .agent/rules/windsurfrules | 86 - .agent/skills/coding-levels/SKILL.md | 93 - .agent/test1.txt | 1 - .agent/uploads/1773926284401_audit.md | 123 - .../1773926307026_audit_service_variants.md | 693 ------ .../uploads/1773926322631_audit_log_review.md | 323 --- .../uploads/1773926334916_audit_log_flow.md | 942 -------- .../Disk_Cleanup_Workflow_Knowledge.md | 69 - .agent/workflows/audit.md | 123 - .agent/workflows/backup.md | 249 -- .../blazor_wasm_compression_optimization.md | 118 - .agent/workflows/decrypt-forge.md | 37 - .agent/workflows/deploy-ifc-converter.md | 52 - .agent/workflows/docker-deploy.md | 185 -- .agent/workflows/initial.md | 39 - .agent/workflows/ins-develop.md | 225 -- .agent/workflows/prepair-ifc-workflow.md | 52 - Tailscale-Custom-Setup.wixpdb | Bin 13229 -> 13344 bytes 27 files changed, 7 insertions(+), 6225 deletions(-) delete mode 100644 .agent/agent-db.json delete mode 100644 .agent/agents/module-yarp-audit.agent.md delete mode 100644 .agent/rules/antigravityrules delete mode 100644 .agent/rules/copilot-instructions.md delete mode 100644 .agent/rules/cursorrules delete mode 100644 .agent/rules/test2.txt delete mode 100644 .agent/rules/windsurfrules delete mode 100644 .agent/skills/coding-levels/SKILL.md delete mode 100644 .agent/test1.txt delete mode 100644 .agent/uploads/1773926284401_audit.md delete mode 100644 .agent/uploads/1773926307026_audit_service_variants.md delete mode 100644 .agent/uploads/1773926322631_audit_log_review.md delete mode 100644 .agent/uploads/1773926334916_audit_log_flow.md delete mode 100644 .agent/workflows/Disk_Cleanup_Workflow_Knowledge.md delete mode 100644 .agent/workflows/audit.md delete mode 100644 .agent/workflows/backup.md delete mode 100644 .agent/workflows/blazor_wasm_compression_optimization.md delete mode 100644 .agent/workflows/decrypt-forge.md delete mode 100644 .agent/workflows/deploy-ifc-converter.md delete mode 100644 .agent/workflows/docker-deploy.md delete mode 100644 .agent/workflows/initial.md delete mode 100644 .agent/workflows/ins-develop.md delete mode 100644 .agent/workflows/prepair-ifc-workflow.md diff --git a/.agent/.antigravity-version b/.agent/.antigravity-version index c157d3e37..afaf360d3 100644 --- a/.agent/.antigravity-version +++ b/.agent/.antigravity-version @@ -1 +1 @@ -639120951971372439 \ No newline at end of file +1.0.0 \ No newline at end of file diff --git a/.agent/ARCHITECTURE.md b/.agent/ARCHITECTURE.md index 4f700e0cc..99ca60a1b 100644 --- a/.agent/ARCHITECTURE.md +++ b/.agent/ARCHITECTURE.md @@ -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 | diff --git a/.agent/agent-db.json b/.agent/agent-db.json deleted file mode 100644 index 65212d397..000000000 --- a/.agent/agent-db.json +++ /dev/null @@ -1,2128 +0,0 @@ -{ - "version": "1.0", - "agents": [ - { - "type": "agent", - "name": "backend-specialist", - "description": "Expert backend architect for Node.js, Python, and modern serverless/edge systems. Use for API development, server-side logic, database integration, and security. Triggers on backend, server, api, endpoint, database, auth.", - "keywords": [ - "api", - "architect", - "auth.", - "backend", - "database", - "development", - "edge", - "endpoint", - "expert", - "integration", - "logic", - "modern", - "node.js", - "python", - "security.", - "server", - "serverless", - "side", - "specialist", - "systems.", - "triggers" - ], - "skills": [ - "clean-code", - "nodejs-best-practices", - "python-patterns", - "api-patterns", - "database-design", - "mcp-builder", - "lint-and-validate", - "powershell-windows", - "bash-linux", - "rust-pro" - ], - "file": "agents/backend-specialist.md", - "id": 1 - }, - { - "type": "agent", - "name": "code-archaeologist", - "description": "Expert in legacy code, refactoring, and understanding undocumented systems. Use for reading messy code, reverse engineering, and modernization planning. Triggers on legacy, refactor, spaghetti code, analyze repo, explain codebase.", - "keywords": [ - "analyze", - "archaeologist", - "code", - "codebase.", - "engineering", - "expert", - "explain", - "legacy", - "messy", - "modernization", - "planning.", - "reading", - "refactor", - "refactoring", - "repo", - "reverse", - "spaghetti", - "systems.", - "triggers", - "understanding", - "undocumented" - ], - "skills": [ - "clean-code", - "refactoring-patterns", - "code-review-checklist" - ], - "file": "agents/code-archaeologist.md", - "id": 2 - }, - { - "type": "agent", - "name": "database-architect", - "description": "Expert database architect for schema design, query optimization, migrations, and modern serverless databases. Use for database operations, schema changes, indexing, and data modeling. Triggers on database, sql, schema, migration, query, postgres, index, table.", - "keywords": [ - "architect", - "changes", - "data", - "database", - "databases.", - "design", - "expert", - "index", - "indexing", - "migration", - "migrations", - "modeling.", - "modern", - "operations", - "optimization", - "postgres", - "query", - "schema", - "serverless", - "sql", - "table.", - "triggers" - ], - "skills": [ - "clean-code", - "database-design" - ], - "file": "agents/database-architect.md", - "id": 3 - }, - { - "type": "agent", - "name": "debugger", - "description": "Expert in systematic debugging, root cause analysis, and crash investigation. Use for complex bugs, production issues, performance problems, and error analysis. Triggers on bug, error, crash, not working, broken, investigate, fix.", - "keywords": [ - "analysis", - "analysis.", - "broken", - "bug", - "bugs", - "cause", - "complex", - "crash", - "debugger", - "debugging", - "error", - "expert", - "fix.", - "investigate", - "investigation.", - "issues", - "performance", - "problems", - "production", - "root", - "systematic", - "triggers", - "working" - ], - "skills": [ - "clean-code", - "systematic-debugging" - ], - "file": "agents/debugger.md", - "id": 4 - }, - { - "type": "agent", - "name": "devops-engineer", - "description": "Expert in deployment, server management, CI/CD, and production operations. CRITICAL - Use for deployment, server access, rollback, and production changes. HIGH RISK operations. Triggers on deploy, production, server, pm2, ssh, release, rollback, ci/cd.", - "keywords": [ - "access", - "cd.", - "changes.", - "critical", - "deploy", - "deployment", - "devops", - "engineer", - "expert", - "high", - "management", - "operations.", - "pm2", - "production", - "release", - "risk", - "rollback", - "server", - "ssh", - "triggers" - ], - "skills": [ - "clean-code", - "deployment-procedures", - "server-management", - "powershell-windows", - "bash-linux" - ], - "file": "agents/devops-engineer.md", - "id": 5 - }, - { - "type": "agent", - "name": "documentation-writer", - "description": "Expert in technical documentation. Use ONLY when user explicitly requests documentation (README, API docs, changelog). DO NOT auto-invoke during normal development.", - "keywords": [ - "api", - "auto", - "changelog", - "development.", - "docs", - "documentation", - "documentation.", - "during", - "expert", - "explicitly", - "invoke", - "normal", - "readme", - "requests", - "technical", - "user", - "writer" - ], - "skills": [ - "clean-code", - "documentation-templates" - ], - "file": "agents/documentation-writer.md", - "id": 6 - }, - { - "type": "agent", - "name": "explorer-agent", - "description": "Advanced codebase discovery, deep architectural analysis, and proactive research agent. The eyes and ears of the framework. Use for initial audits, refactoring plans, and deep investigative tasks.", - "keywords": [ - "advanced", - "agent", - "agent.", - "analysis", - "architectural", - "audits", - "codebase", - "deep", - "discovery", - "ears", - "explorer", - "eyes", - "framework.", - "initial", - "investigative", - "plans", - "proactive", - "refactoring", - "research", - "tasks." - ], - "skills": [ - "clean-code", - "architecture", - "plan-writing", - "brainstorming", - "systematic-debugging" - ], - "file": "agents/explorer-agent.md", - "id": 7 - }, - { - "type": "agent", - "name": "frontend-specialist", - "description": "Senior Frontend Architect who builds maintainable React/Next.js systems with performance-first mindset. Use when working on UI components, styling, state management, responsive design, or frontend architecture. Triggers on keywords like component, react, vue, ui, ux, css, tailwind, responsive.", - "keywords": [ - "architect", - "architecture.", - "builds", - "component", - "components", - "css", - "design", - "first", - "frontend", - "keywords", - "maintainable", - "management", - "mindset.", - "next.js", - "performance", - "react", - "responsive", - "responsive.", - "senior", - "specialist", - "state", - "styling", - "systems", - "tailwind", - "triggers", - "vue", - "working" - ], - "skills": [ - "clean-code", - "nextjs-react-expert", - "web-design-guidelines", - "tailwind-patterns", - "frontend-design", - "lint-and-validate" - ], - "file": "agents/frontend-specialist.md", - "id": 8 - }, - { - "type": "agent", - "name": "game-developer", - "description": "Game development across all platforms (PC, Web, Mobile, VR/AR). Use when building games with Unity, Godot, Unreal, Phaser, Three.js, or any game engine. Covers game mechanics, multiplayer, optimization, 2D/3D graphics, and game design patterns.", - "keywords": [ - "across", - "building", - "covers", - "design", - "developer", - "development", - "engine.", - "game", - "games", - "godot", - "graphics", - "mechanics", - "mobile", - "multiplayer", - "optimization", - "patterns.", - "phaser", - "platforms", - "three.js", - "unity", - "unreal", - "web" - ], - "skills": [ - "clean-code", - "game-development", - "game-development/pc-games", - "game-development/web-games", - "game-development/mobile-games", - "game-development/game-design", - "game-development/multiplayer", - "game-development/vr-ar", - "game-development/2d-games", - "game-development/3d-games", - "game-development/game-art", - "game-development/game-audio" - ], - "file": "agents/game-developer.md", - "id": 9 - }, - { - "type": "agent", - "name": "mobile-developer", - "description": "Expert in React Native and Flutter mobile development. Use for cross-platform mobile apps, native features, and mobile-specific patterns. Triggers on mobile, react native, flutter, ios, android, app store, expo.", - "keywords": [ - "android", - "app", - "apps", - "cross", - "developer", - "development.", - "expert", - "expo.", - "features", - "flutter", - "ios", - "mobile", - "native", - "patterns.", - "platform", - "react", - "specific", - "store", - "triggers" - ], - "skills": [ - "clean-code", - "mobile-design" - ], - "file": "agents/mobile-developer.md", - "id": 10 - }, - { - "type": "agent", - "name": "module-yarp-audit.agent", - "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.\"", - "keywords": [ - "audit", - "audit.agent", - "backend", - "behind", - "checking", - "checklist.", - "compliance.", - "config", - "deployment", - "gateway", - "integration", - "module", - "production", - "proxy", - "readiness", - "reverse", - "sso", - "validating", - "verifying", - "yarp" - ], - "skills": [], - "file": "agents/module-yarp-audit.agent.md", - "id": 11 - }, - { - "type": "agent", - "name": "orchestrator", - "description": "Multi-agent coordination and task orchestration. Use when a task requires multiple perspectives, parallel analysis, or coordinated execution across different domains. Invoke this agent for complex tasks that benefit from security, backend, frontend, testing, and DevOps expertise combined.", - "keywords": [ - "across", - "agent", - "analysis", - "backend", - "benefit", - "combined.", - "complex", - "coordinated", - "coordination", - "devops", - "different", - "domains.", - "execution", - "expertise", - "frontend", - "invoke", - "multi", - "multiple", - "orchestration.", - "orchestrator", - "parallel", - "perspectives", - "requires", - "security", - "task", - "tasks", - "testing" - ], - "skills": [ - "clean-code", - "parallel-agents", - "behavioral-modes", - "plan-writing", - "brainstorming", - "architecture", - "lint-and-validate", - "powershell-windows", - "bash-linux" - ], - "file": "agents/orchestrator.md", - "id": 12 - }, - { - "type": "agent", - "name": "penetration-tester", - "description": "Expert in offensive security, penetration testing, red team operations, and vulnerability exploitation. Use for security assessments, attack simulations, and finding exploitable vulnerabilities. Triggers on pentest, exploit, attack, hack, breach, pwn, redteam, offensive.", - "keywords": [ - "assessments", - "attack", - "breach", - "expert", - "exploit", - "exploitable", - "exploitation.", - "finding", - "hack", - "offensive", - "offensive.", - "operations", - "penetration", - "pentest", - "pwn", - "red", - "redteam", - "security", - "simulations", - "team", - "tester", - "testing", - "triggers", - "vulnerabilities.", - "vulnerability" - ], - "skills": [ - "clean-code", - "vulnerability-scanner", - "red-team-tactics", - "api-patterns" - ], - "file": "agents/penetration-tester.md", - "id": 13 - }, - { - "type": "agent", - "name": "performance-optimizer", - "description": "Expert in performance optimization, profiling, Core Web Vitals, and bundle optimization. Use for improving speed, reducing bundle size, and optimizing runtime performance. Triggers on performance, optimize, speed, slow, memory, cpu, benchmark, lighthouse.", - "keywords": [ - "benchmark", - "bundle", - "core", - "cpu", - "expert", - "improving", - "lighthouse.", - "memory", - "optimization", - "optimization.", - "optimize", - "optimizer", - "optimizing", - "performance", - "performance.", - "profiling", - "reducing", - "runtime", - "size", - "slow", - "speed", - "triggers", - "vitals", - "web" - ], - "skills": [ - "clean-code", - "performance-profiling" - ], - "file": "agents/performance-optimizer.md", - "id": 14 - }, - { - "type": "agent", - "name": "product-manager", - "description": "Expert in product requirements, user stories, and acceptance criteria. Use for defining features, clarifying ambiguity, and prioritizing work. Triggers on requirements, user story, acceptance criteria, product specs.", - "keywords": [ - "acceptance", - "ambiguity", - "clarifying", - "criteria", - "criteria.", - "defining", - "expert", - "features", - "manager", - "prioritizing", - "product", - "requirements", - "specs.", - "stories", - "story", - "triggers", - "user", - "work." - ], - "skills": [ - "plan-writing", - "brainstorming", - "clean-code" - ], - "file": "agents/product-manager.md", - "id": 15 - }, - { - "type": "agent", - "name": "product-owner", - "description": "Strategic facilitator bridging business needs and technical execution. Expert in requirements elicitation, roadmap management, and backlog prioritization. Triggers on requirements, user story, backlog, MVP, PRD, stakeholder.", - "keywords": [ - "backlog", - "bridging", - "business", - "elicitation", - "execution.", - "expert", - "facilitator", - "management", - "mvp", - "needs", - "owner", - "prd", - "prioritization.", - "product", - "requirements", - "roadmap", - "stakeholder.", - "story", - "strategic", - "technical", - "triggers", - "user" - ], - "skills": [ - "plan-writing", - "brainstorming", - "clean-code" - ], - "file": "agents/product-owner.md", - "id": 16 - }, - { - "type": "agent", - "name": "project-planner", - "description": "Smart project planning agent. Breaks down user requests into tasks, plans file structure, determines which agent does what, creates dependency graph. Use when starting new projects or planning major features.", - "keywords": [ - "agent", - "agent.", - "breaks", - "creates", - "dependency", - "determines", - "down", - "features.", - "file", - "graph.", - "major", - "planner", - "planning", - "plans", - "project", - "projects", - "requests", - "smart", - "starting", - "structure", - "tasks", - "user" - ], - "skills": [ - "clean-code", - "app-builder", - "plan-writing", - "brainstorming" - ], - "file": "agents/project-planner.md", - "id": 17 - }, - { - "type": "agent", - "name": "qa-automation-engineer", - "description": "Specialist in test automation infrastructure and E2E testing. Focuses on Playwright, Cypress, CI pipelines, and breaking the system. Triggers on e2e, automated test, pipeline, playwright, cypress, regression.", - "keywords": [ - "automated", - "automation", - "breaking", - "cypress", - "e2e", - "engineer", - "focuses", - "infrastructure", - "pipeline", - "pipelines", - "playwright", - "regression.", - "specialist", - "system.", - "test", - "testing.", - "triggers" - ], - "skills": [ - "webapp-testing", - "testing-patterns", - "web-design-guidelines", - "clean-code", - "lint-and-validate" - ], - "file": "agents/qa-automation-engineer.md", - "id": 18 - }, - { - "type": "agent", - "name": "security-auditor", - "description": "Elite cybersecurity expert. Think like an attacker, defend like an expert. OWASP 2025, supply chain security, zero trust architecture. Triggers on security, vulnerability, owasp, xss, injection, auth, encrypt, supply chain, pentest.", - "keywords": [ - "architecture.", - "attacker", - "auditor", - "auth", - "chain", - "cybersecurity", - "defend", - "elite", - "encrypt", - "expert.", - "injection", - "owasp", - "pentest.", - "security", - "supply", - "think", - "triggers", - "trust", - "vulnerability", - "xss", - "zero" - ], - "skills": [ - "clean-code", - "vulnerability-scanner", - "red-team-tactics", - "api-patterns" - ], - "file": "agents/security-auditor.md", - "id": 19 - }, - { - "type": "agent", - "name": "seo-specialist", - "description": "SEO and GEO (Generative Engine Optimization) expert. Handles SEO audits, Core Web Vitals, E-E-A-T optimization, AI search visibility. Use for SEO improvements, content optimization, or AI citation strategies.", - "keywords": [ - "audits", - "citation", - "content", - "core", - "engine", - "expert.", - "generative", - "geo", - "handles", - "improvements", - "optimization", - "search", - "seo", - "specialist", - "strategies.", - "visibility.", - "vitals", - "web" - ], - "skills": [ - "clean-code", - "seo-fundamentals", - "geo-fundamentals" - ], - "file": "agents/seo-specialist.md", - "id": 20 - }, - { - "type": "agent", - "name": "test-engineer", - "description": "Expert in testing, TDD, and test automation. Use for writing tests, improving coverage, debugging test failures. Triggers on test, spec, coverage, jest, pytest, playwright, e2e, unit test.", - "keywords": [ - "automation.", - "coverage", - "debugging", - "e2e", - "engineer", - "expert", - "failures.", - "improving", - "jest", - "playwright", - "pytest", - "spec", - "tdd", - "test", - "test.", - "testing", - "tests", - "triggers", - "unit", - "writing" - ], - "skills": [ - "clean-code", - "testing-patterns", - "tdd-workflow", - "webapp-testing", - "code-review-checklist", - "lint-and-validate" - ], - "file": "agents/test-engineer.md", - "id": 21 - } - ], - "skills": [ - { - "type": "skill", - "name": "api-patterns", - "description": "API design principles and decision-making. REST vs GraphQL vs tRPC selection, response formats, versioning, pagination.", - "keywords": [ - "api", - "decision", - "design", - "formats", - "graphql", - "making.", - "pagination.", - "patterns", - "principles", - "response", - "rest", - "selection", - "trpc", - "versioning" - ], - "skills": [], - "file": "skills/api-patterns/SKILL.md", - "id": 22 - }, - { - "type": "skill", - "name": "app-builder", - "description": "Main application building orchestrator. Creates full-stack applications from natural language requests. Determines project type, selects tech stack, coordinates agents.", - "keywords": [ - "agents.", - "app", - "application", - "applications", - "builder", - "building", - "coordinates", - "creates", - "detection", - "determines", - "full", - "keyword", - "language", - "main", - "matrix", - "natural", - "orchestrator.", - "project", - "requests.", - "selects", - "stack", - "starting", - "tech", - "type" - ], - "skills": [], - "file": "skills/app-builder/SKILL.md", - "id": 23 - }, - { - "type": "skill", - "name": "architecture", - "description": "Architectural decision-making framework. Requirements analysis, trade-off evaluation, ADR documentation. Use when making architecture decisions or analyzing system design.", - "keywords": [ - "adr", - "analysis", - "analyzing", - "architectural", - "architecture", - "decision", - "decisions", - "design.", - "documentation.", - "evaluation", - "framework.", - "making", - "off", - "requirements", - "system", - "trade" - ], - "skills": [], - "file": "skills/architecture/SKILL.md", - "id": 24 - }, - { - "type": "skill", - "name": "bash-linux", - "description": "Bash/Linux terminal patterns. Critical commands, piping, error handling, scripting. Use when working on macOS or Linux systems.", - "keywords": [ - "bash", - "commands", - "critical", - "error", - "handling", - "linux", - "macos", - "patterns.", - "piping", - "scripting.", - "systems.", - "terminal", - "working" - ], - "skills": [], - "file": "skills/bash-linux/SKILL.md", - "id": 25 - }, - { - "type": "skill", - "name": "behavioral-modes", - "description": "AI operational modes (brainstorm, implement, debug, review, teach, ship, orchestrate). Use to adapt behavior based on task type.", - "keywords": [ - "adapt", - "based", - "behavior", - "behavioral", - "brainstorm", - "debug", - "implement", - "modes", - "operational", - "orchestrate", - "review", - "ship", - "task", - "teach", - "type." - ], - "skills": [], - "file": "skills/behavioral-modes/SKILL.md", - "id": 26 - }, - { - "type": "skill", - "name": "brainstorming", - "description": "Socratic questioning protocol + user communication. MANDATORY for complex requests, new features, or unclear requirements. Includes progress reporting and error handling.", - "keywords": [ - "brainstorming", - "communication.", - "complex", - "error", - "features", - "handling.", - "includes", - "mandatory", - "progress", - "protocol", - "questioning", - "reporting", - "requests", - "requirements.", - "socratic", - "unclear", - "updates", - "user", - "vague" - ], - "skills": [], - "file": "skills/brainstorming/SKILL.md", - "id": 27 - }, - { - "type": "skill", - "name": "clean-code", - "description": "Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments", - "keywords": [ - "clean", - "code", - "coding", - "comments", - "concise", - "direct", - "engineering", - "pragmatic", - "standards", - "unnecessary" - ], - "skills": [], - "file": "skills/clean-code/SKILL.md", - "id": 28 - }, - { - "type": "skill", - "name": "code-review-checklist", - "description": "Code review guidelines covering code quality, security, and best practices.", - "keywords": [ - "best", - "checklist", - "code", - "covering", - "guidelines", - "practices.", - "quality", - "review", - "security" - ], - "skills": [], - "file": "skills/code-review-checklist/SKILL.md", - "id": 29 - }, - { - "type": "skill", - "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": [ - "accordingly.", - "adaptive", - "adjusts", - "auto", - "beginner", - "code.", - "coding", - "communication", - "detects", - "experience", - "expert", - "explanation", - "friendly", - "level", - "levels", - "mode", - "output", - "style", - "system.", - "teaching", - "user", - "zero" - ], - "skills": [], - "file": "skills/coding-levels/SKILL.md", - "id": 30 - }, - { - "type": "skill", - "name": "database-design", - "description": "Database design principles and decision-making. Schema design, indexing strategy, ORM selection, serverless databases.", - "keywords": [ - "database", - "databases.", - "decision", - "design", - "indexing", - "making.", - "orm", - "principles", - "schema", - "selection", - "serverless", - "strategy" - ], - "skills": [], - "file": "skills/database-design/SKILL.md", - "id": 31 - }, - { - "type": "skill", - "name": "deployment-procedures", - "description": "Production deployment principles and decision-making. Safe deployment workflows, rollback strategies, and verification. Teaches thinking, not scripts.", - "keywords": [ - "decision", - "deployment", - "making.", - "principles", - "procedures", - "production", - "rollback", - "safe", - "scripts.", - "strategies", - "teaches", - "thinking", - "verification.", - "workflows" - ], - "skills": [], - "file": "skills/deployment-procedures/SKILL.md", - "id": 32 - }, - { - "type": "skill", - "name": "documentation-templates", - "description": "Documentation templates and structure guidelines. README, API docs, code comments, and AI-friendly documentation.", - "keywords": [ - "api", - "code", - "comments", - "docs", - "documentation", - "documentation.", - "friendly", - "guidelines.", - "readme", - "structure", - "templates" - ], - "skills": [], - "file": "skills/documentation-templates/SKILL.md", - "id": 33 - }, - { - "type": "skill", - "name": "frontend-design", - "description": "Design thinking and decision-making for web UI. Use when designing components, layouts, color schemes, typography, or creating aesthetic interfaces. Teaches principles, not fixed values.", - "keywords": [ - "aesthetic", - "color", - "components", - "creating", - "decision", - "design", - "designing", - "fixed", - "frontend", - "harmony", - "interfaces.", - "layouts", - "making", - "principles", - "proportional", - "schemes", - "teaches", - "thinking", - "typography", - "ui.", - "values.", - "web" - ], - "skills": [], - "file": "skills/frontend-design/SKILL.md", - "id": 34 - }, - { - "type": "skill", - "name": "game-development", - "description": "Game development orchestrator. Routes to platform-specific skills based on project needs.", - "keywords": [ - "based", - "development", - "example", - "game", - "needs.", - "orchestrator.", - "platform", - "project", - "routes", - "skills", - "specific" - ], - "skills": [], - "file": "skills/game-development/SKILL.md", - "id": 35 - }, - { - "type": "skill", - "name": "geo-fundamentals", - "description": "Generative Engine Optimization for AI search engines (ChatGPT, Claude, Perplexity).", - "keywords": [ - "chatgpt", - "claude", - "data", - "engine", - "engines", - "entities", - "fundamentals", - "generative", - "geo", - "keyword", - "keywords", - "match", - "optimization", - "perplexity", - "search" - ], - "skills": [], - "file": "skills/geo-fundamentals/SKILL.md", - "id": 36 - }, - { - "type": "skill", - "name": "i18n-localization", - "description": "Internationalization and localization patterns. Detecting hardcoded strings, managing translations, locale files, RTL support.", - "keywords": [ - "detecting", - "files", - "hardcoded", - "i18n", - "internationalization", - "locale", - "localization", - "managing", - "patterns.", - "rtl", - "strings", - "support.", - "translations" - ], - "skills": [], - "file": "skills/i18n-localization/SKILL.md", - "id": 37 - }, - { - "type": "skill", - "name": "intelligent-routing", - "description": "Automatic agent selection and intelligent task routing. Analyzes user requests and automatically selects the best specialist agent(s) without requiring explicit user mentions.", - "keywords": [ - "agent", - "analyzes", - "auto", - "automatic", - "automatically", - "best", - "explicit", - "intelligent", - "invoke", - "keywords", - "mentions.", - "requests", - "requiring", - "routing", - "routing.", - "selected", - "selection", - "selects", - "specialist", - "task", - "user", - "without" - ], - "skills": [], - "file": "skills/intelligent-routing/SKILL.md", - "id": 38 - }, - { - "type": "skill", - "name": "lint-and-validate", - "description": "Automatic quality control, linting, and static analysis procedures. Use after every code modification to ensure syntax correctness and project standards. Triggers onKeywords: lint, format, check, validate, types, static analysis.", - "keywords": [ - "after", - "analysis", - "analysis.", - "and", - "automatic", - "check", - "code", - "control", - "correctness", - "ensure", - "every", - "format", - "lint", - "linting", - "modification", - "onkeywords", - "procedures.", - "project", - "quality", - "standards.", - "static", - "syntax", - "triggers", - "types", - "validate" - ], - "skills": [], - "file": "skills/lint-and-validate/SKILL.md", - "id": 39 - }, - { - "type": "skill", - "name": "mcp-builder", - "description": "MCP (Model Context Protocol) server building principles. Tool design, resource patterns, best practices.", - "keywords": [ - "best", - "builder", - "building", - "context", - "design", - "mcp", - "model", - "patterns", - "practices.", - "principles.", - "protocol", - "resource", - "server", - "tool" - ], - "skills": [], - "file": "skills/mcp-builder/SKILL.md", - "id": 40 - }, - { - "type": "skill", - "name": "mobile-design", - "description": "Mobile-first design thinking and decision-making for iOS and Android apps. Touch interaction, performance patterns, platform conventions. Teaches principles, not fixed values. Use when building React Native, Flutter, or native mobile apps.", - "keywords": [ - "android", - "apps.", - "building", - "conventions.", - "decision", - "design", - "first", - "fixed", - "flutter", - "interaction", - "ios", - "making", - "mobile", - "native", - "patterns", - "performance", - "platform", - "principles", - "react", - "teaches", - "thinking", - "touch", - "values." - ], - "skills": [], - "file": "skills/mobile-design/SKILL.md", - "id": 41 - }, - { - "type": "skill", - "name": "react-best-practices", - "description": "React and Next.js performance optimization from Vercel Engineering. Use when building React components, optimizing performance, eliminating waterfalls, reducing bundle size, reviewing code for performance issues, or implementing server/client-side optimizations.", - "keywords": [ - "best", - "building", - "bundle", - "client", - "code", - "components", - "eliminating", - "engineering.", - "implementing", - "issues", - "next.js", - "optimization", - "optimizations.", - "optimizing", - "performance", - "practices", - "react", - "reducing", - "reviewing", - "server", - "side", - "size", - "vercel", - "waterfalls" - ], - "skills": [], - "file": "skills/nextjs-react-expert/SKILL.md", - "id": 42 - }, - { - "type": "skill", - "name": "nodejs-best-practices", - "description": "Node.js development principles and decision-making. Framework selection, async patterns, security, and architecture. Teaches thinking, not copying.", - "keywords": [ - "architecture.", - "async", - "best", - "cases", - "codebases", - "copying.", - "decision", - "development", - "edge", - "existing", - "framework", - "making.", - "node.js", - "nodejs", - "patterns", - "practices", - "principles", - "projects", - "security", - "selection", - "teaches", - "thinking" - ], - "skills": [], - "file": "skills/nodejs-best-practices/SKILL.md", - "id": 43 - }, - { - "type": "skill", - "name": "parallel-agents", - "description": "Multi-agent orchestration patterns. Use when multiple independent tasks can run with different domain expertise or when comprehensive analysis requires multiple perspectives.", - "keywords": [ - "agent", - "agents", - "analysis", - "comprehensive", - "different", - "domain", - "expertise", - "independent", - "multi", - "multiple", - "orchestration", - "parallel", - "patterns.", - "perspectives.", - "requires", - "run", - "tasks" - ], - "skills": [], - "file": "skills/parallel-agents/SKILL.md", - "id": 44 - }, - { - "type": "skill", - "name": "performance-profiling", - "description": "Performance profiling principles. Measurement, analysis, and optimization techniques.", - "keywords": [ - "analysis", - "measurement", - "optimization", - "performance", - "principles.", - "profiling", - "techniques." - ], - "skills": [], - "file": "skills/performance-profiling/SKILL.md", - "id": 45 - }, - { - "type": "skill", - "name": "plan-writing", - "description": "Structured task planning with clear breakdowns, dependencies, and verification criteria. Use when implementing features, refactoring, or any multi-step work.", - "keywords": [ - "breakdowns", - "clear", - "criteria.", - "dependencies", - "features", - "implementing", - "multi", - "plan", - "planning", - "refactoring", - "step", - "structured", - "task", - "verification", - "work.", - "writing" - ], - "skills": [], - "file": "skills/plan-writing/SKILL.md", - "id": 46 - }, - { - "type": "skill", - "name": "powershell-windows", - "description": "PowerShell Windows patterns. Critical pitfalls, operator syntax, error handling.", - "keywords": [ - "critical", - "error", - "handling.", - "operator", - "patterns.", - "pitfalls", - "powershell", - "syntax", - "windows" - ], - "skills": [], - "file": "skills/powershell-windows/SKILL.md", - "id": 47 - }, - { - "type": "skill", - "name": "python-patterns", - "description": "Python development principles and decision-making. Framework selection, async patterns, type hints, project structure. Teaches thinking, not copying.", - "keywords": [ - "async", - "copying.", - "decision", - "development", - "framework", - "hints", - "making.", - "patterns", - "principles", - "project", - "python", - "selection", - "structure.", - "teaches", - "thinking", - "type" - ], - "skills": [], - "file": "skills/python-patterns/SKILL.md", - "id": 48 - }, - { - "type": "skill", - "name": "red-team-tactics", - "description": "Red team tactics principles based on MITRE ATT&CK. Attack phases, detection evasion, reporting.", - "keywords": [ - "att", - "attack", - "based", - "ck.", - "detection", - "evasion", - "mitre", - "phases", - "principles", - "red", - "reporting.", - "tactics", - "team" - ], - "skills": [], - "file": "skills/red-team-tactics/SKILL.md", - "id": 49 - }, - { - "type": "skill", - "name": "rust-pro", - "description": "Master Rust 1.75+ with modern async patterns, advanced type system", - "keywords": [ - "advanced", - "async", - "master", - "modern", - "patterns", - "pro", - "rust", - "system", - "type" - ], - "skills": [], - "file": "skills/rust-pro/SKILL.md", - "id": 50 - }, - { - "type": "skill", - "name": "seo-fundamentals", - "description": "SEO fundamentals, E-E-A-T, Core Web Vitals, and Google algorithm principles.", - "keywords": [ - "algorithm", - "core", - "front", - "fundamentals", - "google", - "keyword", - "principles.", - "seo", - "stuffing", - "vitals", - "web" - ], - "skills": [], - "file": "skills/seo-fundamentals/SKILL.md", - "id": 51 - }, - { - "type": "skill", - "name": "server-management", - "description": "Server management principles and decision-making. Process management, monitoring strategy, and scaling decisions. Teaches thinking, not commands.", - "keywords": [ - "commands.", - "decision", - "decisions.", - "making.", - "management", - "monitoring", - "principles", - "process", - "scaling", - "server", - "strategy", - "teaches", - "thinking" - ], - "skills": [], - "file": "skills/server-management/SKILL.md", - "id": 52 - }, - { - "type": "skill", - "name": "systematic-debugging", - "description": "4-phase systematic debugging methodology with root cause analysis and evidence-based verification. Use when debugging complex issues.", - "keywords": [ - "analysis", - "based", - "cause", - "complex", - "debugging", - "evidence", - "issues.", - "methodology", - "phase", - "root", - "systematic", - "verification." - ], - "skills": [], - "file": "skills/systematic-debugging/SKILL.md", - "id": 53 - }, - { - "type": "skill", - "name": "tailwind-patterns", - "description": "Tailwind CSS v4 principles. CSS-first configuration, container queries, modern patterns, design token architecture.", - "keywords": [ - "architecture.", - "configuration", - "container", - "css", - "design", - "first", - "modern", - "patterns", - "principles.", - "queries", - "tailwind", - "token" - ], - "skills": [], - "file": "skills/tailwind-patterns/SKILL.md", - "id": 54 - }, - { - "type": "skill", - "name": "tdd-workflow", - "description": "Test-Driven Development workflow principles. RED-GREEN-REFACTOR cycle.", - "keywords": [ - "cycle.", - "development", - "driven", - "green", - "principles.", - "red", - "refactor", - "tdd", - "test", - "workflow" - ], - "skills": [], - "file": "skills/tdd-workflow/SKILL.md", - "id": 55 - }, - { - "type": "skill", - "name": "testing-patterns", - "description": "Testing patterns and principles. Unit, integration, mocking strategies.", - "keywords": [ - "integration", - "mocking", - "patterns", - "principles.", - "strategies.", - "testing", - "unit" - ], - "skills": [], - "file": "skills/testing-patterns/SKILL.md", - "id": 56 - }, - { - "type": "skill", - "name": "vulnerability-scanner", - "description": "Advanced vulnerability analysis principles. OWASP 2025, Supply Chain Security, attack surface mapping, risk prioritization.", - "keywords": [ - "advanced", - "analysis", - "attack", - "chain", - "mapping", - "owasp", - "principles.", - "prioritization.", - "risk", - "scanner", - "security", - "supply", - "surface", - "vulnerability" - ], - "skills": [], - "file": "skills/vulnerability-scanner/SKILL.md", - "id": 57 - }, - { - "type": "skill", - "name": "web-design-guidelines", - "description": "Review UI code for Web Interface Guidelines compliance. Use when asked to \"review my UI\", \"check accessibility\", \"audit design\", \"review UX\", or \"check my site against best practices\".", - "keywords": [ - "accessibility", - "against", - "asked", - "audit", - "best", - "check", - "code", - "compliance.", - "design", - "guidelines", - "interface", - "practices", - "review", - "site", - "web" - ], - "skills": [], - "file": "skills/web-design-guidelines/SKILL.md", - "id": 58 - }, - { - "type": "skill", - "name": "webapp-testing", - "description": "Web application testing principles. E2E, Playwright, deep audit strategies.", - "keywords": [ - "application", - "audit", - "deep", - "e2e", - "playwright", - "principles.", - "strategies.", - "testing", - "web", - "webapp" - ], - "skills": [], - "file": "skills/webapp-testing/SKILL.md", - "id": 59 - } - ], - "workflows": [ - { - "type": "workflow", - "name": "/Disk_Cleanup_Workflow_Knowledge", - "description": "", - "keywords": [ - "disk_cleanup_workflow_knowledge" - ], - "skills": [], - "file": "workflows/Disk_Cleanup_Workflow_Knowledge.md", - "id": 60 - }, - { - "type": "workflow", - "name": "/audit", - "description": "Audit all features on a page/module like a picky user. Find and fix form issues, API silent failures, missing fields, hardcoded values.", - "keywords": [ - "api", - "audit", - "failures", - "features", - "fields", - "find", - "fix", - "form", - "hardcoded", - "issues", - "missing", - "module", - "page", - "picky", - "silent", - "user.", - "values." - ], - "skills": [], - "file": "workflows/audit.md", - "id": 61 - }, - { - "type": "workflow", - "name": "/backup", - "description": "Backup all workspaces by compressing to ZIP and uploading to Google Drive (G:\\My Drive\\Code_Backups)", - "keywords": [ - "backup", - "backups", - "code", - "compressing", - "drive", - "google", - "uploading", - "workspaces", - "zip" - ], - "skills": [], - "file": "workflows/backup.md", - "id": 62 - }, - { - "type": "workflow", - "name": "/blazor_wasm_compression_optimization", - "description": "", - "keywords": [ - "blazor_wasm_compression_optimization" - ], - "skills": [], - "file": "workflows/blazor_wasm_compression_optimization.md", - "id": 63 - }, - { - "type": "workflow", - "name": "/brainstorm", - "description": "Structured brainstorming for projects and features. Explores multiple options before implementation.", - "keywords": [ - "before", - "brainstorm", - "brainstorming", - "committing", - "explore", - "explores", - "features.", - "implementation", - "implementation.", - "multiple", - "need", - "options", - "projects", - "structured" - ], - "skills": [], - "file": "workflows/brainstorm.md", - "id": 64 - }, - { - "type": "workflow", - "name": "/create", - "description": "Create new application command. Triggers App Builder skill and starts interactive dialogue with user.", - "keywords": [ - "app", - "application", - "builder", - "command.", - "create", - "dialogue", - "interactive", - "skill", - "starts", - "triggers", - "user." - ], - "skills": [], - "file": "workflows/create.md", - "id": 65 - }, - { - "type": "workflow", - "name": "/debug", - "description": "Debugging command. Activates DEBUG mode for systematic problem investigation.", - "keywords": [ - "activates", - "command.", - "debug", - "debugging", - "investigation.", - "mode", - "problem", - "systematic" - ], - "skills": [], - "file": "workflows/debug.md", - "id": 66 - }, - { - "type": "workflow", - "name": "/decrypt-forge", - "description": "Decrypt Forge App ID & Secret from database using connection string in appsettings*.json of current workspace", - "keywords": [ - "app", - "appsettings", - "connection", - "current", - "database", - "decrypt", - "forge", - "json", - "secret", - "string", - "using", - "workspace" - ], - "skills": [], - "file": "workflows/decrypt-forge.md", - "id": 67 - }, - { - "type": "workflow", - "name": "/deploy-ifc-converter", - "description": "Pipeline and Template for deploying IFC 3D Viewer Converter into a new ATAD application", - "keywords": [ - "application", - "atad", - "converter", - "deploy", - "deploying", - "ifc", - "pipeline", - "template", - "viewer" - ], - "skills": [], - "file": "workflows/deploy-ifc-converter.md", - "id": 68 - }, - { - "type": "workflow", - "name": "/deploy", - "description": "Deployment command for production releases. Pre-flight checks and deployment execution.", - "keywords": [ - "checks", - "command", - "deploy", - "deployment", - "execution.", - "flight", - "pre", - "production", - "releases." - ], - "skills": [], - "file": "workflows/deploy.md", - "id": 69 - }, - { - "type": "workflow", - "name": "/docker-deploy", - "description": "Deploy any INS module to VPS with pre-flight connectivity checks, local build, and post-deploy verification", - "keywords": [ - "build", - "checks", - "connectivity", - "deploy", - "docker", - "flight", - "ins", - "local", - "module", - "post", - "pre", - "verification", - "vps" - ], - "skills": [], - "file": "workflows/docker-deploy.md", - "id": 70 - }, - { - "type": "workflow", - "name": "/enhance", - "description": "Add or update features in existing application. Used for iterative development.", - "keywords": [ - "add", - "application.", - "development.", - "enhance", - "existing", - "features", - "iterative", - "update", - "used" - ], - "skills": [], - "file": "workflows/enhance.md", - "id": 71 - }, - { - "type": "workflow", - "name": "/initial", - "description": "Initialize Workspace Memory Protocol for Antigravity", - "keywords": [ - "antigravity", - "initial", - "initialize", - "memory", - "protocol", - "workspace" - ], - "skills": [], - "file": "workflows/initial.md", - "id": 72 - }, - { - "type": "workflow", - "name": "/ins-develop", - "description": "INS Module feature development workflow. Full lifecycle from planning to verification. Standard for all INS modules (HRM, PRO, CDE, WJC, etc.)", - "keywords": [ - "cde", - "develop", - "development", - "etc.", - "feature", - "full", - "hrm", - "ins", - "lifecycle", - "module", - "modules", - "planning", - "pro", - "standard", - "verification.", - "wjc", - "workflow." - ], - "skills": [], - "file": "workflows/ins-develop.md", - "id": 73 - }, - { - "type": "workflow", - "name": "/orchestrate", - "description": "Coordinate multiple agents for complex tasks. Use for multi-perspective analysis, comprehensive reviews, or tasks requiring different domain expertise.", - "keywords": [ - "agents", - "analysis", - "complex", - "comprehensive", - "coordinate", - "different", - "domain", - "expertise.", - "multi", - "multiple", - "orchestrate", - "perspective", - "requiring", - "reviews", - "tasks", - "tasks." - ], - "skills": [], - "file": "workflows/orchestrate.md", - "id": 74 - }, - { - "type": "workflow", - "name": "/plan", - "description": "Create project plan using project-planner agent. No code writing - only plan file generation.", - "keywords": [ - "agent.", - "code", - "create", - "file", - "generation.", - "plan", - "planner", - "project", - "using", - "writing" - ], - "skills": [], - "file": "workflows/plan.md", - "id": 75 - }, - { - "type": "workflow", - "name": "/preview", - "description": "Preview server start, stop, and status check. Local development server management.", - "keywords": [ - "check.", - "development", - "local", - "management.", - "preview", - "server", - "start", - "status", - "stop" - ], - "skills": [], - "file": "workflows/preview.md", - "id": 76 - }, - { - "type": "workflow", - "name": "/status", - "description": "Display agent and project status. Progress tracking and status board.", - "keywords": [ - "agent", - "board.", - "display", - "progress", - "project", - "status", - "status.", - "tracking" - ], - "skills": [], - "file": "workflows/status.md", - "id": 77 - }, - { - "type": "workflow", - "name": "/test", - "description": "Test generation and test running command. Creates and executes tests for code.", - "keywords": [ - "code.", - "command.", - "creates", - "executes", - "generation", - "running", - "test", - "tests" - ], - "skills": [], - "file": "workflows/test.md", - "id": 78 - }, - { - "type": "workflow", - "name": "/ui-ux-pro-max", - "description": "Plan and implement UI", - "keywords": [ - "different", - "elegant", - "example", - "implement", - "insights", - "keywords", - "max", - "name", - "plan", - "pro", - "professional", - "reveal", - "soft", - "style" - ], - "skills": [], - "file": "workflows/ui-ux-pro-max.md", - "id": 79 - }, - { - "type": "workflow", - "name": "prepair-ifc-workflow", - "description": "Workflow for preparing IFC models, standardizing filenames, and syncing with APIs & FTP across ATAD software modules", - "keywords": [], - "skills": [], - "file": "workflows/prepair-ifc-workflow.md", - "id": 80 - } - ] -} \ No newline at end of file diff --git a/.agent/agents/module-yarp-audit.agent.md b/.agent/agents/module-yarp-audit.agent.md deleted file mode 100644 index 5ae4d22c4..000000000 --- a/.agent/agents/module-yarp-audit.agent.md +++ /dev/null @@ -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 | `` 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}" diff --git a/.agent/rules/GEMINI.md b/.agent/rules/GEMINI.md index 20b81e2b2..b35dccec3 100644 --- a/.agent/rules/GEMINI.md +++ b/.agent/rules/GEMINI.md @@ -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. diff --git a/.agent/rules/antigravityrules b/.agent/rules/antigravityrules deleted file mode 100644 index 91f545597..000000000 --- a/.agent/rules/antigravityrules +++ /dev/null @@ -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. diff --git a/.agent/rules/copilot-instructions.md b/.agent/rules/copilot-instructions.md deleted file mode 100644 index 4726528af..000000000 --- a/.agent/rules/copilot-instructions.md +++ /dev/null @@ -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. diff --git a/.agent/rules/cursorrules b/.agent/rules/cursorrules deleted file mode 100644 index 3b18ab32a..000000000 --- a/.agent/rules/cursorrules +++ /dev/null @@ -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. diff --git a/.agent/rules/test2.txt b/.agent/rules/test2.txt deleted file mode 100644 index 9dc724115..000000000 --- a/.agent/rules/test2.txt +++ /dev/null @@ -1 +0,0 @@ -Hello world from test2 \ No newline at end of file diff --git a/.agent/rules/windsurfrules b/.agent/rules/windsurfrules deleted file mode 100644 index 2eb9bf849..000000000 --- a/.agent/rules/windsurfrules +++ /dev/null @@ -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. diff --git a/.agent/skills/coding-levels/SKILL.md b/.agent/skills/coding-levels/SKILL.md deleted file mode 100644 index 9a261a6c8..000000000 --- a/.agent/skills/coding-levels/SKILL.md +++ /dev/null @@ -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 diff --git a/.agent/test1.txt b/.agent/test1.txt deleted file mode 100644 index b7f32beb2..000000000 --- a/.agent/test1.txt +++ /dev/null @@ -1 +0,0 @@ -Hello world from test1 \ No newline at end of file diff --git a/.agent/uploads/1773926284401_audit.md b/.agent/uploads/1773926284401_audit.md deleted file mode 100644 index 847f718ec..000000000 --- a/.agent/uploads/1773926284401_audit.md +++ /dev/null @@ -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 -``` diff --git a/.agent/uploads/1773926307026_audit_service_variants.md b/.agent/uploads/1773926307026_audit_service_variants.md deleted file mode 100644 index 4bab2e80f..000000000 --- a/.agent/uploads/1773926307026_audit_service_variants.md +++ /dev/null @@ -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 -{ - /// - /// Platform-agnostic audit logging interface. - /// Implementations: WasmAuditService, ServerAuditService, ApiAuditService - /// - public interface IAuditService - { - /// - /// Log INSERT operation - /// - Task LogInsertAsync(string tableName, string rowId, object? newData, - string? schemaName = null, string? functionName = null); - - /// - /// Log UPDATE operation - /// - Task LogUpdateAsync(string tableName, string rowId, - object? oldData, object? newData, - string? schemaName = null, string? primaryKeyColumns = null, - string? functionName = null); - - /// - /// Log DELETE operation - /// - Task LogDeleteAsync(string tableName, string rowId, object? oldData, - string? schemaName = null, string? functionName = null); - - /// - /// Log custom operation (APPROVE, REJECT, EXPORT, etc.) - /// - 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; - -/// -/// 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) -/// -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; - -/// -/// 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) -/// -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; - -/// -/// 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) -/// -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 -{ - /// - /// 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 - /// - public interface IAuditUserProvider - { - Task 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; - -/// -/// Lấy user info từ AuthenticationStateProvider (hoạt động cả WASM + Server) -/// -public class BlazorAuditUserProvider : IAuditUserProvider -{ - private readonly AuthenticationStateProvider _authState; - - public BlazorAuditUserProvider(AuthenticationStateProvider authState) - { - _authState = authState; - } - - public async Task 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; - -/// -/// Lấy user info từ HttpContext (API-only, có HTTP pipeline) -/// -public class ApiAuditUserProvider : IAuditUserProvider -{ - private readonly IHttpContextAccessor _httpContext; - - public ApiAuditUserProvider(IHttpContextAccessor httpContext) - { - _httpContext = httpContext; - } - - public Task 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; - -/// -/// 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. -/// -public interface ICircuitContextAccessor -{ - string? CorrelationId { get; } - string? IpAddress { get; } - string? UserAgent { get; } - string? SessionId { get; } - string CircuitId { get; } -} - -/// -/// Capture context từ HttpContext lúc circuit mở. -/// Registered: Scoped (per-circuit) -/// -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; } = ""; -} - -/// -/// CircuitHandler populate CircuitContextAccessor khi circuit mở. -/// Capture IP, UserAgent, SessionId từ initial HTTP handshake. -/// -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 -{ - /// - /// Blazor WASM: audit log qua HTTP → Module Controller → gRPC → RabbitMQ - /// Yêu cầu: IHttpClientFactory đã cấu hình "AuthenticatedClient" - /// - public static IServiceCollection AddAuditService_BlazorWasm(this IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - return services; - } - - /// - /// Blazor Server: audit log direct inject → IAuditLogPublisher → RabbitMQ - /// Yêu cầu: IAuditLogPublisher, IRabbitMQService đã đăng ký - /// - public static IServiceCollection AddAuditService_BlazorServer(this IServiceCollection services) - { - services.AddHttpContextAccessor(); - - // Context capture từ SignalR initial connection - services.AddScoped(); - services.AddScoped(sp => sp.GetRequiredService()); - services.AddScoped(); - - // Audit services - services.AddScoped(); - services.AddScoped(); - return services; - } - - /// - /// API-only: audit log direct inject → IAuditLogPublisher → RabbitMQ - /// Yêu cầu: IAuditLogPublisher, IRequestContextAccessor đã đăng ký - /// - public static IServiceCollection AddAuditService_Api(this IServiceCollection services) - { - services.AddHttpContextAccessor(); - services.AddScoped(); - services.AddScoped(); - 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 { - /// - /// 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: - /// - - 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(); -builder.Services.AddSingleton(); -builder.Services.AddScoped(); - -// Audit service -builder.Services.AddAuditService_BlazorServer(); -``` - -### API-only - -```csharp -// Program.cs (API project) -// Prerequisites -builder.Services.AddScoped(); -builder.Services.AddSingleton(); -builder.Services.AddScoped(); - -// 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 - - -@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 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()` | diff --git a/.agent/uploads/1773926322631_audit_log_review.md b/.agent/uploads/1773926322631_audit_log_review.md deleted file mode 100644 index 3681b97ba..000000000 --- a/.agent/uploads/1773926322631_audit_log_review.md +++ /dev/null @@ -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(); - -// Blazor Server → dùng ServerAuditLogger (direct inject) -services.AddScoped(); - -// 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
(HTTP Client)"] - B --> C["Module Controller
POST /api/logging/audit"] - C --> D["gRPC → LoggingGrpcService"] - end - - subgraph "Blazor Server" - E["INS_AuditLogger"] --> F["IServerAuditLogger
(Direct Inject)"] - F --> G["IAuditLogPublisher
(Direct)"] - end - - subgraph "API Only" - H["Controller Code"] --> G - end - - D --> I["IRabbitMQService.SendAsync()"] - G --> I - I --> J["RabbitMQ
ins.logs.exchange"] - J --> K["AuditLogConsumerService"] - K --> L["SQL Server
[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(); -else if (isBlazorWasm) - services.AddScoped(); -else // API - services.AddScoped(); -``` diff --git a/.agent/uploads/1773926334916_audit_log_flow.md b/.agent/uploads/1773926334916_audit_log_flow.md deleted file mode 100644 index c0e9563a0..000000000 --- a/.agent/uploads/1773926334916_audit_log_flow.md +++ /dev/null @@ -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
(DXComponents)"] - B["LogService.cs
(HTTP Client)"] - end - - subgraph "TIER 2: Module Backend" - C["Module Controller
(REST API /api/logging/audit)"] - D["gRPC Client
(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
Exchange: ins.logs.exchange
(topic)"] - K["Queue: ins.server.auditlog.queue
Binding: logs.audit.#"] - end - - subgraph "Consumer Layer" - L["AuditLogConsumerService
(BackgroundService)"] - M["BaseRabbitMqConsumerService
(lazy init + retry)"] - end - - subgraph "Database" - N["SQL Server
[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 - - -@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` và `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(); -``` - ---- - -### 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 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() -``` - ---- - -### 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 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(); -builder.Services.AddSingleton(); -builder.Services.AddScoped(); -``` - -**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` và `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` và `logs.audit.data` - -**Core Library**: `INS.RabbitMQ` (v1.2.1, RabbitMQ.Client 7.2.0) -- Interface: `IRabbitMQService` -- Methods: `SendAsync(exchange, routingKey, message)`, `CreateConsumer()` - ---- - -### 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()` fluent builder - -```csharp -public abstract class BaseRabbitMqConsumerService : 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() - .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 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 -{ - public AuditLogConsumerService( - IServiceProvider serviceProvider, - IConfiguration configuration, - ILogger logger) - : base(serviceProvider, configuration, logger, "AuditLog") // config section - { } - - protected override async Task 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(); - - // 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(); -``` - ---- - -### 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(); -builder.Services.AddSingleton(); - -// Publisher -builder.Services.AddScoped(); - -// Consumers (BackgroundServices) -builder.Services.AddHostedService(); -builder.Services.AddHostedService(); -builder.Services.AddHostedService(); -``` - -### 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() -services.AddScoped(); -``` - ---- - -## 6. Checklist để tái tạo chức năng - -- [ ] **Shared Models**: Tạo `RabbitMQAuditLogDto` và `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()` và `CreateConsumer()` 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 diff --git a/.agent/workflows/Disk_Cleanup_Workflow_Knowledge.md b/.agent/workflows/Disk_Cleanup_Workflow_Knowledge.md deleted file mode 100644 index 5e1090da0..000000000 --- a/.agent/workflows/Disk_Cleanup_Workflow_Knowledge.md +++ /dev/null @@ -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 -``` diff --git a/.agent/workflows/audit.md b/.agent/workflows/audit.md deleted file mode 100644 index 847f718ec..000000000 --- a/.agent/workflows/audit.md +++ /dev/null @@ -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 -``` diff --git a/.agent/workflows/backup.md b/.agent/workflows/backup.md deleted file mode 100644 index 6afc180d1..000000000 --- a/.agent/workflows/backup.md +++ /dev/null @@ -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!" -``` diff --git a/.agent/workflows/blazor_wasm_compression_optimization.md b/.agent/workflows/blazor_wasm_compression_optimization.md deleted file mode 100644 index 1a7d10875..000000000 --- a/.agent/workflows/blazor_wasm_compression_optimization.md +++ /dev/null @@ -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` và `.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(); - options.Providers.Add(); - options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] - { - "image/svg+xml", "application/javascript", "text/css" - }); -}); -builder.Services.Configure(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 - - - - -``` - -> **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 | diff --git a/.agent/workflows/decrypt-forge.md b/.agent/workflows/decrypt-forge.md deleted file mode 100644 index 31dcd5cd0..000000000 --- a/.agent/workflows/decrypt-forge.md +++ /dev/null @@ -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. diff --git a/.agent/workflows/deploy-ifc-converter.md b/.agent/workflows/deploy-ifc-converter.md deleted file mode 100644 index 720daa704..000000000 --- a/.agent/workflows/deploy-ifc-converter.md +++ /dev/null @@ -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` và `API_Sample/Services` vào Source Code Web của bạn. -- Cấu hình Dependency Injection ở Program.cs: - ```csharp - services.AddScoped(); - services.AddSingleton(); - ``` -- 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. diff --git a/.agent/workflows/docker-deploy.md b/.agent/workflows/docker-deploy.md deleted file mode 100644 index 537dfbfce..000000000 --- a/.agent/workflows/docker-deploy.md +++ /dev/null @@ -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\.Backend\.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 :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 "-$ts.tar" :latest -``` - ---- - -## Phase 4: Deploy to VPS - -### 4.1. Upload .tar to VPS -```powershell -scp -o StrictHostKeyChecking=no "-*.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/-*.tar && docker stop 2>/dev/null; docker rm 2>/dev/null; docker run -d --name --network app-network --restart unless-stopped -p : -e ASPNETCORE_ENVIRONMENT=Production -e DOTNET_RUNNING_IN_CONTAINER=true --health-cmd 'curl -f http://localhost:/health/ready || exit 1' --health-interval 30s --health-timeout 10s --health-retries 3 --health-start-period 60s :latest && rm -f /tmp/-*.tar" -``` - -### 4.3. Clean up local .tar -```powershell -Remove-Item -Path "-*.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= --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 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:/health/ready | python3 -m json.tool 2>/dev/null || curl -s http://localhost:/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 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 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 ` | -| 30s request timeouts | gRPC SSO unreachable | Check SSO container + network alias | diff --git a/.agent/workflows/initial.md b/.agent/workflows/initial.md deleted file mode 100644 index 6f5743944..000000000 --- a/.agent/workflows/initial.md +++ /dev/null @@ -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 `` 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`). diff --git a/.agent/workflows/ins-develop.md b/.agent/workflows/ins-develop.md deleted file mode 100644 index eb677a559..000000000 --- a/.agent/workflows/ins-develop.md +++ /dev/null @@ -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 = "" - # 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 -``` diff --git a/.agent/workflows/prepair-ifc-workflow.md b/.agent/workflows/prepair-ifc-workflow.md deleted file mode 100644 index 4a55abe4d..000000000 --- a/.agent/workflows/prepair-ifc-workflow.md +++ /dev/null @@ -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: `___.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. diff --git a/Tailscale-Custom-Setup.wixpdb b/Tailscale-Custom-Setup.wixpdb index 953dc4191e7f3f58a72e73db2fd86ea4f1cbbd66..b2fb127f2a08180a1cb36f514ccc4a982501da8d 100644 GIT binary patch literal 13344 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 literal 13229 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