Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/.*'
26 changes: 26 additions & 0 deletions .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}'
Expand Down
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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 '/<!-- autofill-start -->/,/<!-- autofill-end -->/{//!d}' final_body.md

# Insert the new commits after the start tag
sed -i "/<!-- autofill-start -->/r /dev/stdin" final_body.md <<EOF
$COMMITS
EOF
# Insert the new commits from commits.tmp after the start tag
sed -i "/<!-- autofill-start -->/r commits.tmp" final_body.md

# Cleanup temp files
rm -f commits.tmp

- name: Update PR 🚀
env:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release-prepare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,14 @@ jobs:
title: 'chore(release): 🚀 ${{ steps.vars.outputs.tag_name }}'
branch: 'chore/release-${{ steps.vars.outputs.tag_name }}'
body: |
<!-- autofill-start -->
## 📦 Release ${{ steps.vars.outputs.tag_name }}

<!-- autofill-start -->
${{ steps.git-cliff-pr.outputs.content }}
<!-- autofill-end -->

---
- **Bump Strategy**: ${{ github.event.inputs.tag_name != '' && 'Manual Override' || format('git-hygiene recommendation ({0})', steps.bump.outputs.releaseType) }}
<!-- autofill-end -->
labels: |
chore
release
Expand Down
22 changes: 11 additions & 11 deletions docs/maintenance/ci-cd.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

---

Expand Down
44 changes: 44 additions & 0 deletions src/modules/entitlement-check/entitlement-check.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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})`,
);
Expand Down
Loading