# CI Report Upload Flow: GitHub Actions / GitLab CI / Bitbucket Pipelines

Tài liệu chi tiết luồng hoạt động khi chạy SunLint trên CI/CD,
từ lúc scan code đến khi report được lưu vào database.
Hỗ trợ 3 nền tảng: GitHub Actions, GitLab CI, Bitbucket Pipelines.

---

## Workflow YAML mẫu

### GitHub Actions

```yaml
name: Sunlint Code Quality Check
on: [pull_request]
jobs:
  sunlint:
    runs-on: "macos-latest"
    permissions:
      id-token: write    # BẮT BUỘC - cho phép request OIDC token
      contents: read
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: "21"
      - run: npm install -g @sun-asterisk/sunlint
      - run: sunlint --specific --languages=dart --input=lib --output-summary=report.json --upload-report --quiet
```

### GitLab CI

```yaml
sunlint:
  image: node:21
  stage: test
  # Cấu hình OIDC token cho SunLint
  id_tokens:
    SUNLINT_ID_TOKEN:
      aud: coding-standards-report-api
  script:
    - npm install -g @sun-asterisk/sunlint
    - sunlint --specific --languages=dart --input=lib --output-summary=report.json --upload-report --quiet
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
```

### Bitbucket Pipelines

```yaml
pipelines:
  pull-requests:
    '**':
      - step:
          name: SunLint Code Quality Check
          image: node:21
          script:
            - npm install -g @sun-asterisk/sunlint
            - sunlint --specific --languages=dart --input=lib --output-summary=report.json --upload-report --quiet
```

> **Lưu ý Bitbucket**: Dùng `SUNLINT_API_KEY` (API key) thay vì OIDC.
> Cấu hình trong Repository Settings → Repository variables → `SUNLINT_API_KEY` (đánh dấu Secured).

---

## Tổng quan 8 phases

```
PHASE 1: Khởi tạo CLI
PHASE 2: Chọn rules & files
PHASE 3: Phân tích code
PHASE 4: Tạo report.json
PHASE 5: Upload report (xác thực)
PHASE 6: Server nhận & xác thực
PHASE 7: Lưu vào Database
PHASE 8: CI kết thúc
```

## Data flow tổng quát

```
┌──────────┐    ┌──────────┐    ┌───────────┐    ┌──────────┐    ┌──────────┐
│    CI     │    │  SunLint │    │   OIDC    │    │ Dashboard│    │PostgreSQL│
│  Runner   │───>│  CLI     │───>│ Provider  │    │  API     │    │    DB    │
│           │    │          │    │ /API Key  │    │          │    │          │
└──────────┘    └────┬─────┘    └─────┬─────┘    └────┬─────┘    └────┬─────┘
                     │                │               │               │
              1. Scan code            │               │               │
              2. Tạo report.json      │               │               │
              3. Lấy auth token ─────>│               │               │
              4. Nhận JWT/API key <───┘               │               │
              5. POST report.json + token ───────────>│               │
              6. Verify token ─────────────────────── │               │
              7. Token hợp lệ                         │               │
              8. Parse report.json                    │               │
              9. INSERT repository ───────────────────┼──────────────>│
             10. INSERT report ───────────────────────┼──────────────>│
             11. INSERT violations ───────────────────┼──────────────>│
             12. Invalidate cache                     │               │
             13. HTTP 201 <───────────────────────────┘               │
             14. CI exit(0)                                           │
```

---

## PHASE 1: Khởi tạo CLI

**File**: `core/cli-action-handler.js` - `execute()` (line 42-141)

```
cli.js  →  CliActionHandler.execute()
  ├── displayModernBanner()        → "SunLint v1.x.x"
  ├── handleShortcuts()            → check --version, --list-rules
  └── loadConfiguration()          → ConfigManager đọc .sunlint.json (nếu có)
```

### Validate input (line 316-390)

```
validateInput(config)
  ├── Check --engine option hợp lệ
  ├── Check --upload-report CẦN --output-summary
  │     └── options.uploadReport chưa có URL?
  │         → Gán default: "https://coding-standards-report.sun-asterisk.vn/api/reports"
  └── Check --input=lib tồn tại
```

---

## PHASE 2: Chọn rules & files

### Select rules (line 63)

