Added comprehensive section covering: - Conventional Commits specification requirement - DCO (Developer Certificate of Origin) signoff requirement - Atomic commits best practices - Examples of proper commit format - Reference to official LibreSign commit guidelines This ensures AI agents follow the project's commit conventions documented at https://docs.libresign.coop/developer_manual/getting-started/commits.html Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
16 KiB
LibreSign Development Guide for AI Agents
LibreSign is a Nextcloud app for digital PDF signature using personal or system-generated certificates. This guide helps AI agents understand the critical patterns and workflows specific to this codebase.
Architecture Overview
Core Components
- Backend (PHP): Nextcloud app following OCP patterns
lib/Service/: Business logic (SignFileService, AccountService, CertificatePolicyService, etc.)lib/Handler/: Signature engines (Pkcs12Handler, Pkcs7Handler) and certificate engines (OpenSslHandler, CfsslHandler)lib/Controller/: API controllers inheriting fromAEnvironmentAwareControllerlib/Db/: Database entities and mappers (File, SignRequest, AccountFile, Crl)
- Frontend (Vue 2): SPA with Pinia store, using
@nextcloud/vuecomponentssrc/views/: Main view components (Validation.vue, etc.)src/store/: Pinia stores for state managementsrc/Components/: Reusable Vue components
- Integration Points:
- Files app integration via
FilesTemplateLoaderand sidebar listeners - Event system using
SignedEvent,SendSignNotificationEvent - Activity, Notification, and Mail listeners for multi-channel notifications
- Files app integration via
Key Architectural Patterns
- Certificate Engines: Factory pattern (
CertificateEngineFactory) returns OpenSSL, CFSSL, or None handler based on config - Signature Engines:
SignEngineFactoryprovides Pkcs12Handler (PDF) or Pkcs7Handler (CMS) based on file type - Identity Methods: Dynamic loading via namespace convention (
OCA\Libresign\Service\IdentifyMethod\*) - Event-Driven: Uses Nextcloud event dispatcher for cross-component communication (Activity, Notifications, Mail)
Development Environment
Docker-Based Setup
LibreSign runs within a Nextcloud Docker container. The project is located at /home/username/projects/nextcloud/php82-master/volumes/nextcloud.
Container structure:
- Main Nextcloud installation:
/var/www/html/ - LibreSign app:
/var/www/html/apps-extra/libresign/ - Data directory:
/var/www/html/data/
Common commands:
# Access container shell
docker compose exec nextcloud bash
# Inside container, navigate to app
cd apps-extra/libresign
# Run commands in container context
docker compose exec nextcloud bash -c "cd apps-extra/libresign && composer test:unit -- --filter TestName"
Testing Workflows
PHPUnit Tests (inside container):
cd apps-extra/libresign
# ⚠️ NEVER run tests without filter - always specify --filter to run specific tests
composer test:unit -- --filter ClassName # Specific test class (REQUIRED)
composer test:unit -- --filter testMethod # Specific test method
composer test:coverage -- --filter ClassName # With coverage for specific tests
CRITICAL: Always use --filter when running tests. Running all tests without filter can:
- Take excessive time
- Consume unnecessary resources
- Make debugging harder
- Clutter output
Behat Integration Tests (inside container):
# First time setup (run only once)
cd tests/integration
composer i
chown -R www-data: .
# Running integration tests (from libresign root directory)
cd tests/integration
runuser -u www-data -- vendor/bin/behat features/<path>.feature
# Example: Run specific feature file
cd tests/integration
runuser -u www-data -- vendor/bin/behat features/auth/login.feature
Frontend Testing:
npm test # Jest tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage
VS Code Tasks
Pre-configured tasks for quick testing (all run inside Docker container):
- "LibreSign: Run OrderCertificatesTrait Tests" - Certificate ordering and validation tests
- "LibreSign: Access Container Shell" - Direct bash access to container
- "LibreSign: Run Tests with Coverage" - PHPUnit with coverage reports
- "LibreSign: Test Certificate Validation" -
testValidateCertificateChaintests - "LibreSign: Test ICP-Brasil Certificates" -
testICPBrasilRealWorldExampletests - "LibreSign: Test LYSEON TECH Certificate Chain" - 4-certificate chain validation
- "LibreSign: Run Specific Test Method" - Custom test method via input
Use Tasks: Run Task command or Ctrl+Shift+P > Tasks: Run Task to execute these.
All tasks automatically run inside the Docker container - no need to manually exec into container.
Code Style and Quality
Write Clear, Self-Documenting Code
Prefer clarity over comments. The code itself should be readable and self-explanatory.
Good practices:
- Use descriptive variable and method names that communicate intent
- Extract complex logic into well-named methods
- Use type hints for all parameters and return types
- Keep methods focused on a single responsibility
When to comment:
- Explain why something is done (not what - the code shows that)
- Document business rules or complex algorithms
- Add PHPDoc for public APIs
Example - Avoid:
// Get certificates and revoke them
$certs = $mapper->find($userId);
foreach ($certs as $c) {
// Revoke the certificate
$service->revoke($c->getSerial());
}
Example - Prefer:
public function revokeUserCertificates(string $userId): int {
$certificates = $this->crlMapper->findIssuedByOwner($userId);
return $this->revokeCertificateList($certificates, $userId);
}
The second example is clear without comments because the method names describe exactly what happens.
Project-Specific Conventions
PHP Namespace Structure
- Services:
OCA\Libresign\Service\*- Business logic, dependency injection via constructor - Controllers:
OCA\Libresign\Controller\*- OCS API controllers: Extend
AEnvironmentAwareController(for OCS routes) - Regular controllers: Follow standard Nextcloud controller pattern (extend
Controller)
- OCS API controllers: Extend
- Handlers:
OCA\Libresign\Handler\*- Specialized processors (SignEngine, CertificateEngine) - Db:
OCA\Libresign\Db\*- Entities extendEntity, Mappers extendQBMapper
Frontend Architecture
- Vue 2 with Composition API patterns via
@vueuse/core - Pinia stores for state (not Vuex)
- Router:
src/router/router.jsdefines SPA routes - OpenAPI integration: TypeScript types generated from OpenAPI spec via
npm run typescript:generate
Critical Files
lib/Service/SignFileService.php: Core signature orchestration (846 lines)lib/Handler/SignEngine/SignEngineHandler.php: Abstract base for signature engineslib/AppInfo/Application.php: Bootstrap, event listener registration, middlewareappinfo/info.xml: App metadata, dependencies, commands, background jobs
Database & Migrations
- Migrations in
lib/Migration/Version*.php(date-based naming) - Use
QBMapperfor queries, avoid raw SQL
Certificate Revocation List (CRL)
- CRL management via
lib/Service/CrlService.php - Database table:
libresign_crlstores revocation information - Serial number validation is critical for CRL operations
- Use
occ libresign:crl:*commands for CRL operations
Git & Commit Practices
LibreSign follows specific commit conventions. See the official commit guidelines for complete details.
Conventional Commits
All commits must follow Conventional Commits specification.
Commit format:
<type>: <short description>
[optional body]
Signed-off-by: Your Name <your.email@example.com>
Common types:
feat: New featurefix: Bug fixdocs: Documentation changestest: Adding or updating testsrefactor: Code refactoringchore: Maintenance tasks
Examples:
# Feature
git commit -s -m "feat: add CRL revocation endpoint"
# Bug fix with description
git commit -s -m "fix: validate certificate chain order
Certificate chains with more than 3 certificates were failing
validation due to incorrect ordering. The OrderCertificatesTrait
now properly orders from end-entity to root.
Fixes #1234"
# Documentation
git commit -s -m "docs: add donation links to GitHub Sponsors and Stripe"
DCO (Developer Certificate of Origin)
Always sign off commits using git commit -s or git commit --signoff.
Every commit must include the Signed-off-by line to comply with the DCO.
Atomic Commits Best Practices
- One logical change per commit
- Commit should be self-contained and functional
- Tests should pass after each commit
- Makes git bisect and code review more effective
When to commit:
- After completing a logical unit of work
- Before switching tasks or branches
- After tests pass for the changes
- At natural breakpoints in development
Bad practices to avoid:
- Committing unrelated changes together
- Generic messages like "fixes", "wip", "misc changes"
- Committing without signoff (
-sflag) - Missing conventional commit type prefix
- Large commits mixing multiple features/fixes
Build & Release Process
Development
make dev-setup # Install deps (composer + npm)
npm run watch # Frontend hot reload
npm run dev # Build frontend once
Production Build
make build-js-production # Webpack production build
composer cs:fix # Format PHP code
composer psalm # Static analysis
Linting & Formatting
- PHP:
.php-cs-fixer.dist.phpconfig, runcomposer cs:fix - PHP Static Analysis:
psalm.xmlconfig, runcomposer psalm - JavaScript: ESLint via
eslint.config.mjs, runnpm run lint:fix - CSS: Stylelint via
stylelint.config.js, runnpm run stylelint:fix
OpenAPI Workflow
composer openapi # Generate OpenAPI spec from PHP annotations
npm run typescript:generate # Generate TypeScript types from spec
Pattern: PHP controllers use @OpenAPI annotations → spec generation → TS types
Testing Patterns
PHPUnit Structure
Unit test organization mirrors source code structure:
- The folder structure in
tests/php/Unit/reflects the structure oflib/ - For every class in
lib/, create a corresponding test intests/php/Unit/with the same path
Examples:
lib/Service/CrlService.php
→ tests/php/Unit/Service/CrlServiceTest.php
lib/Controller/CrlApiController.php
→ tests/php/Unit/Controller/CrlApiControllerTest.php
lib/Db/CrlMapper.php
→ tests/php/Unit/Db/CrlMapperTest.php
This convention makes it easy to locate tests for any class and maintain consistency.
Test directories:
tests/php/Unit/: Unit tests with mocked dependencies (mirrorslib/structure)tests/php/Api/: API integration teststests/php/Integration/: Full integration scenariostests/php/fixtures/: Test fixtures (e.g.,small_valid.pdf)
Mocking Convention
Use PHPUnit createMock() for dependencies:
$this->mockService = $this->createMock(ServiceClass::class);
$this->mockService->method('methodName')->willReturn($value);
Certificate Testing
Special test cases for certificate chain validation:
testValidateCertificateChain: Generic chain validationtestICPBrasilRealWorldExample: ICP-Brasil specifictestLyseonTechRealWorldExample: 4-cert chain example (LYSEON TECH)OrderCertificatesTraitTest: Tests for certificate ordering and chain validation
Important: Certificate chains must be ordered from end-entity to root. The OrderCertificatesTrait handles automatic ordering.
Run via: docker compose exec nextcloud bash -c "cd apps-extra/libresign && composer test:unit -- --filter testName"
Common Pitfalls
Testing Without Filters
🚨 CRITICAL: NEVER run composer test:unit without --filter parameter
- Always specify which test class or method to run
- Running all tests is slow, resource-intensive, and unnecessary for development
- Example:
composer test:unit -- --filter ServiceNameTest
Docker Context Awareness
- Always run tests inside container: Host environment lacks Nextcloud dependencies
- File paths: Use absolute paths inside container (
/var/www/html/apps-extra/libresign/) - Database: Container uses SQLite by default; postgres/mysql for CI
Nextcloud OCP Updates
Run make updateocp after pulling Nextcloud server changes to sync OCP interfaces.
Composer Autoload
After adding new classes:
composer dump-autoload -o
Autoloader suffix: Libresign (see composer.json)
Frontend Build Issues
- Node version: Requires Node 20.x (
enginesinpackage.json) - Missing types: Re-run
npm run typescript:generateafter OpenAPI changes - Webpack cache: Clear
node_modules/.cacheif seeing stale builds
Debugging Tips
PHP Debugging
\OCP\Server::get(\Psr\Log\LoggerInterface::class)->debug('Message', ['context' => $data]);
Logs appear in data/nextcloud.log inside container.
Frontend Debugging
import logger from './logger.js'
logger.debug('Debug info', { data })
Test Debugging
# ⚠️ ALWAYS use --filter when debugging tests
# Run single test with verbose output
vendor/bin/phpunit -c tests/php/phpunit.xml --filter testMethodName --testdox
# Run specific test class
vendor/bin/phpunit -c tests/php/phpunit.xml --filter ClassName --testdox
Never run tests without specifying --filter: This is a development requirement to maintain performance and focus.
Integration Points
Files App
- Sidebar:
LoadSidebarListenerregisters tab - Template loader:
FilesTemplateLoader::register()inApplication::boot() - File actions:
JSActionshelper defines file menu items
Notifications
Multi-channel notification flow:
SendSignNotificationEventdispatched- Listeners:
NotificationListener,MailNotifyListener,ActivityListener,TwofactorGatewayListener - Each listener handles notification in its domain
Background Jobs
OCA\Libresign\BackgroundJob\Reminder: Sends signature remindersOCA\Libresign\BackgroundJob\UserDeleted: Cleanup on user deletion- Registered in
appinfo/info.xml
Commands (OCC)
# Configuration
occ libresign:configure:check # Verify setup
occ libresign:configure:cfssl # Setup CFSSL engine
occ libresign:configure:openssl # Setup OpenSSL engine
# CRL Management
occ libresign:crl:stats # CRL statistics
occ libresign:crl:cleanup # Clean old CRLs
occ libresign:crl:revoke <serial> # Revoke certificate
# Development
occ libresign:developer:reset # Reset dev environment
occ libresign:developer:sign-setup # Sign setup files for app store
# Installation
occ libresign:install --all # Install signing binaries
occ libresign:uninstall # Remove binaries
Workflow Examples
Adding a New Service
- Create
lib/Service/NewService.phpextending no base (use DI) - Add constructor with typed dependencies (auto-wired)
- Implement business logic methods
- Register in controller constructor if needed
- Add unit tests in
tests/php/Unit/Service/NewServiceTest.php
Adding API Endpoint
- Add method to controller in
lib/Controller/ - Use
@OpenAPIannotations for spec generation - Run
composer openapito update spec - Run
npm run typescript:generatefor frontend types - Implement frontend service call in
src/services/
Certificate Chain Debugging
Use test data approach:
$pemChain = [
file_get_contents(__DIR__ . '/fixtures/cert1.pem'),
file_get_contents(__DIR__ . '/fixtures/cert2.pem'),
// ... ordered from end-entity to root
];
$service->validateCertificateChain($pemChain);
Certificate order matters: end-entity → intermediate(s) → root.
Key Principle: LibreSign integrates tightly with Nextcloud's architecture (OCP, event system, Files app). Always consider Nextcloud patterns and test in the full container environment.