AI Prompt: Implement Stubs, Audit Services, and Retry Queue Handlers¶
Purpose: Replace all stub/placeholder implementations identified in the TODO.md "In-Code TODOs, Stubs, and Placeholders" scan (items 4–11) with working code, including unit tests and acceptance tests
Estimated Effort: 8–12 hours
Prerequisites: Working codebase with passing build, lint, and existing tests
Reference:TODO.mdsection "In-Code TODOs, Stubs, and Placeholders" (items 4–11)
Output: Fully functional audit services in shipping-service/gridflock-service/print-service, a real fulfillment service in shipping-service, and working retry handlers for print job creation and cancellation in both order-service and shipping-service
Status: ✅ COMPLETED (Phase 1 completed 2026-02-28, Phase 2 completed 2026-02-28, Phase 3 completed 2026-02-28, Phase 4 completed 2026-02-28, Phase 5 completed 2026-02-28)
🎯 Mission¶
Implement all stub and placeholder code identified in the TODO.md scan (items 4–11). Each item falls into one of two categories:
Category A — Audit Service Stubs (items 4, 6, 7):
The AuditService and AUDIT_ACTIONS in shipping-service, gridflock-service, and print-service are no-op stubs. Replace them with real audit logging that matches the reference implementation in order-service.
Category B — Retry Queue Placeholders (items 5, 8, 9, 10, 11):
The FulfillmentService stub in shipping-service, and the PRINT_JOB_CREATION and CANCELLATION retry handlers in both order-service and shipping-service are placeholders. Implement them with real business logic.
After implementation, update TODO.md to mark each item as resolved with the implementation date.
📐 Architecture¶
Audit Service Pattern¶
order-service (reference) shipping-service / gridflock-service / print-service
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ AuditService │ │ AuditService │
│ ├── log(input) │ ──> │ ├── log(input) │
│ ├── logAuth(action, req, opt) │ copy │ ├── logAuth(action, req, opt) │
│ ├── logPermissionDenied(...) │ │ └── getClientIp(req) │
│ ├── logUserManagement(...) │ │ │
│ └── getClientIp(req) │ │ AuditRepository │
│ │ │ └── create(input) │
│ AuditRepository │ │ │
│ └── create(input) │ │ AUDIT_ACTIONS (full constant) │
│ │ └────────────────────────────────┘
│ AUDIT_ACTIONS (full constant) │
└────────────────────────────────┘
Retry Queue Handler Pattern¶
RetryQueueProcessor.processJob(job)
├── FULFILLMENT → FulfillmentService.createFulfillment() ✅ Already works
├── NOTIFICATION → NotificationsService.sendEmail() ✅ Already works
├── PRINT_JOB_CREATION → PrintJobsService.createPrintJobsForLineItem() ❌ Placeholder (items 9, 11)
├── CANCELLATION → CancellationService.cancelOrder() ❌ Placeholder (items 8, 10)
└── SHIPMENT → (shipping-service only) ✅ Already works
📋 Implementation¶
Phase 1: Audit Services for shipping-service, gridflock-service, and print-service (3–4 hours)¶
Priority: P1 | Impact: High | Dependencies: None Resolves: TODO.md items 4, 6, 7
These three services have minimal stub AuditService classes that do nothing. Replace them with real implementations modeled on the order-service's AuditService.
Reference implementation¶
The order-service's audit module at apps/order-service/src/audit/ contains:
audit.service.ts— the main service withlog(),logAuth(),logPermissionDenied(),logUserManagement(), andgetClientIp()methodsaudit.repository.ts— Prisma repository that writes to theAuditLogtableaudit.module.ts— NestJS moduledto/audit-event.dto.ts— DTO for audit log inputindex.ts— barrel exportsAUDIT_ACTIONSconstant — full set of action constants
1. Determine each service's audit scope¶
Not all services need all audit methods. Scope them appropriately:
| Service | log() |
logAuth() |
logPermissionDenied() |
logUserManagement() |
Reasoning |
|---|---|---|---|---|---|
| shipping-service | ✅ | ✅ | ❌ | ❌ | Handles auth (session-based), no user management |
| gridflock-service | ✅ | ✅ | ❌ | ❌ | Handles auth (internal API key), no user management |
| print-service | ✅ | ✅ | ❌ | ❌ | Handles auth (internal API key), no user management |
2. For each of the three services, create:¶
a) audit.repository.ts — Copy from apps/order-service/src/audit/audit.repository.ts. This writes to the AuditLog Prisma table which is shared across all services (they all connect to the same database).
b) audit.service.ts — Create a service with:
- log(input: CreateAuditLogInput) — best-effort logging to AuditLog table with Sentry fallback
- logAuth(action, request, options) — authentication event logging
- getClientIp(request) — private method to extract client IP from request headers
- Optional OtelLoggerService integration (if @forma3d/observability is available as a dependency in the service)
c) audit.module.ts — Import PrismaModule (or however the service provides Prisma), provide AuditRepository and AuditService, export AuditService.
d) dto/audit-event.dto.ts — Copy from apps/order-service/src/audit/dto/audit-event.dto.ts.
e) index.ts — Export AuditService, AUDIT_ACTIONS, and the DTO.
f) Update AUDIT_ACTIONS — Replace the minimal stub constant with the full set of actions from the order-service reference. Each service only needs the actions relevant to it, but include the full set for consistency.
3. Update module imports¶
In each service's root module (e.g., apps/shipping-service/src/app/app.module.ts), import the new AuditModule so the real AuditService is available for dependency injection.
4. Verify existing callers¶
Search each service for existing references to AuditService or AUDIT_ACTIONS. Ensure they now use the real implementation without breaking any existing code. The stub's logAuth(action, req, metadata) signature may differ slightly from the real implementation's logAuth(action, req, options) — update callers if needed.
5. Unit tests for each service's AuditService¶
Create __tests__/audit.service.spec.ts and __tests__/audit.repository.spec.ts in each service's audit directory. Follow the test patterns from the order-service:
audit.service.spec.ts should test:
- log() calls the repository and succeeds
- log() catches repository errors and reports to Sentry (best-effort)
- logAuth() correctly extracts user info, tenant ID, and IP
- logAuth() uses DEFAULT_TENANT_ID when no user is available
- getClientIp() extracts from x-forwarded-for, x-real-ip, and falls back to socket address
audit.repository.spec.ts should test:
- create() calls prisma.auditLog.create() with the correct data
- Correct field mapping (tenantId, actorUserId, action, etc.)
Phase 2: Shipping-Service FulfillmentService (1–2 hours)¶
Priority: P1 | Impact: High | Dependencies: None Resolves: TODO.md item 5
The FulfillmentService in apps/shipping-service/src/fulfillment/fulfillment.service.ts is a stub. The shipping-service's fulfillment needs are different from the order-service's — the shipping-service does not create Shopify fulfillments directly. Instead, the shipping-service's role in fulfillment is to:
- Track when a shipment has been created (via Sendcloud)
- Emit events that the order-service listens to for creating Shopify fulfillments
1. Evaluate what createFulfillment should do in the shipping-service¶
The shipping-service's RetryQueueProcessor calls FulfillmentService.createFulfillment({ orderId }) for FULFILLMENT retry jobs. Look at how FULFILLMENT retry jobs are enqueued in the shipping-service to understand what failed operation needs retrying.
If the shipping-service enqueues FULFILLMENT retry jobs when Sendcloud label creation fails, then createFulfillment should:
- Fetch the order details
- Retry the Sendcloud label creation
- Emit a fulfillment.completed or fulfillment.failed event
If no code in the shipping-service currently enqueues FULFILLMENT retry jobs, then the stub may be there for future use. In this case:
- Implement a minimal but real version that logs the attempt and emits the appropriate event
- Add a Logger.warn for the case where no retry logic is needed yet
- Do NOT leave the method body empty — at minimum log and return
2. Update the module¶
Ensure FulfillmentModule imports any services needed by the real implementation (e.g., SendcloudService, EventEmitter2).
3. Unit tests¶
Create __tests__/fulfillment.service.spec.ts with tests for:
- createFulfillment() with a valid orderId
- createFulfillment() with a non-existent order
- Error handling and event emission
- Integration with the retry queue (if applicable)
Phase 3: Print Job Creation Retry Handler (2–3 hours)¶
Priority: P0 | Impact: Critical | Dependencies: None Resolves: TODO.md items 9 (order-service) and 11 (shipping-service)
The processPrintJobRetry() method in both order-service and shipping-service currently throws a ConflictError('Print job retry not yet implemented'). Replace this with a real implementation.
1. Order-service implementation¶
In apps/order-service/src/retry-queue/retry-queue.processor.ts, replace processPrintJobRetry():
private async processPrintJobRetry(job: RetryQueue): Promise<void> {
const payload = job.payload as { lineItemId: string; orderId: string };
// Fetch the line item to get context needed for print job creation
const lineItems = await this.ordersService.getLineItems(payload.orderId);
const lineItem = lineItems.find(li => li.id === payload.lineItemId);
if (!lineItem) {
throw new NotFoundException(
`Line item ${payload.lineItemId} not found for order ${payload.orderId}`
);
}
await this.printJobsService.createPrintJobsForLineItem(lineItem);
}
Key requirements:
- Inject PrintJobsService (or the appropriate interface IPrintJobsService) into the RetryQueueProcessor constructor
- Inject IOrdersService (via ORDERS_SERVICE token) to fetch line items
- Update the RetryQueueModule to import the modules that provide these services
- Remove the ConflictError import if no longer used elsewhere in the file
Important: Check what createPrintJobsForLineItem expects as input. The method signature in apps/order-service/src/print-jobs/print-jobs.service.ts uses a PrintJobLineItemContext type. The retry payload may need to store additional fields (shopifyProductId, shopifyVariantId, productSku, productName, quantity) or these need to be fetched from the line item at retry time.
2. Shipping-service implementation¶
The shipping-service may not have its own PrintJobsService. Check if it does:
- If yes: implement the same pattern as order-service
- If no: the shipping-service should not be processing PRINT_JOB_CREATION retry jobs. In this case, replace the ConflictError with a clear log message explaining this job type is not applicable to the shipping-service, and mark the job as completed (not failed):
private async processPrintJobRetry(job: RetryQueue): Promise<void> {
this.logger.warn(
`PRINT_JOB_CREATION retry job ${job.id} received by shipping-service — ` +
`this job type should be processed by the order-service. Marking as completed.`
);
}
3. Update unit tests¶
In apps/order-service/src/retry-queue/__tests__/retry-queue.processor.spec.ts:
Replace the test 'should handle print job creation retry (throws not implemented)' with:
'should retry print job creation successfully'— mock the line item lookup andcreatePrintJobsForLineItem, verify they are called with correct arguments'should handle failure when line item not found'— verifyNotFoundExceptionis thrown and caught by the outer error handler'should handle failure when print job creation fails'— verifyhandleFailureis called
In apps/shipping-service/src/retry-queue/__tests__/retry-queue.processor.spec.ts:
Update the equivalent test to match the shipping-service's implementation (either real retry or the "not applicable" log).
Phase 4: Cancellation Retry Handler (2–3 hours)¶
Priority: P0 | Impact: Critical | Dependencies: None Resolves: TODO.md items 8 (order-service) and 10 (shipping-service)
The CANCELLATION case in both services' RetryQueueProcessor.processJob() currently only logs a warning. Implement real cancellation retry logic.
1. Order-service implementation¶
In apps/order-service/src/retry-queue/retry-queue.processor.ts, the CANCELLATION case should call the existing CancellationService:
case RetryJobType.CANCELLATION:
await this.processCancellationRetry(job);
break;
Add a new private method:
private async processCancellationRetry(job: RetryQueue): Promise<void> {
const payload = job.payload as { orderId: string; reason?: string };
await this.cancellationService.cancelOrder(payload.orderId, payload.reason);
}
Key requirements:
- Inject CancellationService into the RetryQueueProcessor constructor
- Update RetryQueueModule to import CancellationModule
- Handle the case where the order is already cancelled (idempotent — CancellationService.cancelOrder() already handles this by checking order status)
Cancellation payload structure: Check how CANCELLATION retry jobs are enqueued in the codebase (search for RetryJobType.CANCELLATION in enqueue calls) to confirm the payload structure. It likely contains { orderId: string; reason?: string }, but verify.
2. Shipping-service implementation¶
The shipping-service may need to cancel shipments when an order is cancelled. Check if the shipping-service has a CancellationService or equivalent:
- If yes: inject it and call the appropriate cancellation method
- If no: check what cancellation means in the shipping-service context. It may need to:
- Cancel a Sendcloud shipment/label
- Update the local shipment status
- Emit a cancellation event
If the shipping-service genuinely has no cancellation logic yet, implement a minimal but functional handler:
private async processCancellationRetry(job: RetryQueue): Promise<void> {
const payload = job.payload as { orderId: string; reason?: string };
this.logger.log(`Processing cancellation retry for order ${payload.orderId}`);
// If Sendcloud shipment exists, attempt to cancel it
// Otherwise, log and complete
}
3. Update unit tests¶
In apps/order-service/src/retry-queue/__tests__/retry-queue.processor.spec.ts:
Replace the test 'should handle cancellation job type with warning' with:
'should retry cancellation successfully'— mockCancellationService.cancelOrder, verify it's called with the correct orderId and reason'should handle cancellation when order is already cancelled'— verify idempotent behavior'should handle cancellation failure'— verifyhandleFailureis called whencancelOrderthrows
In apps/shipping-service/src/retry-queue/__tests__/retry-queue.processor.spec.ts:
Update the equivalent test to match the shipping-service's implementation.
Phase 5: Update TODO.md (15 min)¶
Priority: P0 | Impact: Required | Dependencies: Phases 1–4
After all implementations are complete, update TODO.md section "In-Code TODOs, Stubs, and Placeholders" to reflect that items 4–11 have been resolved.
For each resolved item, update the table row to include the implementation status. Add a new column or modify the Description column to append: — ✅ Implemented (YYYY-MM-DD)
Example:
| 4 | `apps/shipping-service/src/audit/index.ts` | 17 | `AuditService.logAuth()` — ✅ Implemented (2026-03-XX) |
✅ Validation Checklist¶
Audit Services (items 4, 6, 7)¶
-
apps/shipping-service/src/audit/audit.service.tsexists with reallog()andlogAuth()methods -
apps/shipping-service/src/audit/audit.repository.tsexists and writes toAuditLogtable -
apps/gridflock-service/src/audit/audit.service.tsexists with reallog()andlogAuth()methods -
apps/gridflock-service/src/audit/audit.repository.tsexists and writes toAuditLogtable -
apps/print-service/src/audit/audit.service.tsexists with reallog()andlogAuth()methods -
apps/print-service/src/audit/audit.repository.tsexists and writes toAuditLogtable -
AUDIT_ACTIONSin all three services has the full set of action constants (matching order-service) - All three services have
AuditModuleproperly imported in their root module - No stub comments remain (
// Stub,// does nothing, etc.) - Unit tests pass for all three
AuditServiceimplementations - Unit tests pass for all three
AuditRepositoryimplementations
Fulfillment Service (item 5)¶
-
apps/shipping-service/src/fulfillment/fulfillment.service.tshas a realcreateFulfillment()implementation (not an empty body) - No stub comments remain
- Unit tests exist and pass for the fulfillment service
-
FulfillmentModuleimports all required dependencies
Print Job Creation Retry (items 9, 11)¶
-
apps/order-service/src/retry-queue/retry-queue.processor.tsprocessPrintJobRetry()callsPrintJobsService.createPrintJobsForLineItem()(or equivalent) - No
ConflictError('Print job retry not yet implemented')remains in order-service -
apps/shipping-service/src/retry-queue/retry-queue.processor.tsprocessPrintJobRetry()has appropriate handling (real retry or explicit "not applicable" log) - No
ConflictError('Print job retry not yet implemented')remains in shipping-service - Unit tests updated: no test expects the
ConflictErrorfor "not yet implemented" - New unit tests verify the actual retry behavior
Cancellation Retry (items 8, 10)¶
-
apps/order-service/src/retry-queue/retry-queue.processor.tsCANCELLATIONcase callsCancellationService.cancelOrder() - No
"Cancellation retry not yet implemented"warning remains in order-service -
apps/shipping-service/src/retry-queue/retry-queue.processor.tsCANCELLATIONcase has appropriate handling - No
"Cancellation retry not yet implemented"warning remains in shipping-service - Unit tests updated: no test expects only a warning log for cancellation
- New unit tests verify the actual cancellation retry behavior
TODO.md Update¶
- Items 4–11 in
TODO.mdare marked as✅ Implemented (YYYY-MM-DD) - No stale placeholder descriptions remain
Build & Lint¶
-
pnpm nx run-many -t build --allpasses -
pnpm nx run-many -t lint --allpasses -
pnpm nx run-many -t test --all --exclude=acceptance-testspasses - No
any,ts-ignore, oreslint-disableintroduced - No
console.login production code
Acceptance Tests¶
- Existing acceptance tests still pass:
pnpm nx run acceptance-tests:e2e - If retry queue endpoints exist in the API, verify they work with the new implementations
- Consider adding acceptance test scenarios for:
- Print job creation retry: enqueue a
PRINT_JOB_CREATIONretry job, wait for processing, verify print jobs are created - Cancellation retry: enqueue a
CANCELLATIONretry job, wait for processing, verify order is cancelled - (These may be integration tests rather than full acceptance tests — use judgment based on the existing test infrastructure)
🚫 Constraints and Rules¶
MUST DO¶
- Follow the order-service's
AuditServiceas the reference implementation for all three audit services - Use best-effort logging (catch errors, report to Sentry, never throw from audit)
- Write to the shared
AuditLogPrisma table - Inject real services via NestJS DI — no direct instantiation
- Use
forwardRef()where circular dependencies exist (follow existing patterns in the retry queue processors) - Keep the retry queue processor's error handling pattern intact: individual job failures are caught and handled via
retryQueueService.handleFailure() - Update existing unit tests that expect stub behavior (e.g., tests expecting
ConflictError('Print job retry not yet implemented')) - Verify cancellation payload structure by searching for
RetryJobType.CANCELLATIONenqueue calls - Update
TODO.mdafter all implementations
MUST NOT¶
- Break any existing functionality or tests
- Add
any,ts-ignore, oreslint-disable - Add
console.login production code paths - Change the retry queue's cron schedule or concurrency model
- Modify the
AuditLogPrisma schema (use the existing table as-is) - Remove the
RetryJobType.CANCELLATIONorRetryJobType.PRINT_JOB_CREATIONenum values from Prisma - Leave any stub comments (
// Stub,// placeholder,// not yet implemented) in the implemented code - Skip unit tests — every new or modified method must have test coverage
SHOULD DO (Nice to Have)¶
- Add OpenTelemetry integration (
OtelLoggerService) to audit services if the@forma3d/observabilitylibrary is available in those services - Add structured Sentry tags to all error captures for better filtering in the Sentry dashboard
- Consider whether the shipping-service's retry queue should handle
PRINT_JOB_CREATIONat all — if not, document why in a code comment - Add event log entries for successful retry operations (e.g.,
'PRINT_JOB_CREATION_RETRIED','CANCELLATION_RETRIED')
📚 Key References¶
Reference Implementations (order-service):
- Audit service: apps/order-service/src/audit/audit.service.ts
- Audit repository: apps/order-service/src/audit/audit.repository.ts
- Audit module: apps/order-service/src/audit/audit.module.ts
- Audit DTO: apps/order-service/src/audit/dto/audit-event.dto.ts
- Audit tests: apps/order-service/src/audit/__tests__/
- Fulfillment service: apps/order-service/src/fulfillment/fulfillment.service.ts
- Cancellation service: apps/order-service/src/cancellation/cancellation.service.ts
- Print jobs service: apps/order-service/src/print-jobs/print-jobs.service.ts
- Retry queue processor: apps/order-service/src/retry-queue/retry-queue.processor.ts
- Retry queue tests: apps/order-service/src/retry-queue/__tests__/retry-queue.processor.spec.ts
Stubs to Replace:
- Shipping audit: apps/shipping-service/src/audit/index.ts
- Shipping fulfillment: apps/shipping-service/src/fulfillment/fulfillment.service.ts
- Shipping retry processor: apps/shipping-service/src/retry-queue/retry-queue.processor.ts
- GridFlock audit: apps/gridflock-service/src/audit/index.ts
- Print-service audit: apps/print-service/src/audit/index.ts
Domain Contracts:
- libs/domain-contracts/ — shared interfaces (IOrdersService, IPrintJobsService, etc.)
- libs/domain/ — shared error types (ConflictError, PrintJobStateError, etc.)
Prisma Schema:
- prisma/schema.prisma — AuditLog table definition, RetryJobType enum
TODO Tracking:
- TODO.md — section "In-Code TODOs, Stubs, and Placeholders", items 4–11
END OF PROMPT
This prompt replaces all stub and placeholder implementations identified in the TODO.md code scan (items 4–11). It covers three categories: (1) real audit services for shipping-service, gridflock-service, and print-service modeled on the order-service reference; (2) a real fulfillment service in shipping-service; (3) working retry handlers for print job creation and cancellation in both order-service and shipping-service. Each implementation includes unit tests and updates to existing tests that expected stub behavior. After completion, TODO.md is updated to mark all items as resolved.