```
ruleSelectionService.selectRules(config, options)
  └── --specific --languages=dart
      → Chọn tất cả rules hỗ trợ Dart:
        C001, C002, C003, C005, C006, C008, C010...
        D001, D002, D003, D004, D005...
        S001, S002, S003...
        (khoảng 80-100 rules cho Dart)
```

### File targeting (line 71)

```
fileTargetingService.getTargetFiles(["lib"], config, options)
  └── Scan thư mục lib/
      → Tìm tất cả file *.dart
      → Trả về danh sách: ["lib/main.dart", "lib/app.dart", ...]
      → KHÔNG filter bởi git diff → QUÉT TOÀN BỘ CODE
```

> **Lưu ý**: Với lệnh `--input=lib` (KHÔNG có `--changed-files`),
> sunlint quét **TOÀN BỘ** code trong thư mục `lib/`, không chỉ phần diff.
> Muốn chỉ quét diff, thêm `--changed-files`.

---

## PHASE 3: Phân tích code

### Khởi tạo engines (line 146-188)

```
runModernAnalysis(rulesToRun, files, config)
  └── orchestrator.initialize({
        enabledEngines: ["heuristic"],     ← Dart chỉ dùng heuristic
        heuristicConfig: {
          targetFiles: ["lib/*.dart"],
          projectPath: "/path/to/project",
          maxSemanticFiles: 1000
        }
      })
```

### Chạy phân tích

```
orchestrator.analyze(files, rulesToRun, options)
  │
  ├── Với mỗi file .dart:
  │     ├── HeuristicEngine: pattern matching (regex)
  │     └── DartAnalyzer: sunlint-dart-macos binary (nếu spawn OK)
  │           ├── Binary chạy được? → Deep AST analysis (nhiều violations)
  │           └── Binary FAIL?      → Fallback regex (ít violations)
  │
  └── Kết quả: { results: [...], summary: { total, errors, warnings } }
```

---

## PHASE 4: Tạo report.json

### Output results

**File**: `core/output-service.js` - `outputResults()` (line 38-129)

```
outputService.outputResults(results, options, metadata)
  │
  ├── generateReport(results)           → Formatted text + violations array
  │
  ├── console.log(report.formatted)     ← ĐÂY LÀ CHỖ BỊ KILL nếu quá nhiều output
  │                                        --quiet sẽ skip dòng này
  │
  └── options.outputSummary = "report.json"  → Vào bước tiếp theo
```

### Tạo summary report

**File**: `core/summary-report-service.js` - `generateSummaryReport()` (line 152-335)

#### Thu thập Git info (line 37-143)

```
getGitInfo()
  ├── git config --get remote.origin.url  → "https://github.com/org/repo"
  ├── git rev-parse --abbrev-ref HEAD     → "feature/xyz"
  ├── git rev-parse HEAD                  → "abc123..."
  ├── git log -1 --pretty=%B              → "feat: add login page"
  ├── git log -1 --pretty=%ae             → "dev@sun-asterisk.com"
  ├── git log -1 --pretty=%an             → "Nguyen Van A"
  └── Trích PR number từ commit message hoặc branch name
```

Trên CI, override bằng env vars (theo thứ tự ưu tiên: GitHub → GitLab → Bitbucket → git):

| Trường | GitHub Actions | GitLab CI | Bitbucket Pipelines |
|--------|----------------|-----------|---------------------|
| repository_url | `GITHUB_REPOSITORY` (ghép) | `CI_PROJECT_URL` | `BITBUCKET_WORKSPACE` + `BITBUCKET_REPO_SLUG` (ghép) |
| repository_name | `GITHUB_REPOSITORY` (split) | `CI_PROJECT_NAME` | `BITBUCKET_REPO_SLUG` |
| branch | `GITHUB_REF_NAME` | `CI_COMMIT_REF_NAME` | `BITBUCKET_BRANCH` |
| commit_hash | `GITHUB_SHA` | `CI_COMMIT_SHA` | `BITBUCKET_COMMIT` |
| commit_message | `GITHUB_EVENT_PATH` (JSON) | `CI_COMMIT_MESSAGE` | git fallback |
| author_email | `GITHUB_EVENT_PATH` (JSON) | `GITLAB_USER_EMAIL` | git fallback |
| author_name | `GITHUB_EVENT_PATH` (JSON) | `GITLAB_USER_NAME` | git fallback |
| PR/MR number | `GITHUB_EVENT_PATH` (JSON) | `CI_MERGE_REQUEST_IID` | `BITBUCKET_PR_ID` |

