Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion demo/symfony8/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions demo/symfony8/config/reference.php
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,7 @@
* @psalm-type NowoWordToPdfConfig = array{
* engine?: scalar|Param|null, // Conversion backend: libreoffice (LibreOffice Writer / soffice). // Default: "libreoffice"
* default_profile?: scalar|Param|null, // Default: "default"
* max_batch_size?: int|Param, // Maximum number of Word sources accepted by convertMany() (DoS guard). // Default: 10
* profiles?: array<string, array{ // Default: []
* binary_path?: scalar|Param|null, // Absolute path to soffice/libreoffice. Null = auto-detect. // Default: null
* temp_dir?: scalar|Param|null, // Writable temp directory. Null = sys_get_temp_dir(). // Default: null
Expand Down
10 changes: 10 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
## Table of contents

- [[Unreleased]](#unreleased)
- [[1.3.2] - 2026-10-01](#132-2026-10-01)
- [[1.3.1] - 2026-09-28](#131-2026-09-28)
- [[1.3.0] - 2026-09-28](#130-2026-09-28)
- [[1.2.10] - 2026-09-27](#1210-2026-09-27)
Expand All @@ -26,6 +27,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [1.3.2] - 2026-10-01

### Fixed

- **Igor IncompleteReset:** `RuntimeBootCheckListener::$checked` is marked `#[WorkerSafe]` (once-per-worker LibreOffice probe, **REQ-RUNTIME-002**) so `make igor` reports 0 WARN without changing the intentional no-op `reset()`.

[1.3.2]: https://github.com/nowo-tech/WordToPdfBundle/releases/tag/v1.3.2
[1.3.1]: https://github.com/nowo-tech/WordToPdfBundle/releases/tag/v1.3.1

## [1.3.1] - 2026-09-28

### Fixed
Expand Down
3 changes: 2 additions & 1 deletion docs/FRANKENPHP-WORKER-AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
| Audit date | 2026-09-25 |
| Method | Manual review of every PHP file under `src/` + PHPStan `ruleset-classic.neon` + `ruleset-worker-no-kernel-reset.neon` |
| Remediation (2026-09-25) | `WordToPdfBundle::getContainerExtension()` made stateless; `RuntimeBootCheckListener` implements `ResetInterface` with a **no-op** `reset()` so the once-per-worker LibreOffice probe survives `services_resetter`; phpstan includes `ruleset-worker-no-kernel-reset.neon` |
| Remediation (2026-10-01) | `$checked` marked `#[WorkerSafe]` so Igor IncompleteReset is silent while the probe remains once-per-worker |
| **Verdict** | ✅ **Viable under scenario B** (and A) — no request-scoped state leaks; LibreOffice runs as an isolated child process with explicit timeouts |

## Execution model assumed
Expand All @@ -22,7 +23,7 @@ A bundle that is safe under **B** is safe under **A** and under classic mode / P

| Area | Status | Notes |
|------|--------|-------|
| Mutable state in shared services | ✅ | Only `RuntimeBootCheckListener::$checked` (intentional once-per-worker; `reset()` is a no-op) |
| Mutable state in shared services | ✅ | Only `RuntimeBootCheckListener::$checked` (intentional once-per-worker; `#[WorkerSafe]`; `reset()` is a no-op) |
| Static properties / `static` locals | ✅ | None that hold request data |
| `ResetInterface` / `kernel.reset` | ✅ | Boot-check listener implements `ResetInterface`; `reset()` intentionally does **not** clear `$checked` |
| Request / user / locale in services | ✅ | Paths, options and profiles are method arguments |
Expand Down
10 changes: 10 additions & 0 deletions docs/UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- [From 1.2.8 to 1.2.9](#from-128-to-129)
- [From 1.2.7 to 1.2.8](#from-127-to-128)
- [Unreleased](#unreleased)
- [To 1.3.2](#to-132)
- [To 1.3.1](#to-131)
- [To 1.3.0](#to-130)
- [To 1.2.10](#to-1210)
- [To 1.2.7](#to-127)
Expand Down Expand Up @@ -48,6 +50,14 @@ composer update nowo-tech/word-to-pdf-bundle

## Unreleased

## To 1.3.2

From **1.3.1** — Igor `#[WorkerSafe]` on the once-per-worker boot-check flag. **No application upgrade steps.**

```bash
composer update nowo-tech/word-to-pdf-bundle
```

## To 1.3.1

From **1.3.0** — CI style only. **No application upgrade steps.**
Expand Down
2 changes: 1 addition & 1 deletion specs/001-baseline/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Production source count: every PHP class under `src/` plus `src/Resources/config

1. **Given** profile `timeout: N`, **When** LibreOffice exceeds N seconds, **Then** `ConversionFailedException` (timed out) is thrown and the process is stopped.
2. **Given** a hanging binary, **When** idle timeout elapses, **Then** the same failure path runs (no endless worker block).
3. **Given** FrankenPHP worker with kernel reuse and `check_on_boot: true`, **When** multiple main requests hit the same worker, **Then** the LibreOffice boot probe runs at most once (`RuntimeBootCheckListener::reset()` is a no-op).
3. **Given** FrankenPHP worker with kernel reuse and `check_on_boot: true`, **When** multiple main requests hit the same worker, **Then** the LibreOffice boot probe runs at most once (`RuntimeBootCheckListener::reset()` is a no-op; `$checked` is `#[WorkerSafe]` for Igor).
4. **Given** `phpstan.neon.dist` includes `ruleset-worker-no-kernel-reset.neon`, **When** `composer phpstan` runs, **Then** analysis passes with no `frankenphp.worker.noMissingResetInterface` errors on bundle services.

---
Expand Down
5 changes: 5 additions & 0 deletions src/EventListener/RuntimeBootCheckListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Nowo\WordToPdfBundle\EventListener;

use IgorPhp\IgorBundle\Attribute\WorkerSafe;
use Nowo\WordToPdfBundle\Config\ProfileResolver;
use Nowo\WordToPdfBundle\Exception\MissingDependencyException;
use Nowo\WordToPdfBundle\Runtime\RuntimeRequirementsChecker;
Expand All @@ -22,6 +23,10 @@
*/
final class RuntimeBootCheckListener implements EventSubscriberInterface, ResetInterface
{
#[WorkerSafe(
scope: 'boot-time',
reason: 'Once-per-worker LibreOffice probe (REQ-RUNTIME-002); intentionally survives services_resetter',
)]
private bool $checked = false;

private readonly LoggerInterface $logger;
Expand Down
Loading