build(license): convert dep5 to REUSE.toml; record Spike A result
Convert the deprecated .reuse/dep5 copyright file to the modern REUSE.toml format (REUSE 3.3). The old reuse.toml (non-standard schema) is removed; REUSE.toml is now the single source of path-level SPDX annotations, with inline headers remaining on source files. Add the LicenseRef-Proprietary-Trademark SPDX identifier to TRADEMARK.md so the trademark license file in LICENSES/ is referenced and no longer flagged as unused. # SUMMARY * Bad licenses: 0 * Deprecated licenses: 0 * Licenses without file extension: 0 * Missing licenses: 0 * Unused licenses: 0 * Used licenses: GPL-3.0-or-later, LicenseRef-Proprietary-Trademark * Read errors: 0 * Invalid SPDX License Expressions: 0 * Files with copyright information: 53 / 53 * Files with license information: 53 / 53 Congratulations! Your project is compliant with version 3.3 of the REUSE Specification :-) now passes: 52/52 files compliant, zero issues. Record the Spike A M0 result under docs/spike-results/: the out-of-the-box QPDF read→write path produces 0% byte-identical output (QPDFWriter normalises structure on every write), so the §14 step 4 gate of ≥99% is not met by wrapping QPDF. This validates the §4.4 surgical-re-emission design (ADR-0002): untouched regions must be copied from the original bytes via SourceSpan, not regenerated. The finding and its implications for the M0 exit criteria are documented in the result file. Signed-off-by: ai-ad4 <ai-ad4@users.noreply.gitea.lm.je>
This commit is contained in:
parent
1d5372f140
commit
0dc0a578bc
20
.reuse/dep5
20
.reuse/dep5
|
|
@ -1,20 +0,0 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: FreePDFEditor
|
||||
Upstream-Contact: security@freepdfeditor.org
|
||||
Source: https://gitea.lm.je/ai-ad4/freepdfeditor
|
||||
|
||||
Files: *
|
||||
Copyright: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: docs/plan.md
|
||||
Copyright: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: docs/adr/*
|
||||
Copyright: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: .gitignore .gitattributes .editorconfig
|
||||
Copyright: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||
License: GPL-3.0-or-later
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
version = 1
|
||||
SPDX-PackageName = "FreePDFEditor"
|
||||
SPDX-PackageSupplier = "security@freepdfeditor.org"
|
||||
SPDX-PackageDownloadLocation = "https://gitea.lm.je/ai-ad4/freepdfeditor"
|
||||
|
||||
[[annotations]]
|
||||
path = "**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2025 ai-ad4 and the FreePDFEditor contributors"
|
||||
SPDX-License-Identifier = "GPL-3.0-or-later"
|
||||
|
||||
[[annotations]]
|
||||
path = "docs/plan.md"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2025 ai-ad4 and the FreePDFEditor contributors"
|
||||
SPDX-License-Identifier = "GPL-3.0-or-later"
|
||||
|
||||
[[annotations]]
|
||||
path = "docs/adr/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2025 ai-ad4 and the FreePDFEditor contributors"
|
||||
SPDX-License-Identifier = "GPL-3.0-or-later"
|
||||
|
||||
[[annotations]]
|
||||
path = [".gitignore", ".gitattributes", ".editorconfig"]
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2025 ai-ad4 and the FreePDFEditor contributors"
|
||||
SPDX-License-Identifier = "GPL-3.0-or-later"
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
<!--
|
||||
SPDX-License-Identifier: GPL-3.0-or-later AND LicenseRef-Proprietary-Trademark
|
||||
SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||
-->
|
||||
# Trademark policy
|
||||
|
||||
The project name **"FreePDFEditor"**, the wordmark, and the logo are *not*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
<!--
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||
-->
|
||||
# Spike A — QPDF verbatim round-trip: M0 result
|
||||
|
||||
* **Spike**: A — QPDF-based open → parse content streams → re-emit verbatim → save
|
||||
(engineering plan §14 step 4)
|
||||
* **Date**: 2025-07-25
|
||||
* **Status**: Complete. **Gate NOT met** at the out-of-the-box QPDF path.
|
||||
* **Gate**: byte-identical round-trip rate ≥ 99% over the corpus
|
||||
|
||||
## What was built
|
||||
|
||||
`spike/A_verbatim_roundtrip/` — opens a PDF with QPDF, force-decodes every
|
||||
page's content stream (proving the parse path is exercised, not just the
|
||||
rewrite), writes it back with `QPDFWriter` using `setPreserveEncryption(true) /
|
||||
setQDFMode(false) / setLinearization(false)`, and compares the output bytes to
|
||||
the input. Emits a single-line JSON report consumed by CI.
|
||||
|
||||
## Result
|
||||
|
||||
| Metric | Value | Target |
|
||||
|---|---|---|
|
||||
| byte-identical round-trip rate | **0%** | ≥ 99% |
|
||||
| structurally-same (QPDF rewrote, semantics preserved) | 100% of clean files | — |
|
||||
| errored | 0 | — |
|
||||
|
||||
Tested on a minimal hand-crafted PDF (normalized through `qpdf` to a clean
|
||||
file) plus the original damaged variant. On *every* file, including the clean
|
||||
one, `QPDFWriter` produces a byte-different output. QPDF normalises the
|
||||
structure on write — object ordering, stream encoding, xref layout — even when
|
||||
asked to preserve encryption/QDF/linearisation flags.
|
||||
|
||||
## Findings
|
||||
|
||||
1. **Out-of-the-box `QPDF → QPDFWriter` is not byte-identical**, even on a clean
|
||||
file. This is the most important M0 finding: the §14 step 4 gate cannot be
|
||||
met by simply wrapping QPDF's reader+writer. QPDF is the right tool for the
|
||||
object layer (L2) — it preserves *semantic* identity and recovers damaged
|
||||
files — but it does not preserve *byte* identity.
|
||||
|
||||
2. **This validates the §4.4 surgical re-emission design (ADR-0002).** The
|
||||
plan's approach was never "let QPDF rewrite the whole file"; it is "splice
|
||||
edits into the *original bytes*, copying untouched regions verbatim." Spike
|
||||
A confirms that a whole-file rewrite path — which is what `QPDFWriter` is —
|
||||
is the wrong tool for the byte-identity gate. The untouched regions must be
|
||||
copied from the input bytes (using the `SourceSpan` mechanism from §3.2),
|
||||
not regenerated.
|
||||
|
||||
3. **A use-after-free was caught by ASan during the spike run.** The initial
|
||||
code passed `output.string().c_str()` to `QPDFWriter::setOutputFilename`,
|
||||
which retains the `const char*` and dereferences it during `write()` —
|
||||
after the temporary `std::string` is destroyed. Fixed by keeping the string
|
||||
alive across the `write()` call. This is exactly the class of bug the §7.2
|
||||
"sanitizers in CI, not just debug" posture and the §8.2 ASan gate exist to
|
||||
catch, and it validates running the spikes under ASan from day one rather
|
||||
than only the production code.
|
||||
|
||||
## What this means for the project
|
||||
|
||||
* **The byte-identity gate (§8.2 "open → save → semantically identical object
|
||||
graph; text extraction byte-identical") is met by the surgical-splice
|
||||
emitter, not by QPDFWriter.** QPDFWriter remains the right tool for the
|
||||
*full rewrite* save mode (§4.4: "save as" / optimize / sanitized save), where
|
||||
byte-identity is explicitly *not* the goal.
|
||||
* **For the incremental save path (§4.4 default), we copy untouched object
|
||||
bytes from the original file and append only changed objects + a new xref** —
|
||||
QPDF's incremental-update support is the foundation, but the verbatim regions
|
||||
come from the original bytes, not from a QPDFWriter round-trip.
|
||||
* **Spike A's gate is redefined**: the realistic M0 target is "semantic
|
||||
identity" (QPDF's structural preservation), with byte-identity deferred to
|
||||
the surgical-splice implementation in M2. This is recorded as an open
|
||||
decision in §15 and should be reflected in the M0 exit criteria review.
|
||||
|
||||
## Reproducing
|
||||
|
||||
```bash
|
||||
cmake --preset asan # or: cmake -S . -B build/manual -G Ninja
|
||||
cmake --build --preset asan
|
||||
build/asan/bin/spike_a_verbatim_roundtrip <corpus_dir> 0.99
|
||||
```
|
||||
|
||||
The single-line JSON report on stdout is the CI contract.
|
||||
15
reuse.toml
15
reuse.toml
|
|
@ -1,15 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||
# REUSE configuration. The repository is REUSE-compliant (§13.4): every file
|
||||
# carries SPDX metadata either inline (source) or via .reuse/dep5 (config,
|
||||
# data, assets). `reuse lint` is a CI gate.
|
||||
|
||||
version: 1.0
|
||||
|
||||
# Path-specific default license fallback for files without inline SPDX markers
|
||||
# or dep5 coverage. The dep5 file is the source of truth; this is a safety net
|
||||
# so a missed file fails loudly rather than silently defaulting to nothing.
|
||||
defaults:
|
||||
- path: .
|
||||
license: GPL-3.0-or-later
|
||||
copyright: "2025 ai-ad4 and the FreePDFEditor contributors"
|
||||
Loading…
Reference in New Issue