> **Bitbucket**: Không cung cấp commit_message, author_email, author_name qua env vars.
> SunLint tự lấy từ `git log` trên runner (luôn có vì đã checkout code).

#### Build report JSON (line 152-335)

Gom violations theo rule:

```
violations[] → { C006: 150, C010: 89, D008: 200, ... }
```

Cấu trúc file `report.json`:

```json
{
  "repository_url": "https://github.com/org/my-flutter-app",
  "repository_name": "my-flutter-app",
  "project_path": null,
  "branch": "feature/login",
  "commit_hash": "abc123def456...",
  "commit_message": "feat: add login page (#42)",
  "author_email": "dev@sun-asterisk.com",
  "author_name": "Nguyen Van A",
  "pr_number": 42,
  "score": 65,
  "total_violations": 4293,
  "error_count": 12,
  "warning_count": 4281,
  "info_count": 0,
  "lines_of_code": 50000,
  "files_analyzed": 320,
  "sunlint_version": "1.3.25",
  "analysis_duration_ms": 15000,
  "scoring_mode": "project",
  "violations": [
    { "rule_code": "D008", "count": 200, "severity": "warning" },
    { "rule_code": "C006", "count": 150, "severity": "warning" },
    { "rule_code": "C010", "count": 89, "severity": "warning" }
  ],
  "metadata": {
    "generated_at": "2026-03-12T10:30:00.000Z",
    "tool": "SunLint",
    "version": "1.3.25"
  },
  "quality": {
    "score": 65,
    "grade": "C",
    "metrics": { "errors": 12, "warnings": 4281, "linesOfCode": 50000 }
  }
}
```

```
fs.writeFileSync("report.json", JSON.stringify(summaryReport))
→ "Summary report saved to: report.json"
```

---

## PHASE 5: Upload report

### Kích hoạt upload

**File**: `core/output-service.js` (line 110-112)

```
options.uploadReport = "https://coding-standards-report.sun-asterisk.vn/api/reports"
  → handleUploadReport("report.json", apiUrl, options)
    → uploadService.uploadReportToApi("report.json", apiUrl)
```

### Lấy auth token

**File**: `core/upload-service.js` - `getOidcToken()` (line 226-280)

Thứ tự thử xác thực:

```
getOidcToken()
  │
  ├── 1. GitHub Actions OIDC?
  │     Check: GITHUB_ACTIONS + ACTIONS_ID_TOKEN_REQUEST_TOKEN + ACTIONS_ID_TOKEN_REQUEST_URL
  │     → requestGitHubOidcToken()  (gọi API GitHub để lấy JWT)
  │
  ├── 2. GitLab CI OIDC?
  │     Check: GITLAB_CI
  │     → requestGitLabOidcToken()  (đọc env var SUNLINT_ID_TOKEN)
  │
  ├── 3. API Key? (Bitbucket hoặc bất kỳ CI nào)
  │     Check: SUNLINT_API_KEY || API_SECRET_KEY
  │     → return apiKey
  │
  └── 4. Không có gì → return null + warning message
```

#### GitHub Actions: Lấy OIDC token qua API

```
requestGitHubOidcToken()
  │
  │  curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN"
  │       "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=coding-standards-report-api"
  │
  │  HTTPS request tới GitHub OIDC Provider
  │
  └── Response: { "value": "eyJhbGciOiJSUzI1NiIs..." }
                              │
                              └── JWT Token chứa:
                                  {
                                    "iss": "https://token.actions.githubusercontent.com",
                                    "aud": "coding-standards-report-api",
                                    "sub": "repo:org/my-flutter-app:ref:refs/heads/feature/login",
                                    "repository": "org/my-flutter-app",
                                    "repository_owner": "sun-asterisk",
                                    "actor": "nguyen-van-a",
                                    "exp": 1741689600   ← hết hạn sau ~5 phút
                                  }
```

#### GitLab CI: Token có sẵn trong env var

```
requestGitLabOidcToken()
  │
  └── return process.env.SUNLINT_ID_TOKEN
        │
        └── JWT Token chứa:
            {
              "iss": "https://gitlab.com",
              "aud": "coding-standards-report-api",
              "sub": "project_path:group/repo:ref_type:branch:ref:main",
              "project_path": "group/repo",
              "namespace_path": "group",
              "user_login": "nguyen-van-a",
              "pipeline_id": "12345"
            }
```

