From ad76eccd2d36aeff4f256c223e6f5a12333871c7 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Tue, 4 Aug 2026 18:51:03 +0800 Subject: [PATCH 1/4] chore(ci): upgrade pnpm setup action --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5170d3..7300aec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v6 - name: Set up pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 - name: Set up Node.js uses: actions/setup-node@v6 From cc7a9a41849fbdd34cf1af27247764f44c78743a Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Tue, 4 Aug 2026 18:56:09 +0800 Subject: [PATCH 2/4] chore(ci): show verification steps separately --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7300aec..27450ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,8 +38,14 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Verify application - run: pnpm verify + - name: Typecheck + run: pnpm typecheck + + - name: Run unit tests + run: pnpm test + + - name: Build application + run: pnpm build - name: Set up Terraform uses: hashicorp/setup-terraform@v4 From 899eac8d7e6fa72db078b5ffd3a3ae8f3357678d Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Tue, 4 Aug 2026 19:07:16 +0800 Subject: [PATCH 3/4] test: silence expected error logs --- .../src/deep-analysis-source-preparation.service.spec.ts | 4 ++++ .../src/memory-analysis/memory-analysis.service.spec.ts | 4 ++++ .../src/deep-analysis-report-processor.service.spec.ts | 6 ++++++ .../src/memory-analysis-report-runner.service.spec.ts | 4 ++++ apps/worker/src/sqs-consumer.service.spec.ts | 8 ++++++++ 5 files changed, 26 insertions(+) diff --git a/apps/api/src/deep-analysis-source-preparation.service.spec.ts b/apps/api/src/deep-analysis-source-preparation.service.spec.ts index d7473c4..547a408 100644 --- a/apps/api/src/deep-analysis-source-preparation.service.spec.ts +++ b/apps/api/src/deep-analysis-source-preparation.service.spec.ts @@ -78,6 +78,9 @@ describe('deep analysis source preparation service', () => { enqueueLlmMessage: jest.fn(), } as never, ); + const errorSpy = jest + .spyOn(service['logger'], 'error') + .mockImplementation(() => undefined); await service.prepareAndEnqueue({ reportId: 'dar_1', @@ -94,5 +97,6 @@ describe('deep analysis source preparation service', () => { errorCode: 'DEEP_ANALYSIS_SOURCE_PREP_FAILED', }), ); + expect(errorSpy).toHaveBeenCalled(); }); }); diff --git a/apps/api/src/memory-analysis/memory-analysis.service.spec.ts b/apps/api/src/memory-analysis/memory-analysis.service.spec.ts index 8d3914d..a8b0a39 100644 --- a/apps/api/src/memory-analysis/memory-analysis.service.spec.ts +++ b/apps/api/src/memory-analysis/memory-analysis.service.spec.ts @@ -446,6 +446,9 @@ describe('memory analysis report service', () => { throw new Error('sqs down'); }), }); + const errorSpy = jest + .spyOn(service['logger'], 'error') + .mockImplementation(() => undefined); await expect(service.createReport(reportContext, { createdAfter: '2026-06-01T00:00:00.000Z', @@ -459,6 +462,7 @@ describe('memory analysis report service', () => { reportStage: 'failed', failCode: 'MEMORY_ANALYSIS_QUEUE_ENQUEUE_FAILED', })); + expect(errorSpy).toHaveBeenCalled(); }); it('lists memory analysis reports by type', async () => { diff --git a/apps/worker/src/deep-analysis-report-processor.service.spec.ts b/apps/worker/src/deep-analysis-report-processor.service.spec.ts index 6073848..88ab1da 100644 --- a/apps/worker/src/deep-analysis-report-processor.service.spec.ts +++ b/apps/worker/src/deep-analysis-report-processor.service.spec.ts @@ -1122,12 +1122,18 @@ describe('deep analysis report processor service', () => { createConfig() as never, ); + const errorSpy = jest + .spyOn(processor['logger'], 'error') + .mockImplementation(() => undefined); + await processor.process({ messageType: 'deep_report', reportId: 'dar_trim_failure', traceId: 'trace_trim_failure', }); + expect(errorSpy).toHaveBeenCalled(); + expect(repository.updateDeepAnalysisReport).toHaveBeenLastCalledWith( 'dar_trim_failure', expect.objectContaining({ diff --git a/apps/worker/src/memory-analysis-report-runner.service.spec.ts b/apps/worker/src/memory-analysis-report-runner.service.spec.ts index 98c7922..c9ae07d 100644 --- a/apps/worker/src/memory-analysis-report-runner.service.spec.ts +++ b/apps/worker/src/memory-analysis-report-runner.service.spec.ts @@ -119,6 +119,9 @@ describe('MemoryAnalysisReportRunnerService', () => { repository as never, new FakeRedis() as never, ); + const errorSpy = jest + .spyOn(runner['logger'], 'error') + .mockImplementation(() => undefined); await runner.generateReport(reportContext, 1, { createdAfter: '2026-06-01T00:00:00.000Z', @@ -131,6 +134,7 @@ describe('MemoryAnalysisReportRunnerService', () => { failCode: 'QWEN_NOT_CONFIGURED', failReason: 'Qwen API key or model is not configured.', })); + expect(errorSpy).toHaveBeenCalled(); }); it('uses the cache version for period summary cache lookups and writes', async () => { diff --git a/apps/worker/src/sqs-consumer.service.spec.ts b/apps/worker/src/sqs-consumer.service.spec.ts index 611bb91..488de00 100644 --- a/apps/worker/src/sqs-consumer.service.spec.ts +++ b/apps/worker/src/sqs-consumer.service.spec.ts @@ -128,6 +128,9 @@ describe('sqs consumer service', () => { } as never, createConfig(), ); + const errorSpy = jest + .spyOn(consumer['logger'], 'error') + .mockImplementation(() => undefined); const setIntervalSpy = jest.spyOn(global, 'setInterval'); consumer['running'] = true; @@ -140,6 +143,7 @@ describe('sqs consumer service', () => { expect(Sentry.captureException).toHaveBeenCalledWith( expect.any(SyntaxError), ); + expect(errorSpy).toHaveBeenCalled(); }); it('keeps consuming after one processor failure', async () => { @@ -205,6 +209,9 @@ describe('sqs consumer service', () => { } as never, createConfig(), ); + const errorSpy = jest + .spyOn(consumer['logger'], 'error') + .mockImplementation(() => undefined); consumer['running'] = true; await consumer['consumeBatchLoop'](); @@ -214,6 +221,7 @@ describe('sqs consumer service', () => { expect(queue.deleteMessage).toHaveBeenCalledWith('rh_2'); expect(Sentry.captureException).toHaveBeenCalledTimes(1); expect(Sentry.captureException).toHaveBeenCalledWith(expect.any(Error)); + expect(errorSpy).toHaveBeenCalled(); }); it('dispatches memory analysis report llm messages', async () => { From eae2d9bbcf00f355db1bcfcbdb41e60264dc49eb Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Tue, 4 Aug 2026 20:25:56 +0800 Subject: [PATCH 4/4] ci: verify production API health after deploy --- .github/workflows/deploy-production.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index 3b2d213..eb46645 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -16,6 +16,7 @@ env: ECR_REGISTRY: 401696231252.dkr.ecr.ap-southeast-1.amazonaws.com API_REPOSITORY: mem9-node-prod-api WORKER_REPOSITORY: mem9-node-prod-worker + API_HEALTH_URL: https://napi.mem9.ai/health/ready TF_STATE_BUCKET: mem9-node-prod-terraform-state TF_STATE_KMS_KEY_ARN: arn:aws:kms:ap-southeast-1:401696231252:key/4cd4c7a1-c1a6-47ad-9205-548678b84d86 TERRAFORM_VERSION: 1.15.4 @@ -242,6 +243,20 @@ jobs: --services "$API_SERVICE" "$WORKER_SERVICE" \ --region "$AWS_REGION" + - name: Verify production API health + run: | + curl \ + --fail-with-body \ + --silent \ + --show-error \ + --retry 10 \ + --retry-delay 5 \ + --retry-all-errors \ + --connect-timeout 5 \ + --max-time 10 \ + "$API_HEALTH_URL" \ + | jq -e '.status == "ready"' + - name: Record successful release run: | API_TASK_DEFINITION=$(terraform -chdir="$TF_WORKING_DIR" output -raw api_task_definition_arn)