Skip to content

fix(license,spec): correct 30 AGPL licence headers and 11 dangling @spec anchors (gate-28, gate-46) - #2345

Closed
rubenvdlinde wants to merge 11 commits into
developmentfrom
fix/license-triangle-and-spec-anchors
Closed

fix(license,spec): correct 30 AGPL licence headers and 11 dangling @spec anchors (gate-28, gate-46)#2345
rubenvdlinde wants to merge 11 commits into
developmentfrom
fix/license-triangle-and-spec-anchors

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Fixes the two gates that a real 10-file openregister diff failed once the
61-gate Hydra Gates tier was switched on. Both were verified real before
anything was changed.

gate-28 license-triangle — 30 of 32 files corrected

32 files under lib/ declared @license AGPL-3.0-or-later while
composer.json, appinfo/info.xml and the repo LICENSE all declare
EUPL-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:

  • repo LICENSE is the EUPL-1.2 text; its first committed version was
    Apache-2.0 — it was never AGPL
  • appinfo/info.xml was deliberately moved agpl -> EUPL-1.2 in 9107a57
  • every @copyright in lib/ names Conduction B.V.
  • all 30 corrected files were created in this repository by Conduction
    contributors (git log --follow --diff-filter=A); none was vendored
  • 28 of the 30 already carried SPDX-License-Identifier: EUPL-1.2 in the same
    docblock
    as the AGPL tag — the machine-readable line already said EUPL and
    only the PHPDoc tag was stale
  • the canonical form is used by 1486 other tags in lib/ vs 65 AGPL

So these are Nextcloud-app-template boilerplate residue, not derivation from
AGPL code.

2 files deliberately NOT relicensed