#### Bitbucket Pipelines: API key từ env var

```
Không có OIDC → Fallback sang API key:
  process.env.SUNLINT_API_KEY  →  "k7Hs9mPqR2xYwN3v..."
```

### Gửi report

**File**: `core/upload-service.js` - `buildCurlCommand()` (line 180-208)

```bash
curl -X POST \
  --connect-timeout 30 \
  --max-time 30 \
  -H "Content-Type: application/json" \
  -H "User-Agent: SunLint-Report-Uploader/1.0" \
  -H "Idempotency-Key: sunlint-a1b2c3d4e5f6..." \
  -H "Authorization: Bearer <OIDC_TOKEN hoặc API_KEY>" \
  --data-binary @"report.json" \
  -i -s \
  "https://coding-standards-report.sun-asterisk.vn/api/reports"
```

```
execSync(curlCommand)
  → Parse HTTP response headers + body
  → Trích xuất status code (201 = thành công)
```

---

## PHASE 6: Server nhận & xác thực

### API Route

**File**: `coding-standards-reports/app/api/reports/route.ts` - `POST` (line 45-406)

```
POST /api/reports
  │
  └── verifyAuth(authHeader)
        │
        ├── 1. Thử API_SECRET_KEY match?
        │     token === process.env.API_SECRET_KEY → OK (actor: "api-key")
        │     (Bitbucket Pipelines dùng cách này)
        │
        ├── 2. Thử GitHub OIDC?
        │     verifyGitHubOIDCToken(token) → OK (actor: github username)
        │
        ├── 3. Thử GitLab OIDC?
        │     verifyGitLabOIDCToken(token) → OK (actor: gitlab username)
        │
        └── 4. Tất cả fail → 401 Unauthorized
```

### Xác minh JWT (GitHub OIDC)

**File**: `coding-standards-reports/lib/auth/github-oidc.ts` (line 79-111)

```
verifyGitHubOIDCToken(token, "coding-standards-report-api")
  │
  ├── Fetch JWKS:
  │     GET https://token.actions.githubusercontent.com/.well-known/jwks
  │     → Lấy public keys của GitHub (RSA)
  │
  ├── jwtVerify(token, JWKS, {
  │     issuer: "https://token.actions.githubusercontent.com",
  │     audience: "coding-standards-report-api",
  │     requiredClaims: ["iss","sub","aud","repository","repository_owner"]
  │   })
  │
  ├── Verify chữ ký RSA                               OK
  ├── Check issuer khớp                                OK
  ├── Check audience = "coding-standards-report-api"   OK
  ├── Check token chưa hết hạn (exp)                   OK
  ├── Check có đủ required claims                      OK
  │
  └── Return payload:
      {
        repository: "org/my-flutter-app",
        repository_owner: "sun-asterisk",
        actor: "nguyen-van-a"
      }
```

### Xác minh JWT (GitLab OIDC)

**File**: `coding-standards-reports/lib/auth/gitlab-oidc.ts` (line 122-153)

```
verifyGitLabOIDCToken(token, "coding-standards-report-api")
  │
  ├── Fetch JWKS:
  │     GET https://gitlab.com/oauth/discovery/keys
  │     → Lấy public keys của GitLab (RSA)
  │
  ├── jwtVerify(token, JWKS, {
  │     issuer: "https://gitlab.com",
  │     audience: "coding-standards-report-api",
  │     requiredClaims: ["iss","sub","aud","project_path","namespace_path"]
  │   })
  │
  └── Return payload:
      {
        project_path: "group/my-flutter-app",
        namespace_path: "group",
        user_login: "nguyen-van-a"
      }
```

### Xác minh API key (Bitbucket)

```
token === process.env.API_SECRET_KEY?
  → Có: authenticated = true, actor = "api-key"
  → Không: tiếp tục thử OIDC providers
```

---

## So sánh 3 phương thức xác thực

