Technical Dashboard Aggregate (Task 4.2) Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Implement real backend aggregate for GET /dashboard/technical with strict technical ticket scope and operational-only sections.
Architecture: Keep orchestration in TechnicalDashboardService, scope enforcement in DashboardPermissionService, and data aggregation in DashboardQueryService. Reuse the existing dashboard role-service pattern (admin/owner/staff) while extending technical DTOs from placeholders to explicit typed contracts. Ticket scope comes from assignment (assignedTechnicalId) and internal ownership (createdByTechnicalId, INTERNAL_MAINTENANCE) and is applied before building sections.
Tech Stack: NestJS 11, TypeORM 0.3, PostgreSQL enums/entities, Jest, Bun, Biome.
File Structure Map
Modify
backend/src/dashboard/dashboard.module.ts- Add
TickettoTypeOrmModule.forFeatureso dashboard permission/query services can inject ticket repository.
- Add
backend/src/dashboard/dashboard-permission.service.ts- Implement technical ticket scope methods.
backend/src/dashboard/dashboard-permission.service.spec.ts- Add technical-scope tests.
backend/src/dashboard/dashboard-query.service.ts- Add technical aggregate builder and section query helpers.
backend/src/dashboard/dto/technical-dashboard-response.dto.ts- Replace placeholder arrays with explicit item DTOs.
backend/src/dashboard/services/technical-dashboard.service.ts- Replace placeholder response with real scope + query orchestration.
Create
backend/src/dashboard/services/technical-dashboard.service.spec.ts- Unit tests for orchestration + sensitive-data boundaries.
backend/src/dashboard/dashboard-query.technical.spec.ts- Unit tests for technical section aggregation and scope filtering.
Verification Commands
bun run backend:test -- src/dashboard/dashboard-permission.service.spec.tsbun run backend:test -- src/dashboard/services/technical-dashboard.service.spec.tsbun run backend:test -- src/dashboard/dashboard-query.technical.spec.tsbun run check
Task 1: Write failing tests for technical scope in permission service (RED)
Files:
Modify:
backend/src/dashboard/dashboard-permission.service.spec.tsTest:
backend/src/dashboard/dashboard-permission.service.spec.tsStep 1: Add failing technical scope tests and repository mock wiring
import { Ticket } from '@/tickets/entities/ticket.entity';
import { TicketType } from '@/tickets/enums/ticket-type.enum';
// In providers array
{
provide: getRepositoryToken(Ticket),
useValue: {
find: jest.fn(),
exist: jest.fn(),
},
},
// In beforeEach
let ticketRepository: jest.Mocked<Repository<Ticket>>;
ticketRepository = module.get(getRepositoryToken(Ticket));
it('returns assigned ticket ids for technical user within organization', async () => {
const technicalUser: CurrentUserData = {
...ownerUser,
userId: 'tech-1',
role: RoleName.TECHNICAL,
roles: [RoleName.TECHNICAL],
};
ticketRepository.find.mockResolvedValue([
{ id: 'ticket-1' } as Ticket,
{ id: 'ticket-2' } as Ticket,
]);
await expect(service.getTechnicalAssignedTicketIds(technicalUser)).resolves.toEqual([
'ticket-1',
'ticket-2',
]);
});
it('returns internal ticket ids created by technical user', async () => {
const technicalUser: CurrentUserData = {
...ownerUser,
userId: 'tech-1',
role: RoleName.TECHNICAL,
roles: [RoleName.TECHNICAL],
};
ticketRepository.find.mockResolvedValue([{ id: 'ticket-3' } as Ticket]);
await expect(service.getTechnicalInternalTicketIds(technicalUser)).resolves.toEqual(['ticket-3']);
expect(ticketRepository.find).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
createdByTechnicalId: 'tech-1',
type: TicketType.INTERNAL_MAINTENANCE,
}),
}),
);
});
it('throws when ticket is not assigned to technical user', async () => {
ticketRepository.exist.mockResolvedValue(false);
await expect(service.assertTicketAssignedToTechnical('ticket-1', 'tech-1')).rejects.toThrow(
ForbiddenException,
);
});
it('throws when internal ticket was not created by technical user', async () => {
ticketRepository.exist.mockResolvedValue(false);
await expect(
service.assertInternalTicketCreatedByTechnical('ticket-2', 'tech-1'),
).rejects.toThrow(ForbiddenException);
});
- Step 2: Run test to verify RED
Run:
bun run backend:test -- src/dashboard/dashboard-permission.service.spec.ts
Expected: FAIL with missing method errors (getTechnicalInternalTicketIds) and/or constructor injection mismatch for Ticket repo.
- Step 3: Commit failing tests
git add backend/src/dashboard/dashboard-permission.service.spec.ts
git commit -m "test: define technical ticket scope behavior in dashboard permission service"
Task 2: Implement permission scope for technical tickets (GREEN)
Files:
Modify:
backend/src/dashboard/dashboard.module.tsModify:
backend/src/dashboard/dashboard-permission.service.tsTest:
backend/src/dashboard/dashboard-permission.service.spec.tsStep 1: Register
Ticketentity in dashboard module
import { Ticket } from '@/tickets/entities/ticket.entity';
TypeOrmModule.forFeature([
Area,
Pond,
Device,
DeviceAlert,
DailyLog,
GrowthRecord,
FarmingCycle,
User,
Organization,
Ticket,
]);
- Step 2: Implement technical scope methods in permission service
import { Ticket } from '@/tickets/entities/ticket.entity';
import { TicketType } from '@/tickets/enums/ticket-type.enum';
constructor(
@InjectRepository(Area)
private areaRepository: Repository<Area>,
@InjectRepository(Pond)
private pondRepository: Repository<Pond>,
@InjectRepository(Ticket)
private ticketRepository: Repository<Ticket>,
) {}
async getTechnicalAssignedTicketIds(currentUser: CurrentUserData): Promise<string[]> {
const organizationId = this.getOrganizationIdOrThrow(currentUser);
const tickets = await this.ticketRepository.find({
where: {
organizationId,
assignedTechnicalId: currentUser.userId,
},
select: { id: true },
});
return tickets.map((ticket) => ticket.id);
}
async getTechnicalInternalTicketIds(currentUser: CurrentUserData): Promise<string[]> {
const organizationId = this.getOrganizationIdOrThrow(currentUser);
const tickets = await this.ticketRepository.find({
where: {
organizationId,
createdByTechnicalId: currentUser.userId,
type: TicketType.INTERNAL_MAINTENANCE,
},
select: { id: true },
});
return tickets.map((ticket) => ticket.id);
}
async assertTicketAssignedToTechnical(ticketId: string, userId: string): Promise<void> {
const assigned = await this.ticketRepository.exist({
where: { id: ticketId, assignedTechnicalId: userId },
});
if (!assigned) {
throw new ForbiddenException('Ticket is not assigned to this technical user');
}
}
async assertInternalTicketCreatedByTechnical(ticketId: string, userId: string): Promise<void> {
const owned = await this.ticketRepository.exist({
where: {
id: ticketId,
createdByTechnicalId: userId,
type: TicketType.INTERNAL_MAINTENANCE,
},
});
if (!owned) {
throw new ForbiddenException('Internal ticket is not created by this technical user');
}
}
- Step 3: Run test to verify GREEN
Run:
bun run backend:test -- src/dashboard/dashboard-permission.service.spec.ts
Expected: PASS for all existing + new permission scope tests.
- Step 4: Commit permission implementation
git add backend/src/dashboard/dashboard.module.ts backend/src/dashboard/dashboard-permission.service.ts
git commit -m "feat: implement technical ticket scope in dashboard permission service"
Task 3: Write failing tests for technical dashboard orchestration service (RED)
Files:
Create:
backend/src/dashboard/services/technical-dashboard.service.spec.tsTest:
backend/src/dashboard/services/technical-dashboard.service.spec.tsStep 1: Create failing service tests
import { ForbiddenException } from '@nestjs/common';
import { Test, type TestingModule } from '@nestjs/testing';
import type { CurrentUserData } from '@/auth/decorators/current-user.decorator';
import { RoleName } from '@/common/enums';
import { DashboardPermissionService } from '@/dashboard/dashboard-permission.service';
import { DashboardQueryService } from '@/dashboard/dashboard-query.service';
import { TechnicalDashboardService } from '@/dashboard/services/technical-dashboard.service';
describe('TechnicalDashboardService', () => {
let service: TechnicalDashboardService;
let dashboardPermissionService: jest.Mocked<DashboardPermissionService>;
let dashboardQueryService: jest.Mocked<DashboardQueryService>;
const technicalUser: CurrentUserData = {
userId: 'tech-1',
email: 'tech@example.com',
organizationId: 'org-1',
role: RoleName.TECHNICAL,
roles: [RoleName.TECHNICAL],
permissions: [],
assignedResources: [],
assignedAreaIds: [],
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TechnicalDashboardService,
{
provide: DashboardPermissionService,
useValue: {
assertRole: jest.fn(),
getOrganizationIdOrThrow: jest.fn().mockReturnValue('org-1'),
getTechnicalAssignedTicketIds: jest.fn().mockResolvedValue(['ticket-a']),
getTechnicalInternalTicketIds: jest.fn().mockResolvedValue(['ticket-b']),
},
},
{
provide: DashboardQueryService,
useValue: {
buildGeneratedAtScope: jest.fn().mockReturnValue({
generatedAt: '2026-04-26T00:00:00.000Z',
scope: 'own',
}),
buildTechnicalSections: jest.fn().mockResolvedValue({
assignedTicketQueue: [{ id: 'ticket-a' }],
internalTicketQueue: [{ id: 'ticket-b' }],
todayRoute: [],
deviceDiagnostics: [],
partsNeeded: [],
maintenanceHistory: [],
serviceReportState: { pending: 1, completed: 0 },
}),
},
},
],
}).compile();
service = module.get(TechnicalDashboardService);
dashboardPermissionService = module.get(DashboardPermissionService);
dashboardQueryService = module.get(DashboardQueryService);
});
it('builds technical dashboard using assigned/internal ticket scopes', async () => {
const result = await service.getDashboard(technicalUser);
expect(dashboardQueryService.buildTechnicalSections).toHaveBeenCalledWith({
organizationId: 'org-1',
userId: 'tech-1',
assignedTicketIds: ['ticket-a'],
internalTicketIds: ['ticket-b'],
});
expect(result.scope).toBe('own');
expect(result.kpis.total).toBe(2);
});
it('does not expose business-sensitive dashboard metrics', async () => {
const result = await service.getDashboard(technicalUser);
expect((result.sections as Record<string, unknown>).cost).toBeUndefined();
expect((result.sections as Record<string, unknown>).revenue).toBeUndefined();
expect((result.sections as Record<string, unknown>).fcr).toBeUndefined();
});
it('propagates forbidden error for non-technical role', async () => {
dashboardPermissionService.assertRole.mockImplementation(() => {
throw new ForbiddenException('Role is not allowed');
});
await expect(service.getDashboard(technicalUser)).rejects.toThrow(ForbiddenException);
});
});
- Step 2: Run test to verify RED
Run:
bun run backend:test -- src/dashboard/services/technical-dashboard.service.spec.ts
Expected: FAIL because buildTechnicalSections and internal ticket scope call flow are not implemented yet.
- Step 3: Commit failing tests
git add backend/src/dashboard/services/technical-dashboard.service.spec.ts
git commit -m "test: define technical dashboard aggregation orchestration"
Task 4: Write failing tests for technical query aggregation (RED)
Files:
Create:
backend/src/dashboard/dashboard-query.technical.spec.tsTest:
backend/src/dashboard/dashboard-query.technical.spec.tsStep 1: Create failing query-level tests for scope filtering
import { Test, type TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { DashboardQueryService } from '@/dashboard/dashboard-query.service';
import { Ticket } from '@/tickets/entities/ticket.entity';
import { TicketPriority } from '@/tickets/enums/ticket-priority.enum';
import { TicketStatus } from '@/tickets/enums/ticket-status.enum';
import { TicketType } from '@/tickets/enums/ticket-type.enum';
describe('DashboardQueryService technical sections', () => {
let service: DashboardQueryService;
const ticketRepository = { find: jest.fn() };
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
DashboardQueryService,
{ provide: getRepositoryToken(Ticket), useValue: ticketRepository },
// Other repositories injected by DashboardQueryService can be minimal stubs
{ provide: getRepositoryToken(require('@/farm/entities/area.entity').Area), useValue: {} },
{ provide: getRepositoryToken(require('@/farm/entities/pond.entity').Pond), useValue: {} },
{ provide: getRepositoryToken(require('@/devices/entities/device.entity').Device), useValue: {} },
{ provide: getRepositoryToken(require('@/devices/entities/device-alert.entity').DeviceAlert), useValue: {} },
{ provide: getRepositoryToken(require('@/aquaculture/entities/daily-log.entity').DailyLog), useValue: {} },
{ provide: getRepositoryToken(require('@/aquaculture/entities/growth-record.entity').GrowthRecord), useValue: {} },
{ provide: getRepositoryToken(require('@/aquaculture/entities/farming-cycle.entity').FarmingCycle), useValue: {} },
{ provide: getRepositoryToken(require('@/auth/entities/user.entity').User), useValue: {} },
{ provide: getRepositoryToken(require('@/organizations/entities/organization.entity').Organization), useValue: {} },
],
}).compile();
service = module.get(DashboardQueryService);
});
it('builds sections from assigned/internal scope only', async () => {
ticketRepository.find.mockResolvedValue([
{
id: 'ticket-a',
type: TicketType.CUSTOMER,
status: TicketStatus.ASSIGNED,
priority: TicketPriority.HIGH,
title: 'Ticket A',
pondId: 'pond-1',
deviceId: 'device-1',
farm: { id: 'farm-1', name: 'Farm 1' },
pond: { id: 'pond-1', name: 'Pond 1' },
device: { id: 'device-1', name: 'Device 1', status: 'offline', lastSeenAt: null },
updatedAt: new Date('2026-04-26T08:00:00.000Z'),
} as Ticket,
]);
const sections = await service.buildTechnicalSections({
organizationId: 'org-1',
userId: 'tech-1',
assignedTicketIds: ['ticket-a'],
internalTicketIds: [],
});
expect(sections.assignedTicketQueue).toHaveLength(1);
expect(sections.todayRoute).toHaveLength(1);
expect(sections.deviceDiagnostics).toHaveLength(1);
expect(sections.partsNeeded).toHaveLength(0);
});
});
- Step 2: Run test to verify RED
Run:
bun run backend:test -- src/dashboard/dashboard-query.technical.spec.ts
Expected: FAIL because buildTechnicalSections does not exist in query service.
- Step 3: Commit failing query tests
git add backend/src/dashboard/dashboard-query.technical.spec.ts
git commit -m "test: define technical dashboard query aggregation boundaries"
Task 5: Implement technical DTO, query aggregation, and service orchestration (GREEN)
Files:
Modify:
backend/src/dashboard/dto/technical-dashboard-response.dto.tsModify:
backend/src/dashboard/dashboard-query.service.tsModify:
backend/src/dashboard/services/technical-dashboard.service.tsTest:
backend/src/dashboard/services/technical-dashboard.service.spec.tsTest:
backend/src/dashboard/dashboard-query.technical.spec.tsStep 1: Replace technical DTO placeholders with explicit section contracts
import type { DashboardBaseResponseDto } from '@/dashboard/dto/dashboard-base-response.dto';
export interface TechnicalTicketQueueItemDto {
id: string;
title: string;
type: 'customer' | 'internal_maintenance';
status: string;
priority: string;
farmId: string | null;
farmName: string | null;
pondId: string | null;
pondName: string | null;
deviceId: string | null;
deviceName: string | null;
updatedAt: string;
}
export interface TechnicalTodayRouteItemDto {
ticketId: string;
priority: string;
farmName: string | null;
pondName: string | null;
locationLabel: string;
status: string;
}
export interface TechnicalDeviceDiagnosticsItemDto {
ticketId: string;
deviceId: string;
deviceName: string;
deviceStatus: string;
lastSeenAt: string | null;
}
export interface TechnicalPartNeededItemDto {
ticketId: string;
title: string;
priority: string;
status: string;
}
export interface TechnicalMaintenanceHistoryItemDto {
ticketId: string;
title: string;
status: string;
completedAt: string;
}
export interface TechnicalServiceReportStateDto {
pending: number;
inProgress: number;
completed: number;
waitingParts: number;
}
export interface TechnicalDashboardSectionsDto {
assignedTicketQueue: TechnicalTicketQueueItemDto[];
internalTicketQueue: TechnicalTicketQueueItemDto[];
todayRoute: TechnicalTodayRouteItemDto[];
deviceDiagnostics: TechnicalDeviceDiagnosticsItemDto[];
partsNeeded: TechnicalPartNeededItemDto[];
maintenanceHistory: TechnicalMaintenanceHistoryItemDto[];
serviceReportState: TechnicalServiceReportStateDto;
}
export type TechnicalDashboardResponseDto = DashboardBaseResponseDto<TechnicalDashboardSectionsDto>;
- Step 2: Add technical section builder in query service with strict ticket scope filter
import { In } from 'typeorm';
import { Ticket } from '@/tickets/entities/ticket.entity';
import { TicketStatus } from '@/tickets/enums/ticket-status.enum';
@InjectRepository(Ticket)
private ticketRepository: Repository<Ticket>,
async buildTechnicalSections(params: {
organizationId: string;
userId: string;
assignedTicketIds: string[];
internalTicketIds: string[];
}): Promise<TechnicalDashboardSectionsDto> {
const scopedTicketIds = Array.from(
new Set([...params.assignedTicketIds, ...params.internalTicketIds]),
);
if (scopedTicketIds.length === 0) {
return this.buildEmptyTechnicalSections();
}
const tickets = await this.ticketRepository.find({
where: {
organizationId: params.organizationId,
id: In(scopedTicketIds),
},
relations: {
farm: true,
pond: true,
device: true,
},
order: { updatedAt: 'DESC' },
take: 100,
});
const assignedSet = new Set(params.assignedTicketIds);
const internalSet = new Set(params.internalTicketIds);
const assignedTicketQueue = tickets
.filter((ticket) => assignedSet.has(ticket.id))
.map((ticket) => this.toTechnicalQueueItem(ticket));
const internalTicketQueue = tickets
.filter((ticket) => internalSet.has(ticket.id))
.map((ticket) => this.toTechnicalQueueItem(ticket));
const todayRoute = tickets
.filter((ticket) => assignedSet.has(ticket.id))
.filter((ticket) =>
[
TicketStatus.ASSIGNED,
TicketStatus.ACCEPTED,
TicketStatus.ON_THE_WAY,
TicketStatus.IN_PROGRESS,
TicketStatus.WAITING_PARTS,
TicketStatus.WAITING_CUSTOMER,
].includes(ticket.status),
)
.slice(0, 20)
.map((ticket) => ({
ticketId: ticket.id,
priority: ticket.priority,
farmName: ticket.farm?.name ?? null,
pondName: ticket.pond?.name ?? null,
locationLabel: ticket.pond?.name ?? ticket.farm?.name ?? 'Điểm chưa xác định',
status: ticket.status,
}));
const deviceDiagnostics = tickets
.filter((ticket) => Boolean(ticket.device?.id))
.map((ticket) => ({
ticketId: ticket.id,
deviceId: ticket.device?.id ?? '',
deviceName: ticket.device?.name ?? 'Thiết bị không tên',
deviceStatus: ticket.device?.status ?? 'unknown',
lastSeenAt: ticket.device?.lastSeenAt ? new Date(ticket.device.lastSeenAt).toISOString() : null,
}));
const partsNeeded = tickets
.filter((ticket) => ticket.status === TicketStatus.WAITING_PARTS)
.map((ticket) => ({
ticketId: ticket.id,
title: ticket.title,
priority: ticket.priority,
status: ticket.status,
}));
const maintenanceHistory = tickets
.filter((ticket) => internalSet.has(ticket.id))
.filter((ticket) => [TicketStatus.RESOLVED, TicketStatus.VERIFIED, TicketStatus.CLOSED].includes(ticket.status))
.slice(0, 20)
.map((ticket) => ({
ticketId: ticket.id,
title: ticket.title,
status: ticket.status,
completedAt: ticket.updatedAt.toISOString(),
}));
const serviceReportState = {
pending: tickets.filter((ticket) => ticket.status === TicketStatus.ASSIGNED).length,
inProgress: tickets.filter((ticket) => ticket.status === TicketStatus.IN_PROGRESS).length,
completed: tickets.filter((ticket) => ticket.status === TicketStatus.RESOLVED).length,
waitingParts: tickets.filter((ticket) => ticket.status === TicketStatus.WAITING_PARTS).length,
};
return {
assignedTicketQueue,
internalTicketQueue,
todayRoute,
deviceDiagnostics,
partsNeeded,
maintenanceHistory,
serviceReportState,
};
}
private toTechnicalQueueItem(ticket: Ticket): TechnicalTicketQueueItemDto {
return {
id: ticket.id,
title: ticket.title,
type: ticket.type,
status: ticket.status,
priority: ticket.priority,
farmId: ticket.farmId,
farmName: ticket.farm?.name ?? null,
pondId: ticket.pondId,
pondName: ticket.pond?.name ?? null,
deviceId: ticket.deviceId,
deviceName: ticket.device?.name ?? null,
updatedAt: ticket.updatedAt.toISOString(),
};
}
- Step 3: Replace technical dashboard service placeholder orchestration
async getDashboard(currentUser: CurrentUserData): Promise<TechnicalDashboardResponseDto> {
this.dashboardPermissionService.assertRole(currentUser, [RoleName.TECHNICAL]);
const organizationId = this.dashboardPermissionService.getOrganizationIdOrThrow(currentUser);
const [assignedTicketIds, internalTicketIds] = await Promise.all([
this.dashboardPermissionService.getTechnicalAssignedTicketIds(currentUser),
this.dashboardPermissionService.getTechnicalInternalTicketIds(currentUser),
]);
const sections = await this.dashboardQueryService.buildTechnicalSections({
organizationId,
userId: currentUser.userId,
assignedTicketIds,
internalTicketIds,
});
const base = this.dashboardQueryService.buildGeneratedAtScope('own');
const uniqueScopeTicketCount = new Set([...assignedTicketIds, ...internalTicketIds]).size;
return {
generatedAt: base.generatedAt,
scope: 'own',
kpis: { total: uniqueScopeTicketCount },
sections,
};
}
- Step 4: Run tests to verify GREEN
Run:
bun run backend:test -- src/dashboard/services/technical-dashboard.service.spec.ts
bun run backend:test -- src/dashboard/dashboard-query.technical.spec.ts
bun run backend:test -- src/dashboard/dashboard-permission.service.spec.ts
Expected: PASS, including scope-specific technical tests.
- Step 5: Commit technical aggregate implementation
git add \
backend/src/dashboard/dto/technical-dashboard-response.dto.ts \
backend/src/dashboard/dashboard-query.service.ts \
backend/src/dashboard/services/technical-dashboard.service.ts
git commit -m "feat: implement technical dashboard aggregate with scoped ticket sections"
Task 6: Full verification and final hardening checks
Files:
Modify (if needed): files failing lint/tests from Task 1-5
Test: all files in this plan
Step 1: Run full required verification commands
Run:
bun run backend:test -- src/dashboard/dashboard-permission.service.spec.ts
bun run backend:test -- src/dashboard/services/technical-dashboard.service.spec.ts
bun run backend:test -- src/dashboard/dashboard-query.technical.spec.ts
bun run check
Expected:
All targeted tests PASS.
bun run checkexits 0.Step 2: Manual acceptance checklist against Task 4.2 spec
Assigned ticket queue returns only
assignedTechnicalId = currentUser.userIdscope.Internal ticket queue returns only
createdByTechnicalId = currentUser.userId+INTERNAL_MAINTENANCE.Today route and device diagnostics are derived only from scoped ticket IDs.
No cost/revenue/FCR/survival/staff performance/daily-log detail in technical response DTO.
Cross-technical ticket access checks throw
ForbiddenExceptionin permission service assertions.Step 3: Commit final polish (if any fixes were needed)
git add backend/src/dashboard
git commit -m "test: verify technical dashboard role isolation and acceptance criteria"
Spec Coverage Self-Check
- Scope isolation for technical assigned/internal tickets: covered in Tasks 1, 2, 3, 4, 5, 6.
- Technical sections implementation: covered in Task 5.
- Forbidden cross-technical behavior: covered in Tasks 1 and 6.
- Sensitive metric exclusion: covered in Tasks 3, 5, and 6.
- Verification evidence (
bun run check+ tests): covered in Task 6.
No uncovered spec requirements remain.