lib/Db/Webhook.php and lib/Migration/Version002003000Date20251013000000.php
both assert SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors alongside @copyright Conduction B.V. — two contradictory
owners in one header. Their content is openregister-specific (a Webhook entity;
a migration creating oc_openregister_vectors), which strongly suggests the
Nextcloud 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/*.php files carry the same Nextcloud SPDX block with an
AGPL-3.0-or-later SPDX line while their PHPDoc says EUPL. gate-28 does not see
them (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 never
existed in openspec/specs/file-actions/spec.md. Nothing was invented.

  • (a) wrong anchor, real requirement exists — 7 sites retargeted.
    getFilesForEntity/getFiles/getFileById -> the file-retrieval requirement
    (which names all three explicitly); updateFile -> the file-update
    requirement; deleteFile -> the update/delete node-permission requirement
    (the only one specifying DeleteFileHandler); saveFile and
    attachTagsToFile -> the creation/upsert requirement, which specifies both by
    name.
  • (b) genuinely unspecced — 3 sites. Folder provisioning
    (createEntityFolder, createFolder, createObjectFolderWithoutUpdate) 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 it behind an exclusion, this adds
    ### Requirement: Object and register folder provisioning, written only
    from behaviour verified by reading the implementations. 5 scenarios.
  • (c) none.

Also retargets a pre-existing dangling anchor in
DocumentProcessingHandler.php (#REQ-ORTPR-003) that pointed at an archived
change 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 --json
sees 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:

control (old content) this branch
scope 32 changed files 32 changed files
gate-28 FAIL — 30 files PASS
gate-46 FAIL — 11 anchors PASS
total failing 4 gates 2 gates

The 2 that remain — gate-47 security-change-has-tests (10 files) and
gate-57 orphaned-write-capability (2 methods) — fail identically in the
control
. 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 agent
running 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 in
the runner: concurrent runs are currently mutually corrupting, and the
corruption looks like progress.

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.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ d9cdc07

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.

@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

CI result — both target gates pass; 2 pre-existing failures remain

quality / Hydra Gates (run 31015191144, scope: 28 changed files vs
origin/development):

[gate-28] license-triangle:      PASS
[gate-46] spec-anchor-existence: PASS
[gate-47] security-change-has-tests:   FAIL
[gate-57] orphaned-write-capability:   FAIL
[hydra-gates] 2 gate(s) failed
[hydra-gates] COVERAGE: 59 of 61 declared gates reported a result.

The summary line is present, so this is a completed run and not an aborted one
reading as green.

This PR is NOT green, and I am not merging it. The two failures:

  1. gate-47 security-change-has-tests — wants a test co-change for 10 files
    it classes as security-touching. This PR's edits to all 10 are
    comment-only (licence tags / @spec pointers); no security semantics
    changed. Satisfying it would mean adding tests to a heuristic rather than to
    a behaviour change.
  2. gate-57 orphaned-write-capability — 2 methods
    (MetadataHandler::generateSlugFromValue,
    RelationshipOptimizationHandler::createLightweightObjectEntity). Real
    pre-existing debt needing a code decision, not a header fix.

Both fail identically in a control branch that reverts all three commits
over the same 32-file scope, so nothing here regressed them — but this PR does
pull them into diff scope, which is a genuine cost of touching those files at
all. That is a call for a reviewer, not something to wave through.

quality / Frontend Check (test:l10n) also fails, and it fails on
development itself (head 0673190) — pre-existing, unrelated, and this PR
touches no l10n/ or src/ file.

…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.
@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Closing — superseded by #2355, which carries this PR's surviving value on a branch cut fresh from development.

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 merge

update-branch -> HTTP 422 "merge conflict between base and head"
compare        -> ahead=11 behind=7 diverged

Why most of it is redundant

chore(license): normalise licence declarations to EUPL-1.2 landed on development after this PR was opened and did the gate-28 half independently. Measured:

  • 23 of this PR's 32 files are already byte-identical to development (compared by blob sha).
  • grep -c AGPL returns 0 on development for every file this PR touches. There are no AGPL headers left to correct.

Why merging it would have cost work

Of the 9 files that still differed, 6 differed only because this branch is 7 commits stale. Taking its content would have reverted:

  • lib/Migration/Version002006000Date20251013000000.php, Version1Date20251103120000.php, Version1Date20251103130000.php, Version1Date20251105140000.php — would have re-added @SuppressWarnings(PHPMD.UnusedFormalParameter) tags that development deliberately removed as part of the fleet UnusedFormalParameter effort.
  • lib/Service/File/FolderManagementHandler.php, lib/Service/File/UpdateFileHandler.php — would have reverted logger->debug back to logger->info and restored log lines development deleted as duplicate noise (the "About to update" / "Register updated" pair).

That is the stale-branch hazard in its clearest form: the licence header was the only thing this PR changed in those files, development had already made the identical change, so the branch contributed nothing there while risking a silent revert of everything else.

What survives, and where it went

Exactly 3 files carried unique value. They are in #2355:

file change
openspec/specs/file-actions/spec.md splits two catch-all requirements into five behaviour-named ones
lib/Service/FileService.php repoints 10 dangling @spec anchors
lib/Service/File/DocumentProcessingHandler.php repoints 1 anchor out of openspec/changes/ onto canonical openspec/specs/

Both PHP files were rebuilt from development's current content with only the anchor lines re-applied, so their diff is anchors and nothing else, and the resulting anchor multiset is identical to the one this PR intended (verified by diffing the sorted anchor lists). php -l clean.

Anchors verified against the spec's own headings, with a negative control:

OK    file-creation-and-upsert-run-a-fixed-validate-write-own-tag-pipeline
OK    file-retrieval-resolves-by-id-or-name-and-projects-nodes-to-metadata
OK    file-update-and-delete-enforce-per-action-node-permissions
OK    file-update-guards-locks-preserves-object-tags-and-persists-or-side-metadata-separately
OK    object-and-register-folder-provisioning
MISS  file-crud-operations-on-objects     <- the anchor being replaced

Nothing is lost. Follow #2355.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant