fix(license,spec): correct 30 AGPL licence headers and 11 dangling @spec anchors (gate-28, gate-46) - #2345
fix(license,spec): correct 30 AGPL licence headers and 11 dangling @spec anchors (gate-28, gate-46)#2345rubenvdlinde wants to merge 11 commits into
Conversation
The `Hydra Gates` tier is now enabled on openregister, and gate-28
(license-triangle) fails: 32 files under lib/ carry an `@license`
PHPDoc tag of AGPL-3.0-or-later while composer.json, appinfo/info.xml
and the repo LICENSE all declare EUPL-1.2.
PROVENANCE — checked before changing anything, because relicensing a
file that genuinely derives from AGPL code would be a licence violation:
- The repository LICENSE is the EUPL-1.2 text. Its first committed
version was Apache-2.0; it was never AGPL.
- composer.json `.license` = EUPL-1.2; appinfo/info.xml <licence> =
EUPL-1.2, deliberately changed from `agpl` in 9107a57.
- Every @copyright tag in lib/ names Conduction B.V. There is not one
non-Conduction copyright holder in the 30 files touched here.
- All 30 were created in this repository by Conduction contributors
(git log --follow --diff-filter=A); none was imported or vendored.
- 28 of the 30 already carry `SPDX-License-Identifier: EUPL-1.2` in the
very same docblock as the AGPL @license tag — the machine-readable
declaration already said EUPL and only the PHPDoc tag was stale.
- The canonical form is used by 1486 other tags in lib/ against 65 AGPL.
So these are Nextcloud-app-template boilerplate residue, not evidence of
derivation from AGPL-licensed code. Correcting them restores the
declaration Conduction already made everywhere else; it does not
relicense anyone else's work.
DELIBERATELY NOT TOUCHED — 2 of the 32 files are excluded because they
assert a THIRD-PARTY copyright:
lib/Db/Webhook.php
lib/Migration/Version002003000Date20251013000000.php
Both carry `SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud
contributors` alongside `@copyright Conduction B.V.` in the same
docblock — two contradictory owners in one header. Their substantive
content is openregister-specific (a Webhook entity; a migration creating
oc_openregister_vectors), which strongly suggests the Nextcloud line is
template residue too, but that is a call for the copyright owner and not
one to make silently in a cleanup commit. gate-28 therefore still
reports 2 findings whole-tree, by design, pending that decision.
Only `@license` lines changed: 30 files, 55 insertions, 55 deletions,
no other line touched, `php -l` clean on all 30.
gate-46 (spec-anchor-existence) reported 10 unresolved @SPEC anchors in lib/Service/FileService.php, all pointing into openspec/specs/file-actions/spec.md. The file exists; the three fragments they named never did: #object-register-folder-management (3 sites) #file-crud-operations-on-objects (6 sites) #object-tagging-via-nextcloud-system-tags (1 site) Nothing was invented. Each anchor was resolved against what the spec and the code actually say: (a) WRONG ANCHOR, REAL REQUIREMENT EXISTS — 7 sites retargeted. The fragments were hand-written concept names that matched no heading, while the behaviour was already specified under a differently-worded requirement: getFilesForEntity / getFiles / getFileById -> #file-retrieval-resolves-by-id-or-name-and-projects-nodes-to-metadata (that requirement names all three explicitly, incl. the "MUST list the object's files via FileService::getFilesForEntity()") updateFile -> #file-update-guards-locks-preserves-object-tags-and-persists-or-side-metadata-separately deleteFile -> #file-update-and-delete-enforce-per-action-node-permissions (the only requirement specifying DeleteFileHandler's behaviour) saveFile -> #file-creation-and-upsert-run-a-fixed-validate-write-own-tag-pipeline (that requirement specifies saveFile()'s upsert contract by name) attachTagsToFile -> same creation/upsert requirement, which literally specifies the "object:<uuid-or-id> tag merged with caller tags via generateObjectTag() + attachTagsToFile()" step (b) GENUINELY UNSPECCED — 3 sites (createEntityFolder, createFolder, createObjectFolderWithoutUpdate). Folder PROVISIONING was described nowhere: file-actions mentions FolderManagementHandler only as a resolution step, and self-folder-access-control governs @self.folder BINDS, not creation. Rather than hide that behind an exclusion, this adds "### Requirement: Object and register folder provisioning", written only from behaviour verified by reading the implementations (dispatch by entity type; FolderAccessDeniedException re-thrown while any other exception logs and yields null; createFolder trims the path, creates the root folder and openregister group when absent, and returns an existing folder as-is; createObjectFolderWithoutUpdate returns the id without writing it back). 5 scenarios. (c) none — no @SPEC tag here was simply wrong for its method. Verified the spec still parses: `openspec validate file-actions --type spec` reports valid, and `openspec show --json` sees 17 requirements (was 16) with 5 scenarios on the new one. Note for anyone adding a requirement here: OpenSpec takes only the FIRST LINE of the requirement body as its text, so the MUST/SHALL keyword has to be on that first line. Written as a second-line MUST it validates as "Requirement must contain SHALL or MUST keyword" while looking correct.
…-46) `DocumentProcessingHandler::getLastStructurePreservation()` carried @SPEC openspec/changes/tag-preserving-redaction/specs/.../spec.md#REQ-ORTPR-003 which gate-46 cannot resolve, for two independent reasons: 1. It targets a CHANGE directory. That change was archived to openspec/changes/archive/2026-07-23-tag-preserving-redaction/ and its requirements are now canonical in openspec/specs/tag-preserving-redaction/. @SPEC must name the canonical spec, never a change dir. 2. The requirement embeds its ID in a trailing parenthetical — "### Requirement: Every PDF redaction MUST return the `structurePreservation` result block with the exact contracted fields (REQ-ORTPR-003)" — and none of the gate's slug rules (full heading, post-colon text, leading token) yields the bare `REQ-ORTPR-003`. The requirement is real and unchanged; only the pointer was wrong, so this is a retarget, not a new spec. Surfaced because the licence-header commit put this file into gate-46's diff scope — it is pre-existing debt, fixed here rather than left to make the branch red.
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ❌ | ||||
| composer | ✅ | ✅ 173/173 | |||
| npm | ✅ | ✅ 713/713 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-05 14:43 UTC
Download the full PDF report from the workflow artifacts.
CI result — both target gates pass; 2 pre-existing failures remain
The summary line is present, so this is a completed run and not an aborted one This PR is NOT green, and I am not merging it. The two failures:
Both fail identically in a control branch that reverts all three commits
|
…06000Date20251013000000.php This branch predates development's newer work in this file. Its own change here was the AGPL->EUPL licence header, which development has already made identically, so the branch adds nothing and only risks reverting the newer changes (removed @SuppressWarnings(PHPMD.UnusedFormalParameter) tags and the logger->info -> logger->debug demotions). Taking development's content verbatim resolves the conflict without losing either side.
…e20251103120000.php This branch predates development's newer work in this file. Its own change here was the AGPL->EUPL licence header, which development has already made identically, so the branch adds nothing and only risks reverting the newer changes (removed @SuppressWarnings(PHPMD.UnusedFormalParameter) tags and the logger->info -> logger->debug demotions). Taking development's content verbatim resolves the conflict without losing either side.
…e20251103130000.php This branch predates development's newer work in this file. Its own change here was the AGPL->EUPL licence header, which development has already made identically, so the branch adds nothing and only risks reverting the newer changes (removed @SuppressWarnings(PHPMD.UnusedFormalParameter) tags and the logger->info -> logger->debug demotions). Taking development's content verbatim resolves the conflict without losing either side.
…e20251105140000.php This branch predates development's newer work in this file. Its own change here was the AGPL->EUPL licence header, which development has already made identically, so the branch adds nothing and only risks reverting the newer changes (removed @SuppressWarnings(PHPMD.UnusedFormalParameter) tags and the logger->info -> logger->debug demotions). Taking development's content verbatim resolves the conflict without losing either side.
…nagementHandler.php This branch predates development's newer work in this file. Its own change here was the AGPL->EUPL licence header, which development has already made identically, so the branch adds nothing and only risks reverting the newer changes (removed @SuppressWarnings(PHPMD.UnusedFormalParameter) tags and the logger->info -> logger->debug demotions). Taking development's content verbatim resolves the conflict without losing either side.
…leHandler.php This branch predates development's newer work in this file. Its own change here was the AGPL->EUPL licence header, which development has already made identically, so the branch adds nothing and only risks reverting the newer changes (removed @SuppressWarnings(PHPMD.UnusedFormalParameter) tags and the logger->info -> logger->debug demotions). Taking development's content verbatim resolves the conflict without losing either side.
…elopment Rebuilt from development's current content, then re-applied only this branch's @SPEC anchor corrections. The branch's other change to this file was the AGPL->EUPL header, which development has already made identically, so keeping the branch's copy would have reverted newer work. Anchors verified against the spec headings (Requirement: prefix stripped): all five targets resolve, and the old #file-crud-operations-on-objects anchor does NOT resolve -- a negative control showing the old anchors were genuinely dangling and that the check can fail. php -l clean.
…r.php onto development Rebuilt from development's current content, then re-applied only this branch's @SPEC anchor corrections. The branch's other change to this file was the AGPL->EUPL header, which development has already made identically, so keeping the branch's copy would have reverted newer work. Anchors verified against the spec headings (Requirement: prefix stripped): all five targets resolve, and the old #file-crud-operations-on-objects anchor does NOT resolve -- a negative control showing the old anchors were genuinely dangling and that the check can fail. php -l clean.
|
Closing — superseded by #2355, which carries this PR's surviving value on a branch cut fresh from This PR is unmergeable and, more importantly, most of it is now redundant and some of it would actively revert newer work. Why it cannot mergeWhy most of it is redundant
Why merging it would have cost workOf the 9 files that still differed, 6 differed only because this branch is 7 commits stale. Taking its content would have reverted:
That is the stale-branch hazard in its clearest form: the licence header was the only thing this PR changed in those files, What survives, and where it wentExactly 3 files carried unique value. They are in #2355:
Both PHP files were rebuilt from Anchors verified against the spec's own headings, with a negative control: Nothing is lost. Follow #2355. |
Fixes the two gates that a real 10-file openregister diff failed once the
61-gate
Hydra Gatestier was switched on. Both were verified real beforeanything was changed.
gate-28 license-triangle — 30 of 32 files corrected
32 files under
lib/declared@license AGPL-3.0-or-laterwhilecomposer.json,appinfo/info.xmland the repoLICENSEall declareEUPL-1.2.
Provenance was established before any header was touched, because
relicensing a file that genuinely derives from AGPL code would be a licence
violation:
LICENSEis the EUPL-1.2 text; its first committed version wasApache-2.0 — it was never AGPL
appinfo/info.xmlwas deliberately movedagpl->EUPL-1.2in 9107a57@copyrightinlib/names Conduction B.V.contributors (
git log --follow --diff-filter=A); none was vendoredSPDX-License-Identifier: EUPL-1.2in the samedocblock as the AGPL tag — the machine-readable line already said EUPL and
only the PHPDoc tag was stale
lib/vs 65 AGPLSo these are Nextcloud-app-template boilerplate residue, not derivation from
AGPL code.
2 files deliberately NOT relicensed
lib/Db/Webhook.phpandlib/Migration/Version002003000Date20251013000000.phpboth assert
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributorsalongside@copyright Conduction B.V.— two contradictoryowners in one header. Their content is openregister-specific (a Webhook entity;
a migration creating
oc_openregister_vectors), which strongly suggests theNextcloud line is template residue as well — but that is the copyright owner's
call, not one to make silently in a cleanup commit. gate-28 therefore still
reports 2 findings whole-tree, by design.
9 further
lib/Migration/*.phpfiles carry the same Nextcloud SPDX block with anAGPL-3.0-or-laterSPDX line while their PHPDoc says EUPL. gate-28 does not seethem (it reads only
@license), and they are left alone for the same reason.These 11 files need a licensing decision.
gate-46 spec-anchor-existence — 10 dangling anchors resolved
All 10 were in
lib/Service/FileService.php, naming three fragments that neverexisted in
openspec/specs/file-actions/spec.md. Nothing was invented.getFilesForEntity/getFiles/getFileById-> the file-retrieval requirement(which names all three explicitly);
updateFile-> the file-updaterequirement;
deleteFile-> the update/delete node-permission requirement(the only one specifying
DeleteFileHandler);saveFileandattachTagsToFile-> the creation/upsert requirement, which specifies both byname.
(
createEntityFolder,createFolder,createObjectFolderWithoutUpdate) wasdescribed nowhere: file-actions mentions
FolderManagementHandleronly as aresolution step, and
self-folder-access-controlgoverns@self.folderbinds, not creation. Rather than hide it behind an exclusion, this adds
### Requirement: Object and register folder provisioning, written onlyfrom behaviour verified by reading the implementations. 5 scenarios.
Also retargets a pre-existing dangling anchor in
DocumentProcessingHandler.php(#REQ-ORTPR-003) that pointed at an archivedchange directory instead of the canonical spec — pulled into gate-46's diff
scope by the licence commit, so fixed here rather than left red.
openspec validate file-actions --type spec-> valid;openspec show --jsonsees 17 requirements (was 16), 5 scenarios on the new one.
FAIL -> PASS proof
Measured against a control branch that reverts all three commits, so the
identical 32-file set is in scope on both sides:
The 2 that remain —
gate-47 security-change-has-tests(10 files) andgate-57 orphaned-write-capability(2 methods) — fail identically in thecontrol. They are pre-existing debt in files this PR touches
comment-only; nothing here regressed them.
Measurement note
The gate runner writes to hardcoded
/tmp/hydra-gate-*.log. A concurrent agentrunning the same gates on the same tree truncated those files mid-run and
silently corrupted my first counts downward (32->29, 112->63). Every number
above was produced inside a private mount namespace
(
unshare -Urm+mount -t tmpfs tmpfs /tmp) and reproduced. Worth fixing inthe runner: concurrent runs are currently mutually corrupting, and the
corruption looks like progress.