diff --git a/.github/labeler.yml b/.github/labeler.yml index de893c7..ca43af7 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -20,26 +20,37 @@ - changed-files: - any-glob-to-any-file: - 'src/**' - - 'apps/**' - - 'libs/**' + +'area/database': + - changed-files: + - any-glob-to-any-file: + - 'prisma/**' + +'area/api': + - changed-files: + - any-glob-to-any-file: + - 'bruno/**' 'area/deps': - changed-files: - any-glob-to-any-file: - 'package.json' - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' 'area/security': - changed-files: - any-glob-to-any-file: - 'SECURITY.md' + - '.gitleaks.toml' - '.github/workflows/scorecard.yml' - '.github/workflows/workflow-lint.yml' 'area/maintenance': - - changed-files: - - any-glob-to-any-file: - - '.github/workflows/maintenance.yml' - - '.github/workflows/auto-approve.yml' - branch: - - '^maintenance/.*' + - any: + - changed-files: + - any-glob-to-any-file: + - '.github/workflows/maintenance.yml' + - '.github/workflows/auto-approve.yml' + - head-branch: + - '^maintenance/.*' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 869c6d3..fb346d9 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -40,6 +40,32 @@ jobs: client-id: ${{ secrets.CHITRANK_BOT_CLIENT_ID }} private-key: ${{ secrets.CHITRANK_BOT_PRIVATE_KEY }} + - name: Sync Label Colors and Descriptions 🎨 + env: + GH_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + # Define labels as "name:color:description" + labels=( + "area/docs:8FD6E8:Documentation files and markdown files" + "area/ci:F9D0C4:CI/CD workflows and actions configuration" + "area/logic:C2F2D0:Application source code changes" + "area/database:D4C5F9:Database schemas, migrations, and seeds" + "area/api:F9E8B3:API collections and contract definitions" + "area/deps:D2D2D2:Dependency upgrades and lockfile updates" + "area/security:FFB3B3:Security policies and scanner configurations" + "area/maintenance:D0E1FD:Weekly cleanup and automated maintenance tasks" + ) + + for item in "${labels[@]}"; do + # Parse the item + IFS=":" read -r name color description <<< "$item" + + echo "Syncing label '$name' (Color: #$color)..." + # Try to create the label; if it fails (already exists), update it + gh label create "$name" --color "$color" --description "$description" 2>/dev/null || \ + gh label edit "$name" --color "$color" --description "$description" + done + - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0 with: repo-token: '${{ steps.generate_token.outputs.token }}' diff --git a/.github/workflows/pr-autofill.yml b/.github/workflows/release-pr-autofill.yml similarity index 75% rename from .github/workflows/pr-autofill.yml rename to .github/workflows/release-pr-autofill.yml index 344947c..a25fcdb 100644 --- a/.github/workflows/pr-autofill.yml +++ b/.github/workflows/release-pr-autofill.yml @@ -1,14 +1,14 @@ # ============================================================= -# Automated PR Description (Autofill) +# Automated Release PR Description (Autofill) # -# Automatically populates the Pull Request description with a -# summarized changelog of all commits in the branch. +# Automatically updates the Pull Request description for release +# branches with a summarized changelog of all commits in the branch. # -# This ensures that every PR has a professional, categorized -# description without any manual effort from the developer. +# This ensures that Release PRs always stay updated with the latest +# changes during the release lifecycle. # ============================================================= -name: PR Description Autofill ✍️ +name: Release PR Autofill ✍️ on: pull_request: @@ -31,9 +31,7 @@ jobs: timeout-minutes: 5 if: | (github.event_name != 'workflow_dispatch' || github.actor == github.repository_owner) && - github.event.pull_request.user.login != 'renovate[bot]' && - !contains(github.event.pull_request.labels.*.name, 'release') && - (github.event_name == 'workflow_dispatch' || github.event.pull_request.commits > 1) + (github.event_name == 'workflow_dispatch' || startsWith(github.event.pull_request.head.ref, 'chore/release-')) steps: - name: Harden Runner 🛡️ uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 @@ -83,17 +81,20 @@ jobs: fi fi - # 4. Perform a surgical replacement of the content between tags - # We use a temp file to handle multiline safely - echo "$CURRENT_BODY" > final_body.md + # Write current body to a temp file safely + printf "%s" "$CURRENT_BODY" > final_body.md - # Delete everything between tags + # Write new commits changelog to a temp file safely (avoids shell injection/expansion) + printf "%s" "$COMMITS" > commits.tmp + + # Delete everything between tags in final_body.md sed -i '//,//{//!d}' final_body.md - # Insert the new commits after the start tag - sed -i "//r /dev/stdin" final_body.md </r commits.tmp" final_body.md + + # Cleanup temp files + rm -f commits.tmp - name: Update PR 🚀 env: diff --git a/.github/workflows/release-prepare.yml b/.github/workflows/release-prepare.yml index 217049f..7604377 100644 --- a/.github/workflows/release-prepare.yml +++ b/.github/workflows/release-prepare.yml @@ -133,14 +133,14 @@ jobs: title: 'chore(release): 🚀 ${{ steps.vars.outputs.tag_name }}' branch: 'chore/release-${{ steps.vars.outputs.tag_name }}' body: | - ## 📦 Release ${{ steps.vars.outputs.tag_name }} + ${{ steps.git-cliff-pr.outputs.content }} + --- - **Bump Strategy**: ${{ github.event.inputs.tag_name != '' && 'Manual Override' || format('git-hygiene recommendation ({0})', steps.bump.outputs.releaseType) }} - labels: | chore release diff --git a/docs/maintenance/ci-cd.md b/docs/maintenance/ci-cd.md index b987a56..f10ca3e 100644 --- a/docs/maintenance/ci-cd.md +++ b/docs/maintenance/ci-cd.md @@ -93,17 +93,17 @@ To maintain velocity while following strict branch protection rules: ## 📋 Workflow Catalog -| Workflow | File | Purpose | Trigger | -| :----------------- | :--------------------- | :------------------------------------------------------- | :------------------------ | -| **CI & Security** | `ci.yml` | Validates code quality, runs tests, and audits security. | PR / Push to main | -| **Action Linting** | `workflow-lint.yml` | Audits GitHub Actions for security flaws using `zizmor`. | Changes to workflows | -| **PR Autofill** | `pr-autofill.yml` | Populates PR descriptions based on commit history. | PR to main | -| **Auto-Approve** | `auto-approve.yml` | Approves safe automated updates & unblocks reviews. | CI finish on bot branches | -| **Release 1** | `release-prepare.yml` | Bumps version and opens a Release PR. | Manual (Owner Only) | -| **Release 2** | `release-finalize.yml` | Pushes tag and creates GitHub Release on PR merge. | PR Merge (Owner Only) | -| **Scorecard** | `scorecard.yml` | Tracks repo-level security health (OpenSSF). | Weekly / Push to main | -| **Docs Deploy** | `docs.yml` | Builds and publishes documentation. | Push to main / Manual | -| **Maintenance** | `maintenance.yml` | Weekly automated cleanup of dependency overrides. | Weekly / Manual | +| Workflow | File | Purpose | Trigger | +| :---------------------- | :--------------------- | :------------------------------------------------------- | :------------------------ | +| **CI & Security** | `ci.yml` | Validates code quality, runs tests, and audits security. | PR / Push to main | +| **Action Linting** | `workflow-lint.yml` | Audits GitHub Actions for security flaws using `zizmor`. | Changes to workflows | +| **Release PR Autofill** | `pr-autofill.yml` | Updates release PR descriptions based on commit history. | Release PR to main | +| **Auto-Approve** | `auto-approve.yml` | Approves safe automated updates & unblocks reviews. | CI finish on bot branches | +| **Release 1** | `release-prepare.yml` | Bumps version and opens a Release PR. | Manual (Owner Only) | +| **Release 2** | `release-finalize.yml` | Pushes tag and creates GitHub Release on PR merge. | PR Merge (Owner Only) | +| **Scorecard** | `scorecard.yml` | Tracks repo-level security health (OpenSSF). | Weekly / Push to main | +| **Docs Deploy** | `docs.yml` | Builds and publishes documentation. | Push to main / Manual | +| **Maintenance** | `maintenance.yml` | Weekly automated cleanup of dependency overrides. | Weekly / Manual | --- diff --git a/src/modules/entitlement-check/entitlement-check.service.ts b/src/modules/entitlement-check/entitlement-check.service.ts index 6d86896..34a9d10 100644 --- a/src/modules/entitlement-check/entitlement-check.service.ts +++ b/src/modules/entitlement-check/entitlement-check.service.ts @@ -34,6 +34,7 @@ import { } from '@nestjs/common'; import { PrismaService } from '@app-prisma/prisma.service'; +import { randomUUID } from 'node:crypto'; import { ERRORS } from '@common/constants'; @@ -291,6 +292,49 @@ export class EntitlementCheckService { const isOverage = newUsage > limit; const remaining = Math.max(0, limit - newUsage); + // Persist usage event through the pipeline for billing durability. + // Redis increment is the fast path for immediate feedback; + // this ensures the event reaches usage_aggregates for invoicing. + const eventId = `consume-${tenantId}-${featureLookupKey}-${randomUUID()}`; + + try { + await this.prisma.$transaction(async (tx) => { + const usageEvent = await tx.usageEvent.create({ + data: { + eventId, + tenantId, + subscriptionId: snapshot.subscriptionId, + featureLookupKey, + amount, + timestamp: new Date(), + status: 'PENDING', + metadata: { source: 'consume-endpoint' }, + }, + }); + + await tx.outboxEvent.create({ + data: { + topic: 'usage.raw', + aggregateType: 'usage_event', + aggregateId: usageEvent.id, + payload: { + eventId, + tenantId, + subscriptionId: snapshot.subscriptionId, + featureLookupKey, + amount, + }, + }, + }); + }); + } catch (error) { + // Log but don't fail the consume response - Redis already incremented. + // The pipeline will reconcile on the next aggregation cycle. + this.logger.warn( + `Failed to persist usage event for consume: ${error instanceof Error ? error.message : String(error)}`, + ); + } + this.logger.log( `Consumed ${amount} units of ${featureLookupKey} for tenant ${tenantId} (New usage: ${newUsage}, Overage: ${isOverage})`, );