| | GitHub Actions | GitLab CI | Bitbucket Pipelines |
|---|---|---|---|
| **Phương thức** | OIDC (JWT) | OIDC (JWT) | API Key |
| **Cấu hình CI** | `permissions: id-token: write` | `id_tokens: SUNLINT_ID_TOKEN` | `SUNLINT_API_KEY` (Secured variable) |
| **Cách lấy token** | Gọi API GitHub runtime | Có sẵn trong env var | Có sẵn trong env var |
| **Server verify** | Fetch JWKS → verify chữ ký RSA | Fetch JWKS → verify chữ ký RSA | So sánh string với `API_SECRET_KEY` |
| **Shared secret** | Không | Không | Có (API key) |
| **Token hết hạn** | ~5 phút (tự động) | ~5 phút (tự động) | Không bao giờ (rotate thủ công) |
| **Audit trail** | Biết repo + actor + workflow | Biết project + user + pipeline | Chỉ biết "api-key" |
| **Server config** | Không cần | `GITLAB_OIDC_ISSUER` (nếu self-hosted) | `API_SECRET_KEY` |

### Bảo mật

| | OIDC (GitHub/GitLab) | API Key (Bitbucket) |
|---|---|---|
| Rủi ro lộ secret | Không có secret để lộ | Key lộ → ai cũng upload được |
| Phạm vi | Gắn với repo/workflow cụ thể | Ai có key đều dùng được |
| Rotation | Tự động mỗi lần chạy CI | Phải đổi thủ công cả 2 phía |
| Giảm thiểu rủi ro | -- | Đánh dấu Secured + rotate định kỳ |

---

## PHASE 7: Lưu vào Database

### Parse body & nhận diện format

**File**: `coding-standards-reports/app/api/reports/route.ts` (line 56-129)

```
Kiểm tra format:
  body.repository_url        OK
  body.commit_hash           OK
  body.score                 OK
  body.violations[]          OK
  body.metadata.tool === "SunLint"  OK
  → Nhận diện: "Native SunLint CLI format"
```

3 formats được hỗ trợ:
1. **Native SunLint CLI** (`--output-summary`) - format hiện tại
2. **Enriched format** (legacy, nested git object)
3. **Legacy manual format** (từ old workflow)

### Tìm/tạo repository (line 150-238)

```sql
-- Tìm repository đã tồn tại chưa?
SELECT * FROM repositories
  WHERE repository_url = 'https://github.com/org/my-flutter-app'
  AND project_path IS NULL
  LIMIT 1;

-- Nếu chưa có → INSERT
INSERT INTO repositories (repository_url, repository_name, project_path)
  VALUES ('https://github.com/org/my-flutter-app', 'my-flutter-app', NULL)
  RETURNING *;

-- Tìm rubato_id từ project_access (Google Sheets sync)
SELECT rubato_id FROM project_access
  WHERE repository LIKE '%https://github.com/org/my-flutter-app%'
  LIMIT 1;

-- Nếu tìm được → Tạo mapping
INSERT INTO project_repository_mapping (...)
```

### Thêm report (line 241-270)

```sql
INSERT INTO reports (
  repository_id, repository_url, repository_name,
  branch, commit_hash, commit_message,
  author_email, author_name, pr_number,
  score, total_violations, error_count, warning_count,
  info_count, lines_of_code, files_analyzed,
  sunlint_version, analysis_duration_ms
) VALUES (
  42, 'https://github.com/org/my-flutter-app', 'my-flutter-app',
  'feature/login', 'abc123...', 'feat: add login page (#42)',
  'dev@sun-asterisk.com', 'Nguyen Van A', 42,
  65, 4293, 12, 4281,
  0, 50000, 320,
  '1.3.25', 15000
) RETURNING *;
```

### Thêm violations (line 276-287)

```sql
-- Với mỗi violation summary:
INSERT INTO violations (report_id, rule_code, violation_count)
  VALUES (report_id, 'D008', 200)
  ON CONFLICT (report_id, rule_code) DO UPDATE
  SET violation_count = EXCLUDED.violation_count;

INSERT INTO violations (report_id, rule_code, violation_count)
  VALUES (report_id, 'C006', 150) ...

-- ... lặp cho tất cả rules có violations
```

### Thêm dữ liệu architecture (line 290-381, nếu có)

```sql
INSERT INTO report_architecture (
  report_id, primary_pattern, primary_confidence, health_score,
  violation_count, rules_checked, rules_passed, rules_failed,
  is_hybrid, combination, secondary_patterns, analysis_time_ms
) VALUES (...) RETURNING *;

-- Thêm architecture rules + violations
INSERT INTO report_architecture_rules (...) VALUES ...;
INSERT INTO report_architecture_violations (...) VALUES ...;
```

### Xóa cache & phản hồi (line 383-398)

```
revalidateTag("department-health")
revalidateTag("dept-adoption")
revalidateTag("dept-scores")
revalidateTag("score-trend")
revalidateTag("violations-severity")
revalidateTag("projects-filter")

→ Response: { success: true, report_id: 1234, message: "Report submitted successfully" }
→ HTTP 201 Created
```

---

## PHASE 8: CI kết thúc

```
uploadService nhận HTTP 201
  → "Report uploaded successfully!"
  → "HTTP Status: 201"

handleExit(results)
  → Có errors? → process.exit(1)  → CI job FAIL
  → Chỉ warnings? → process.exit(0) → CI job PASS
```

---

## Env vars mapping đầy đủ

### Detect CI platform

| Mục đích | GitHub Actions | GitLab CI | Bitbucket Pipelines |
|----------|----------------|-----------|---------------------|
| Detect CI | `GITHUB_ACTIONS` | `GITLAB_CI` | `BITBUCKET_PIPELINE_UUID` |

### Report metadata

| Mục đích | GitHub Actions | GitLab CI | Bitbucket Pipelines |
|----------|----------------|-----------|---------------------|
| Repository URL | `GITHUB_REPOSITORY` (ghép) | `CI_PROJECT_URL` | `BITBUCKET_WORKSPACE` + `BITBUCKET_REPO_SLUG` (ghép) |
| Repository name | `GITHUB_REPOSITORY` (split `/`) | `CI_PROJECT_NAME` | `BITBUCKET_REPO_SLUG` |
| Branch | `GITHUB_REF_NAME` | `CI_COMMIT_REF_NAME` | `BITBUCKET_BRANCH` |
| Commit SHA | `GITHUB_SHA` | `CI_COMMIT_SHA` | `BITBUCKET_COMMIT` |
| Commit message | `GITHUB_EVENT_PATH` (JSON file) | `CI_COMMIT_MESSAGE` | git fallback |
| Author email | `GITHUB_EVENT_PATH` (JSON file) | `GITLAB_USER_EMAIL` | git fallback |
| Author name | `GITHUB_EVENT_PATH` (JSON file) | `GITLAB_USER_NAME` | git fallback |
| PR/MR number | `GITHUB_EVENT_PATH` (JSON file) | `CI_MERGE_REQUEST_IID` | `BITBUCKET_PR_ID` |

### Authentication

| Mục đích | GitHub Actions | GitLab CI | Bitbucket Pipelines |
|----------|----------------|-----------|---------------------|
| Auth method | OIDC | OIDC | API Key |
| Token source | `ACTIONS_ID_TOKEN_REQUEST_*` (API) | `SUNLINT_ID_TOKEN` (env var) | `SUNLINT_API_KEY` (env var) |
| Audience | `coding-standards-report-api` | `coding-standards-report-api` | N/A |

### Idempotency key context

| Mục đích | GitHub Actions | GitLab CI | Bitbucket Pipelines |
|----------|----------------|-----------|---------------------|
| Run ID | `GITHUB_RUN_ID` | `CI_PIPELINE_ID` | `BITBUCKET_BUILD_NUMBER` |
| Run attempt | `GITHUB_RUN_ATTEMPT` | `CI_JOB_ID` | `BITBUCKET_STEP_UUID` |
| Repo identifier | `GITHUB_REPOSITORY` | `CI_PROJECT_PATH` | `BITBUCKET_REPO_SLUG` |

---

## Source files tham chiếu

| Phase | File | Function |
|-------|------|----------|
| 1 | `core/cli-action-handler.js` | `execute()`, `validateInput()` |
| 2 | `core/rule-selection-service.js` | `selectRules()` |
| 2 | `core/file-targeting-service.js` | `getTargetFiles()` |
| 3 | `core/analysis-orchestrator.js` | `analyze()` |
| 3 | `core/adapters/dart-analyzer.js` | `resolveBinary()`, `analyze()` |
| 4 | `core/output-service.js` | `outputResults()`, `generateAndSaveSummaryReport()` |
| 4 | `core/summary-report-service.js` | `generateSummaryReport()`, `getGitInfo()` |
| 5 | `core/upload-service.js` | `uploadReportToApi()`, `getOidcToken()`, `requestGitHubOidcToken()`, `requestGitLabOidcToken()` |
| 6 | `coding-standards-reports/app/api/reports/route.ts` | `POST()`, `verifyAuth()` |
| 6 | `coding-standards-reports/lib/auth/github-oidc.ts` | `verifyGitHubOIDCToken()` |
| 6 | `coding-standards-reports/lib/auth/gitlab-oidc.ts` | `verifyGitLabOIDCToken()` |
| 7 | `coding-standards-reports/app/api/reports/route.ts` | SQL queries (INSERT) |

---

## Hướng dẫn cấu hình nhanh

### GitHub Actions

1. Thêm `permissions: id-token: write` vào job
2. Thêm `--upload-report --quiet` vào lệnh sunlint
3. Không cần cấu hình gì thêm trên server

### GitLab CI

1. Thêm `id_tokens` vào job trong `.gitlab-ci.yml`:
   ```yaml
   id_tokens:
     SUNLINT_ID_TOKEN:
       aud: coding-standards-report-api
   ```
2. Thêm `--upload-report --quiet` vào lệnh sunlint
3. Nếu GitLab self-hosted: set `GITLAB_OIDC_ISSUER` trên server Dashboard

### Bitbucket Pipelines

1. Tạo API key trên server: `openssl rand -base64 32`
2. Đặt giá trị vào server Dashboard `.env.local` → `API_SECRET_KEY=<giá_trị>`
3. Đặt **cùng giá trị** vào Bitbucket → Repository Settings → Repository variables:
   - Name: `SUNLINT_API_KEY`
   - Value: `<giá_trị>`
   - Secured: ✅
4. Thêm `--upload-report --quiet` vào lệnh sunlint

---

## Xử lý sự cố

### Process bị kill khi output quá nhiều
- **Nguyên nhân**: `console.log(report.formatted)` in tất cả violations ra stdout
- **Cách xử lý**: Thêm `--quiet` để skip console output, report.json vẫn được tạo đầy đủ

### Binary Dart không chạy trên CI
- **Nguyên nhân**: `sunlint-dart-macos` là ARM64, không chạy trên Intel runner
- **Cách xử lý**: Dùng `macos-14` hoặc `macos-latest` (Apple Silicon)

### Upload thất bại 401 Unauthorized (GitHub)
- **Nguyên nhân**: Thiếu `permissions: id-token: write` trong workflow
- **Cách xử lý**: Thêm permission vào job

### Upload thất bại 401 Unauthorized (GitLab)
- **Nguyên nhân**: Thiếu `id_tokens` trong `.gitlab-ci.yml`
- **Cách xử lý**: Thêm cấu hình `id_tokens` với audience `coding-standards-report-api`

### Upload thất bại 401 Unauthorized (Bitbucket)
- **Nguyên nhân**: `SUNLINT_API_KEY` không khớp với `API_SECRET_KEY` trên server
- **Cách xử lý**: Kiểm tra giá trị 2 bên phải giống nhau. Tạo lại bằng `openssl rand -base64 32`

### Upload thất bại - không có token (GitHub)
- **Nguyên nhân**: `ACTIONS_ID_TOKEN_REQUEST_TOKEN` không được set
- **Cách xử lý**: Đảm bảo `id-token: write` và workflow chạy trên GitHub Actions

### Upload thất bại - không có token (GitLab)
- **Nguyên nhân**: `SUNLINT_ID_TOKEN` không được set
- **Cách xử lý**: Thêm `id_tokens` vào job:
  ```yaml
  id_tokens:
    SUNLINT_ID_TOKEN:
      aud: coding-standards-report-api
  ```

### Upload thất bại - không có token (Bitbucket)
- **Nguyên nhân**: `SUNLINT_API_KEY` chưa được cấu hình
- **Cách xử lý**: Thêm `SUNLINT_API_KEY` vào Repository Settings → Repository variables (đánh dấu Secured)

### Self-hosted GitLab: JWKS fetch thất bại
- **Nguyên nhân**: Server Dashboard không biết URL GitLab instance
- **Cách xử lý**: Set `GITLAB_OIDC_ISSUER=https://gitlab.your-company.com` trên server
