Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ec63f20
docs(isms): add CS-437 foundational documents design spec
Marfuen May 29, 2026
a549077
feat(isms): add IsmsDocument data model + Context register (CS-437)
Marfuen May 29, 2026
da24c23
feat(isms): add ISMS documents API module with PDF/DOCX export (CS-437)
Marfuen May 29, 2026
53a3714
feat(isms): framework-grouped Documents IA + Context of the Organizat…
Marfuen May 29, 2026
c866962
refactor(isms): gate on evidence resource, drop feature flag (CS-437)
Marfuen May 29, 2026
4246785
feat(isms): add register tables for Interested Parties, Requirements,…
Marfuen May 29, 2026
e19969b
feat(isms): add api for all six foundational documents via type dispa…
Marfuen May 29, 2026
b69bdbc
feat(isms): detail pages for the 5 remaining foundational documents (…
Marfuen May 29, 2026
cb4ea5b
feat(isms): add IsmsProfile for wizard inputs (CS-438)
Marfuen May 29, 2026
569d886
feat(isms): document-creation wizard (CS-438)
Marfuen May 29, 2026
fcb6eb1
feat(isms): wire "Run setup wizard" entry point into ISMS overview (C…
Marfuen May 29, 2026
0b7d7fa
feat(isms): model ISMS document types as framework-editor parts (CS-437)
Marfuen Jun 1, 2026
1427559
feat(isms): map ISMS document types as framework-editor parts (CS-437…
Marfuen Jun 1, 2026
b30b7be
feat(isms): add document<->control link junctions (CS-437, Paul)
Marfuen Jun 1, 2026
60c1e88
feat(isms): link documents to individual controls (CS-437, Paul)
Marfuen Jun 1, 2026
6d653c7
feat(isms): polish the ISMS area to be design-system-only and auditor…
Marfuen Jun 1, 2026
feb5e05
fix(isms): resolve 11 QA findings from the live walkthrough (CS-437)
Marfuen Jun 1, 2026
3a2e36e
feat(isms): restyle register UIs as read-first cards to match the ove…
Marfuen Jun 1, 2026
e305d1d
feat(isms): make the issues registers denser (CS-437)
Marfuen Jun 1, 2026
8d8d4eb
feat(isms): refine register source provenance display (CS-437)
Marfuen Jun 2, 2026
25a98e4
feat(isms): source as a tag pill under the description (CS-437)
Marfuen Jun 2, 2026
9f7dd25
feat(isms): drop redundant "framework" suffix on source pills (CS-437)
Marfuen Jun 2, 2026
e8ce2ac
feat(isms): move approval banner to the top of each document (CS-437)
Marfuen Jun 2, 2026
f78727a
feat(isms): drop redundant source pill from interested-parties cards …
Marfuen Jun 2, 2026
8074dcd
feat(isms): render context-of-organization as an auditor-ready docume…
Marfuen Jun 2, 2026
175d3c8
feat(documents): gate ISMS tab behind a flag, move SOA to general doc…
Marfuen Jun 2, 2026
613bb14
Merge remote-tracking branch 'origin/main' into mariano/cs-437
Marfuen Jun 2, 2026
34d153f
refactor(isms): consolidate register CRUD into 3 generic endpoints (C…
Marfuen Jun 2, 2026
d4404e8
fix(isms): address cubic review findings on PR #2992 (CS-437)
Marfuen Jun 2, 2026
0d720c3
fix(isms): derive ensure-setup org from session; skip blank objective…
Marfuen Jun 2, 2026
d9481f8
fix(isms): resolve cubic re-review follow-ups (CS-437)
Marfuen Jun 2, 2026
fcfb519
fix(isms): seed narrative when empty on regenerate (CS-437)
Marfuen Jun 2, 2026
a9c6db1
refactor(isms): backend red-team remediation — phase 1 (CS-437)
Marfuen Jun 2, 2026
67e0ce5
refactor(isms): frontend red-team remediation — phase 2 (CS-437)
Marfuen Jun 2, 2026
65fb75c
test(isms): add coverage flagged by the red-team review — phase 3 (CS…
Marfuen Jun 2, 2026
1229976
Merge branch 'main' into mariano/cs-437
Marfuen Jun 2, 2026
338f760
fix(isms): resolve post-merge cubic findings (CS-437)
Marfuen Jun 2, 2026
f135113
Merge branch 'main' into mariano/cs-437
Marfuen Jun 2, 2026
ef221cd
Merge remote-tracking branch 'origin/main' into mariano/cs-437
Marfuen Jun 3, 2026
c5a52fc
fix(isms): resolve cubic findings — concurrency, constraint, schemas …
Marfuen Jun 3, 2026
1862c88
Merge branch 'main' into mariano/cs-437
Marfuen Jun 3, 2026
e92d083
fix(isms): serialize register position allocation; gate control-link …
Marfuen Jun 3, 2026
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
2 changes: 2 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@
"better-auth": "^1.4.22",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"docx": "^9.7.1",
"dotenv": "^17.2.3",
"esbuild": "^0.27.1",
"exceljs": "^4.4.0",
"express": "^4.21.2",
"helmet": "^8.1.0",
"jose": "^6.0.12",
"jspdf": "^4.2.0",
"jspdf-autotable": "^5.0.8",
"mammoth": "^1.8.0",
"nanoid": "^5.1.6",
"pdf-lib": "^1.17.1",
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { VendorsModule } from './vendors/vendors.module';
import { ContextModule } from './context/context.module';
import { TrustPortalModule } from './trust-portal/trust-portal.module';
import { ControlTemplateModule } from './framework-editor/control-template/control-template.module';
import { IsmsDocumentTemplateModule } from './framework-editor/isms-document-template/isms-document-template.module';
import { FrameworkEditorFrameworkModule } from './framework-editor/framework/framework.module';
import { PolicyTemplateModule } from './framework-editor/policy-template/policy-template.module';
import { RequirementModule } from './framework-editor/requirement/requirement.module';
Expand All @@ -34,6 +35,7 @@ import { QuestionnaireModule } from './questionnaire/questionnaire.module';
import { VectorStoreModule } from './vector-store/vector-store.module';
import { KnowledgeBaseModule } from './knowledge-base/knowledge-base.module';
import { SOAModule } from './soa/soa.module';
import { IsmsModule } from './isms/isms.module';
import { IntegrationPlatformModule } from './integration-platform/integration-platform.module';
import { CloudSecurityModule } from './cloud-security/cloud-security.module';
import { BrowserbaseModule } from './browserbase/browserbase.module';
Expand Down Expand Up @@ -94,6 +96,7 @@ import { OffboardingChecklistModule } from './offboarding-checklist/offboarding-
HealthModule,
TrustPortalModule,
ControlTemplateModule,
IsmsDocumentTemplateModule,
FrameworkEditorFrameworkModule,
PolicyTemplateModule,
RequirementModule,
Expand All @@ -104,6 +107,7 @@ import { OffboardingChecklistModule } from './offboarding-checklist/offboarding-
VectorStoreModule,
KnowledgeBaseModule,
SOAModule,
IsmsModule,
IntegrationPlatformModule,
CloudSecurityModule,
BrowserbaseModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsInt, IsOptional, IsString, MaxLength, Min } from 'class-validator';

export class UpdateIsmsDocumentTemplateDto {
@ApiPropertyOptional({ example: 'Context of the Organization' })
@IsString()
@IsOptional()
@MaxLength(255)
name?: string;

@ApiPropertyOptional({
example: 'Internal and external issues relevant to the ISMS.',
})
@IsString()
@IsOptional()
@MaxLength(5000)
description?: string;

@ApiPropertyOptional({ example: '4.1' })
@IsString()
@IsOptional()
@MaxLength(32)
clause?: string;

@ApiPropertyOptional({ example: 0 })
@IsInt()
@IsOptional()
@Min(0)
sortOrder?: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
jest.mock('@db', () => ({ db: {} }));

import { Test, TestingModule } from '@nestjs/testing';
import { PlatformAdminGuard } from '../../auth/platform-admin.guard';
import { IsmsDocumentTemplateController } from './isms-document-template.controller';
import { IsmsDocumentTemplateService } from './isms-document-template.service';

jest.mock('../../auth/platform-admin.guard', () => ({
PlatformAdminGuard: class MockPlatformAdminGuard {},
}));

describe('IsmsDocumentTemplateController', () => {
let controller: IsmsDocumentTemplateController;

const mockService = {
findAll: jest.fn(),
update: jest.fn(),
linkRequirement: jest.fn(),
unlinkRequirement: jest.fn(),
linkControlTemplate: jest.fn(),
unlinkControlTemplate: jest.fn(),
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [IsmsDocumentTemplateController],
providers: [
{ provide: IsmsDocumentTemplateService, useValue: mockService },
],
})
.overrideGuard(PlatformAdminGuard)
.useValue({ canActivate: () => true })
.compile();

controller = module.get(IsmsDocumentTemplateController);
jest.clearAllMocks();
});

it('passes the frameworkId filter to findAll', async () => {
mockService.findAll.mockResolvedValue([]);

await controller.findAll('fw_1');

expect(mockService.findAll).toHaveBeenCalledWith('fw_1');
});

it('passes id and dto to update', async () => {
mockService.update.mockResolvedValue({ id: 'tpl_ctx' });

await controller.update('tpl_ctx', { name: 'New' });

expect(mockService.update).toHaveBeenCalledWith('tpl_ctx', { name: 'New' });
});

it('maps params + query to linkRequirement', async () => {
mockService.linkRequirement.mockResolvedValue({ message: 'linked' });

await controller.linkRequirement('tpl_ctx', 'req_41', 'fw_1');

expect(mockService.linkRequirement).toHaveBeenCalledWith({
templateId: 'tpl_ctx',
requirementId: 'req_41',
frameworkId: 'fw_1',
});
});

it('maps params + query to unlinkRequirement', async () => {
mockService.unlinkRequirement.mockResolvedValue({ message: 'unlinked' });

await controller.unlinkRequirement('tpl_ctx', 'req_41', 'fw_1');

expect(mockService.unlinkRequirement).toHaveBeenCalledWith({
templateId: 'tpl_ctx',
requirementId: 'req_41',
frameworkId: 'fw_1',
});
});

it('maps params + query to linkControlTemplate', async () => {
mockService.linkControlTemplate.mockResolvedValue({ message: 'linked' });

await controller.linkControlTemplate('tpl_ctx', 'ct_1', 'fw_1');

expect(mockService.linkControlTemplate).toHaveBeenCalledWith({
templateId: 'tpl_ctx',
controlTemplateId: 'ct_1',
frameworkId: 'fw_1',
});
});

it('maps params + query to unlinkControlTemplate', async () => {
mockService.unlinkControlTemplate.mockResolvedValue({ message: 'unlinked' });

await controller.unlinkControlTemplate('tpl_ctx', 'ct_1', 'fw_1');

expect(mockService.unlinkControlTemplate).toHaveBeenCalledWith({
templateId: 'tpl_ctx',
controlTemplateId: 'ct_1',
frameworkId: 'fw_1',
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { PlatformAdminGuard } from '../../auth/platform-admin.guard';
import { UpdateIsmsDocumentTemplateDto } from './dto/update-isms-document-template.dto';
import { IsmsDocumentTemplateService } from './isms-document-template.service';

@ApiTags('Framework Editor ISMS Document Templates')
@Controller({ path: 'framework-editor/isms-document-template', version: '1' })
@UseGuards(PlatformAdminGuard)
Comment thread
Marfuen marked this conversation as resolved.
Comment thread
Marfuen marked this conversation as resolved.
export class IsmsDocumentTemplateController {
constructor(private readonly service: IsmsDocumentTemplateService) {}

@Get()
@ApiOperation({ summary: 'List ISMS document templates' })
async findAll(@Query('frameworkId') frameworkId?: string) {
return this.service.findAll(frameworkId);
}

@Patch(':id')
@ApiOperation({ summary: 'Update an ISMS document template' })
@UsePipes(new ValidationPipe({ whitelist: true, transform: true }))
async update(
@Param('id') id: string,
@Body() dto: UpdateIsmsDocumentTemplateDto,
) {
return this.service.update(id, dto);
}

@Post(':id/requirements/:requirementId')
@ApiOperation({
summary: 'Link a requirement to an ISMS document template for a framework',
})
async linkRequirement(
@Param('id') id: string,
@Param('requirementId') requirementId: string,
@Query('frameworkId') frameworkId?: string,
) {
return this.service.linkRequirement({
templateId: id,
requirementId,
frameworkId,
});
}

@Delete(':id/requirements/:requirementId')
@ApiOperation({
summary:
'Unlink a requirement from an ISMS document template for a framework',
})
async unlinkRequirement(
@Param('id') id: string,
@Param('requirementId') requirementId: string,
@Query('frameworkId') frameworkId?: string,
) {
return this.service.unlinkRequirement({
templateId: id,
requirementId,
frameworkId,
});
}

@Post(':id/controls/:controlTemplateId')
@ApiOperation({
summary:
'Link a control template to an ISMS document template for a framework',
})
async linkControlTemplate(
@Param('id') id: string,
@Param('controlTemplateId') controlTemplateId: string,
@Query('frameworkId') frameworkId?: string,
) {
return this.service.linkControlTemplate({
templateId: id,
controlTemplateId,
frameworkId,
});
}

@Delete(':id/controls/:controlTemplateId')
@ApiOperation({
summary:
'Unlink a control template from an ISMS document template for a framework',
})
async unlinkControlTemplate(
@Param('id') id: string,
@Param('controlTemplateId') controlTemplateId: string,
@Query('frameworkId') frameworkId?: string,
) {
return this.service.unlinkControlTemplate({
templateId: id,
controlTemplateId,
frameworkId,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { AuthModule } from '../../auth/auth.module';
import { IsmsDocumentTemplateController } from './isms-document-template.controller';
import { IsmsDocumentTemplateService } from './isms-document-template.service';

@Module({
imports: [AuthModule],
controllers: [IsmsDocumentTemplateController],
providers: [IsmsDocumentTemplateService],
exports: [IsmsDocumentTemplateService],
})
export class IsmsDocumentTemplateModule {}
Loading
Loading