fix(spec): repoint 11 dangling @spec anchors onto real file-actions headings (gate-46) - #2355
fix(spec): repoint 11 dangling @spec anchors onto real file-actions headings (gate-46)#2355rubenvdlinde wants to merge 3 commits into
Conversation
Replaces the two catch-all headings ("Object register folder management",
"File CRUD operations on objects", "Object tagging via Nextcloud system tags")
with requirements named for the behaviour they actually contract, so the @SPEC
anchors in FileService can point at something that exists and stays meaningful.
…s headings (gate-46)
…al spec (gate-46) The old anchor pointed into openspec/changes/, which is a change dir, not the canonical openspec/specs/ home.
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 20:20 UTC
Download the full PDF report from the workflow artifacts.
|
Blocked by gate-47, which I believe is a false positive here — flagging rather than working around it. Everything else is green (25 SUCCESS, This PR changes no code. Measured on the diff: all 30 changed lines under The third file is So gate-47 is asking for a test co-change to accompany an edit that changed only a comment in a file it classifies as security-touching. There is no behaviour here to test — writing a test to satisfy it would be writing a test for nothing, and I am not going to add a suppression, a baseline entry, or flip Suggested fix for the gate owner: gate-47 should exclude diffs whose changed lines in the file are entirely comment/docblock. This is the same shape of problem as the other gate-advice hazards seen tonight — the gate's remedy, applied literally, makes the codebase worse rather than better. The PR itself is sound and its own evidence is in the description: all five repointed anchors resolve against the spec's headings, and the anchor being replaced ( Needs a human call on gate-47 before this can land. |
Hydra gate-46 (spec-anchor-existence) failed on this PR with 6 unresolved targets. All six are pre-existing debt in files this PR already touches, which is what pulled them into the gate's ADR-020 diff scope; none was introduced here. 62 of the other gates passed and coverage was 60 of 60 applicable, so this was a single real failure, not a broken run. lib/Service/VocabularyImportService.php (4 tags) pointed at openspec/changes/skos-concept-registers/... - a CHANGE directory. That change was archived on 2026-07-23, so the path stopped existing the moment it moved to openspec/changes/archive/. A @SPEC tag must target the canonical openspec/specs/ home, which is where the spec lives now; two of the four also carried "#skos-002", which is not a heading, and now name the heading that actually exists. lib/ContextChat/ContentProvider.php (4 tags, 2 distinct anchors) named "#requirement-getitemurl-must-resolve-through-the-existing-deep-link-registry" and "#requirement-initial-import-must-walk-opted-in-schemas-in-batches-and-must- be-re-runnable-via-occ". Neither heading exists; both requirements were merged into one, "Requirement: getItemUrl and initial import reuse existing OpenRegister infrastructure", and the tags were never moved with it. Every target verified against gate-46's OWN two slug rules - slugify() and gh_slugify(), which differ on punctuation inside a word - by resolving each fragment back to the heading text it matches. No overlap with #2355, which repoints a different set of anchors in file-actions.
…InLoopExpression, untrack the phpmd result cache (#2359) * fix(quality): scope the Migration phpmd exclude to lib/, drop a foreign copyright claim, clear the coverage ratchet phpmd-unusedparams.xml carried <exclude-pattern>*/Migration/*</exclude-pattern>. PDepend compiles an exclude-pattern into an UNANCHORED regex (Input\ExcludePathFilter preg_quote()s the pattern, then turns `\*` into `.*`), so that form matches ANY path containing a `/Migration/` segment - lib/Service/Migration/, lib/Command/Migration/, any future lib/*/Migration/. Those are ordinary classes with no interface-mandated signature, so a genuine unused parameter in one would never be reported and the run would still look clean. openconnector carried exactly such a file. openregister has no lib/*/Migration/ directory today, which is precisely why this had to be fixed before one appears: the broad form fails silently and only on the day someone adds the directory. Now `*/lib/Migration/*`, matching the 19 repos that already carry the corrected shape. Twelve files carried `SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors`, scaffolding residue from the Nextcloud app skeleton. The licence sweep in #2350 relabelled the adjacent SPDX-License-Identifier from AGPL-3.0-or-later to EUPL-1.2 - which asserts that Nextcloud GmbH's copyright is EUPL-licensed. We cannot relicense a third party's copyright. Every one of the twelve is Conduction-authored: each carries `@author Conduction Development Team` and `@copyright Conduction B.V.` in its own PHPDoc, and `git log --follow` shows only Conduction committers. The stray holder is corrected to Conduction rather than deleted, so no file loses its REUSE metadata. Side effect, measured by running phpcs on both versions at the identical path: 2 pre-existing "Missing short description in doc comment" errors go away. CountInLoopExpression retired entirely - all 3 baseline entries, all 3 findings. Two are `do { … } while (count($page) === $limit)` where the page is replaced wholesale each iteration and never mutated in the body, so the count is taken once per page into a variable; one is `for ($i = 1; $i < count($rings); $i++)` over an array the body does not touch, so the count is hoisted. Behaviour is identical in all three. .phpmd.result-cache.php untracked and gitignored. It is generated output, and a correctness hazard while committed: `composer quality:phpmd-score` passes --cache, so a stale cache in the tree makes PHPMD replay a verdict for code that has since changed - a gate reporting a result it never computed. .coverage-baseline 58.87 -> 58.93. This was the ONLY red job on development: CI measured coverage that had improved past its own committed baseline. Raising it tightens the ratchet. Unit suite before and after, same container and same vendor: 16030 tests, 35963 assertions, 0 failures, 0 errors - byte-identical totals. * fix(spec): repoint 6 @SPEC anchors that gate-46 could not resolve Hydra gate-46 (spec-anchor-existence) failed on this PR with 6 unresolved targets. All six are pre-existing debt in files this PR already touches, which is what pulled them into the gate's ADR-020 diff scope; none was introduced here. 62 of the other gates passed and coverage was 60 of 60 applicable, so this was a single real failure, not a broken run. lib/Service/VocabularyImportService.php (4 tags) pointed at openspec/changes/skos-concept-registers/... - a CHANGE directory. That change was archived on 2026-07-23, so the path stopped existing the moment it moved to openspec/changes/archive/. A @SPEC tag must target the canonical openspec/specs/ home, which is where the spec lives now; two of the four also carried "#skos-002", which is not a heading, and now name the heading that actually exists. lib/ContextChat/ContentProvider.php (4 tags, 2 distinct anchors) named "#requirement-getitemurl-must-resolve-through-the-existing-deep-link-registry" and "#requirement-initial-import-must-walk-opted-in-schemas-in-batches-and-must- be-re-runnable-via-occ". Neither heading exists; both requirements were merged into one, "Requirement: getItemUrl and initial import reuse existing OpenRegister infrastructure", and the tags were never moved with it. Every target verified against gate-46's OWN two slug rules - slugify() and gh_slugify(), which differ on punctuation inside a word - by resolving each fragment back to the heading text it matches. No overlap with #2355, which repoints a different set of anchors in file-actions. * revert(quality): drop the .coverage-baseline bump — the number drifts with development I raised .coverage-baseline 58.87 -> 58.93 because that was the value CI itself recomputed on development, and Coverage Baseline Check was development's only red job. On this PR it then failed the OTHER direction: Coverage baseline: 58.93% Coverage current: 58.88% FAIL: Coverage dropped by 0.05% Not a regression from this PR. Development moved between my two CI runs — the suite went 16030 -> 16038 tests — so the merge base this PR is measured against computes 58.88, not the 58.93 that development's own HEAD computed earlier. The two jobs also check opposite things: development's runs coverage-guard.php --update-baseline and fails when the committed value is STALE, while a PR runs it plain and fails when coverage DROPS below the committed value. Pinning a number from one tree to satisfy the other is what broke this. So the bump leaves this PR. It belongs in a one-line change computed on development's own HEAD, at a moment development is not mid-merge — not carried in on a PHPMD branch whose merge base keeps moving underneath it. The job was red before this branch existed and is unaffected by it either way. Nothing is weakened: .coverage-baseline returns to development's committed 58.87, exactly as found. * fix(quality): retire CountInLoopExpression from the baseline and drop 10 entries for a deleted file phpmd.baseline.xml 519 -> 506 entries, and one rule family leaves ENTIRELY. A PHPMD baseline entry is scoped to (rule, file) - optionally a method, NEVER a line - so one entry covers every current AND future violation of that rule in that file. It is an open licence, not a record. Shrinking the count is therefore not the point; getting a family to zero is, because only then does a NEW violation of that rule fail CI. CountInLoopExpression: all 3 entries removed. The 3 findings behind them were fixed in this PR, not suppressed. Verified with a single-rule ruleset over all of lib and NO baseline in play: 3 findings before, 0 after. lib/Service/Flow/FlowActionService.php: 10 entries for a file that no longer exists (WeightedMethodCount, CouplingBetweenObjects, LongVariable, ShortVariable, MissingImport, and Cyclomatic/Npath on run/runNamedFlow/runAction). Deleted with the file; suppressing nothing; free to remove. The other 509 entries are all LIVE and were left alone. I checked, and the first answer was wrong in an instructive way: matching baseline entries against the report by rule name reported FIVE families - NPath (62), LongMethod (33), WeightedMethodCount (32), LongParameterList (14), LongClass (6) - as "entirely stale", 147 free deletions. They are not. The baseline stores the rule CLASS (PHPMD\Rule\Design\LongMethod) while the XML report writes the rule NAME (ExcessiveMethodLength), and those five differ. With the mapping applied the accounting closes exactly: 767 true findings, 767 suppressed by live entries, nothing unexplained. Uniformity across five independent families was the tell. Measured with the baseline file MOVED ASIDE, not by dropping --baseline-file: PHPMD auto-discovers phpmd.baseline.xml sitting next to the ruleset and applies it either way, so un-flagging it yields a silently baselined run that looks clean. Independently corroborates #2347's 749 + 16.
Replaces #2345, which became unmergeable after the fleet licence normalisation landed on
development.Why a new PR
#2345 bundled two things: 30 AGPL→EUPL licence headers (gate-28) and 11 dangling
@specanchors (gate-46). Between it being opened and now,chore(license): normalise licence declarations to EUPL-1.2landed ondevelopmentand did the licence half independently.Measured before opening this:
development— the licence work was fully done there.grep -c AGPLacross every file fix(license,spec): correct 30 AGPL licence headers and 11 dangling @spec anchors (gate-28, gate-46) #2345 touched returns 0 ondevelopment.lib/Migration/*files would have re-added the@SuppressWarnings(PHPMD.UnusedFormalParameter)tags thatdevelopmentdeliberately removed (the fleet UFP effort), andFolderManagementHandler/UpdateFileHandlerwould have revertedlogger->debugback tologger->infoand restored log lines that were deliberately deleted as duplicate noise.update-branchon #2345 returns HTTP 422merge conflict between base and head, and the branch isahead=11 behind=7 diverged. Rebuilding it in place kept conflicting, so this branch is cut fresh fromdevelopment@f98fcb4and carries only the surviving work.What this changes
openspec/specs/file-actions/spec.mdlib/Service/FileService.php@specanchorslib/Service/File/DocumentProcessingHandler.phpopenspec/changes/onto the canonicalopenspec/specs/homeNo licence header is touched —
developmentalready owns that.Evidence
Anchors resolved against the spec's own headings (
Requirement:prefix stripped, matching the gate):The negative control is the point: it shows the check can fail, and that the old anchors were genuinely dangling rather than the checker being permissive.
The two PHP files were rebuilt from
development's current content with only the anchor lines re-applied, so their diff is anchors and nothing else.php -lclean on both. The resulting anchor multiset is identical to the one #2345 intended — verified by diffing the sorted anchor lists.